Skip to content

Commit

Permalink
Merge pull request #551 from GuillaumeGomez/wait-for-pos
Browse files Browse the repository at this point in the history
Add `wait-for-position` command
  • Loading branch information
GuillaumeGomez authored Nov 26, 2023
2 parents da4e44a + 58a3848 commit 08427f9
Show file tree
Hide file tree
Showing 75 changed files with 1,507 additions and 441 deletions.
14 changes: 14 additions & 0 deletions goml-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ Here's the command list:
* [`wait-for-css`](#wait-for-css)
* [`wait-for-document-property`](#wait-for-document-property)
* [`wait-for-local-storage`](#wait-for-local-storage)
* [`wait-for-position`](#wait-for-position)
* [`wait-for-property`](#wait-for-property)
* [`wait-for-text`](#wait-for-text)
* [`wait-for-window-property`](#wait-for-window-property)
Expand Down Expand Up @@ -1853,6 +1854,19 @@ wait-for-local-storage: {"key": "value"}
wait-for-local-storage: {"key": "value", "key2": "value2"}
```

#### wait-for-position

**wait-for-position** command waits for the element(s) position to match the expected values. Only `x` and `y` values are accepted as keys for the positions. Examples:

```
wait-for-position: (".class", {"x": 1, "y": 2})
wait-for-position: ("//*[@class='class']", {"x": 1, "y": 2})
// If you want to wait for all elements matching this selector/XPath, use `ALL`:
wait-for-position: (".class", {"x": 1, "y": 2}, ALL)
wait-for-position: ("//*[@class='class']", {"x": 1, "y": 2}, ALL)
```

#### wait-for-property

**wait-for-property** command waits for the given element to have the expected values for the given properties. It'll wait up to 30 seconds by default before failing (can be changed with the [`timeout`](#timeout) command).
Expand Down
1 change: 1 addition & 0 deletions src/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const ORDERS = {
'wait-for-count': commands.parseWaitForCount,
'wait-for-document-property': commands.parseWaitForDocumentProperty,
'wait-for-local-storage': commands.parseWaitForLocalStorage,
'wait-for-position': commands.parseWaitForPosition,
'wait-for-property': commands.parseWaitForProperty,
'wait-for-text': commands.parseWaitForText,
'wait-for-window-property': commands.parseWaitForWindowProperty,
Expand Down
1 change: 1 addition & 0 deletions src/commands/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ module.exports = {
'parseWaitForCount': wait.parseWaitForCount,
'parseWaitForDocumentProperty': wait.parseWaitForDocumentProperty,
'parseWaitForLocalStorage': wait.parseWaitForLocalStorage,
'parseWaitForPosition': wait.parseWaitForPosition,
'parseWaitForProperty': wait.parseWaitForProperty,
'parseWaitForText': wait.parseWaitForText,
'parseWaitForWindowProperty': wait.parseWaitForWindowProperty,
Expand Down
90 changes: 20 additions & 70 deletions src/commands/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const {
makeExtendedChecks,
makeTextExtendedChecks,
getSizes,
validatePositionDict,
commonPositionCheckCode,
} = require('./utils.js');
const { COLOR_CHECK_ERROR } = require('../consts.js');
const { cleanString } = require('../parser.js');
Expand Down Expand Up @@ -923,83 +925,31 @@ function parseAssertPositionInner(parser, assertFalse) {
return selector;
}

const isPseudo = !selector.isXPath && selector.pseudo !== null;

const checkAllElements = selector.checkAllElements;

const json = selector.tuple[1].getRaw();

const entries = validateJson(json, {'number': []}, 'JSON dict key');

if (entries.error !== undefined) {
return entries;
}

const varName = 'parseAssertPosition';

let checks = '';
for (const [key, value] of Object.entries(entries.values)) {
if (key === 'x') {
checks += `
checkAssertPosBrowser(elem, 'left', 'marginLeft', 'X', ${value.value}, errors);`;
} else if (key === 'y') {
checks += `
checkAssertPosBrowser(elem, 'top', 'marginTop', 'Y', ${value.value}, errors);`;
} else {
return {
'error': 'Only accepted keys are "x" and "y", found `' +
`"${key}"\` (in \`${selector.tuple[1].getErrorText()}\`)`,
};
}
}

let check;
if (assertFalse) {
check = `\
if (v === value || roundedV === Math.round(value)) {
errors.push("same " + kind + " values (whereas it shouldn't): " + v + " (or " + roundedV + ") \
!= " + value);
}`;
} else {
check = `\
if (v !== value && roundedV !== Math.round(value)) {
errors.push("different " + kind + " values: " + v + " (or " + roundedV + ") != " + value);
}`;
const checks = validatePositionDict(selector.tuple[1]);
if (checks.error !== undefined) {
return checks;
}

const pseudo = isPseudo ? selector.pseudo : '';
const code = `\
function checkAssertPosBrowser(e, field, styleField, kind, value, errors) {
const v = browserUiTestHelpers.getElementPosition(e, "${pseudo}", field, styleField);
const roundedV = Math.round(v);
${indentString(check, 1)}
}${checks}`;
const errorsVarName = 'errors';
const varName = 'assertPosition';

let whole = getAndSetElements(selector, varName, checkAllElements) + '\n';
let indent = 0;
if (checkAllElements) {
whole += `for (let i = 0, len = ${varName}.length; i < len; ++i) {\n`;
indent = 1;
}
whole += indentString(`\
await page.evaluate(elem => {
const errors = [];
${indentString(code, 1)}
if (errors.length !== 0) {
const errs = errors.join("; ");
throw "The following errors happened: [" + errs + "]";
}
`, indent);
if (checkAllElements) {
whole += ` }, ${varName}[i]);
let whole = commonPositionCheckCode(
selector,
checks.checks,
selector.checkAllElements,
varName,
errorsVarName,
assertFalse,
getAndSetElements(selector, varName, selector.checkAllElements) + '\n',
);
whole += `\
if (${errorsVarName}.length > 0) {
throw "The following errors happened: [" + ${errorsVarName}.join("; ") + "]";
}`;
} else {
whole += `}, ${varName});`;
}

return {
'instructions': [whole],
'warnings': entries.warnings,
'warnings': checks.warnings,
'wait': false,
'checkResult': true,
};
Expand Down
78 changes: 78 additions & 0 deletions src/commands/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,82 @@ const height = e.offsetHeight;
const width = e.offsetWidth;`;
}

function validatePositionDict(json) {
const entries = validateJson(json.getRaw(), {'number': []}, 'JSON dict key');

if (entries.error !== undefined) {
return entries;
}

let checks = '';
for (const [key, value] of Object.entries(entries.values)) {
if (key === 'x') {
checks += `
checkAssertPosBrowser(elem, 'left', 'marginLeft', 'X', ${value.value}, innerErrors);`;
} else if (key === 'y') {
checks += `
checkAssertPosBrowser(elem, 'top', 'marginTop', 'Y', ${value.value}, innerErrors);`;
} else {
return {
'error': 'Only accepted keys are "x" and "y", found `' +
`"${key}"\` (in \`${json.getErrorText()}\`)`,
};
}
}
return {
'checks': checks,
'warnings': entries.warnings,
};
}

function commonPositionCheckCode(
selector, checks, checkAllElements, varName, errorsVarName, assertFalse, whole,
) {
const isPseudo = !selector.isXPath && selector.pseudo !== null;

let check;
if (assertFalse) {
check = `\
if (v === value || roundedV === Math.round(value)) {
errors.push("same " + kind + " values (whereas it shouldn't): " + v + " (or " + roundedV + ") \
!= " + value);
}`;
} else {
check = `\
if (v !== value && roundedV !== Math.round(value)) {
errors.push("different " + kind + " values: " + v + " (or " + roundedV + ") != " + value);
}`;
}

const pseudo = isPseudo ? selector.pseudo : '';
const code = `\
function checkAssertPosBrowser(e, field, styleField, kind, value, errors) {
const v = browserUiTestHelpers.getElementPosition(e, "${pseudo}", field, styleField);
const roundedV = Math.round(v);
${indentString(check, 1)}
}${checks}`;

let indent = 0;
whole += `const ${errorsVarName} = [];\n`;
if (checkAllElements) {
whole += `for (let i = 0, len = ${varName}.length; i < len; ++i) {\n`;
indent = 1;
}
whole += indentString(`\
${errorsVarName}.push(...await page.evaluate(elem => {
const innerErrors = [];
${indentString(code, 1)}
return innerErrors;
`, indent);
if (checkAllElements) {
whole += ` }, ${varName}[i]));
}`;
} else {
whole += `}, ${varName}));`;
}
return whole;
}

module.exports = {
'getAndSetElements': getAndSetElements,
'checkIntegerTuple': checkIntegerTuple,
Expand All @@ -478,4 +554,6 @@ module.exports = {
'makeExtendedChecks': makeExtendedChecks,
'makeTextExtendedChecks': makeTextExtendedChecks,
'getSizes': getSizes,
'validatePositionDict': validatePositionDict,
'commonPositionCheckCode': commonPositionCheckCode,
};
74 changes: 74 additions & 0 deletions src/commands/wait.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const {
fillEnabledChecks,
makeExtendedChecks,
makeTextExtendedChecks,
validatePositionDict,
commonPositionCheckCode,
} = require('./utils.js');

function incrWait(error) {
Expand Down Expand Up @@ -949,13 +951,85 @@ ${indentString(incr, 1)}
};
}

// Possible inputs:
//
// * ("CSS selector", JSON dict)
// * ("XPath", JSON dict)
function parseWaitForPosition(parser) {
const checker = waitForChecker(parser, true);
if (checker.error !== undefined) {
return checker;
}

const enabledChecks = Object.create(null);
const warnings = [];

if (checker.tuple.length === 3) {
const identifiers = ['ALL'];
const ret = fillEnabledChecks(
checker.tuple[2], identifiers, enabledChecks, warnings, 'third');
if (ret !== null) {
return ret;
}
}

const checkAllElements = enabledChecks['ALL'];

const checks = validatePositionDict(checker.tuple[1]);
if (checks.error !== undefined) {
return checks;
}
if (checks.warnings) {
warnings.push(...checks.warnings);
}

const selector = checker.selector;
const varName = 'assertPosition';
const errorsVarName = 'errors';

const whole = commonPositionCheckCode(
selector,
checks.checks,
checkAllElements,
varName,
errorsVarName,
false,
'',
);

const [init, looper] = waitForElement(selector, varName, enabledChecks['ALL'] === true);
const incr = incrWait(`\
const err = ${errorsVarName}.join(", ");
throw new Error("The following checks still fail: [" + err + "]");`);

const instructions = `\
${init}
while (true) {
${indentString(looper, 1)}
${indentString(whole, 1)}
if (errors.length === 0) {
break;
}
${indentString(incr, 1)}
}`;

return {
'instructions': [instructions],
'wait': false,
'warnings': warnings,
'checkResult': true,
};
}

module.exports = {
'parseWaitFor': parseWaitFor,
'parseWaitForAttribute': parseWaitForAttribute,
'parseWaitForCount': parseWaitForCount,
'parseWaitForCss': parseWaitForCss,
'parseWaitForDocumentProperty': parseWaitForDocumentProperty,
'parseWaitForLocalStorage': parseWaitForLocalStorage,
'parseWaitForPosition': parseWaitForPosition,
'parseWaitForProperty': parseWaitForProperty,
'parseWaitForText': parseWaitForText,
'parseWaitForWindowProperty': parseWaitForWindowProperty,
Expand Down
7 changes: 7 additions & 0 deletions tests/html_files/elements.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<button id="js-wait-document-prop" onclick="updateWaitDocumentProp()">hey</button>
<button id="js-wait-prop" onclick="updateWaitElementProp()">hey</button>
<button id="js-change-colors" onclick="updateButtonsColor()">hey</button>
<button id="js-change-pos" onclick="updateWaitElementPosition()">hey</button>
<script>
window.windowProp = null;
document.windowProp = null;
Expand Down Expand Up @@ -126,6 +127,12 @@
document.getElementById("js-wait-prop")["someProp"] = "hello";
}, 250);
}
function updateWaitElementPosition() {
setTimeout(() => {
document.getElementById("js-change-pos").style.marginLeft = "3px";
document.getElementById("js-change-pos").style.marginTop = "3px";
}, 250);
}
document.getElementById("js-wait-prop")["hehe"] = "hoho";
async function updateButtonsColor() {
for (const but of document.getElementsByTagName("button")) {
Expand Down
18 changes: 9 additions & 9 deletions tests/test-js/api-output/parseAssertPosition/basic-1.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
instructions = [
"""let parseAssertPosition = await page.$(\"a\");
if (parseAssertPosition === null) { throw '\"a\" not found'; }
await page.evaluate(elem => {
const errors = [];
"""let assertPosition = await page.$(\"a\");
if (assertPosition === null) { throw '\"a\" not found'; }
const errors = [];
errors.push(...await page.evaluate(elem => {
const innerErrors = [];
function checkAssertPosBrowser(e, field, styleField, kind, value, errors) {
const v = browserUiTestHelpers.getElementPosition(e, \"\", field, styleField);
const roundedV = Math.round(v);
if (v !== value && roundedV !== Math.round(value)) {
errors.push(\"different \" + kind + \" values: \" + v + \" (or \" + roundedV + \") != \" + value);
}
}
if (errors.length !== 0) {
const errs = errors.join(\"; \");
throw \"The following errors happened: [\" + errs + \"]\";
}
}, parseAssertPosition);""",
return innerErrors;
}, assertPosition));if (errors.length > 0) {
throw \"The following errors happened: [\" + errors.join(\"; \") + \"]\";
}""",
]
warnings = [
]
Expand Down
Loading

0 comments on commit 08427f9

Please sign in to comment.