Skip to content

Commit

Permalink
Merge pull request #619 from GuillaumeGomez/wait-for-property-false
Browse files Browse the repository at this point in the history
Add `wait-for-property-false` command'
  • Loading branch information
GuillaumeGomez authored Sep 27, 2024
2 parents df705a8 + 5c03947 commit e5d23cb
Show file tree
Hide file tree
Showing 29 changed files with 1,195 additions and 4 deletions.
52 changes: 51 additions & 1 deletion goml-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ Here's the command list:
* [`wait-for-position`](#wait-for-position)
* [`wait-for-position-false`](#wait-for-position-false)
* [`wait-for-property`](#wait-for-property)
* [`wait-for-property-false`](#wait-for-property-false)
* [`wait-for-text`](#wait-for-text)
* [`wait-for-size`](#wait-for-size)
* [`wait-for-window-property`](#wait-for-window-property)
Expand Down Expand Up @@ -2173,7 +2174,7 @@ wait-for-property: ("//*[@id='element']", {"scrollTop": 10, "name": "hello"})
wait-for-property: ("#element", {"key"."sub-key": "value"})
```

If you want to check that a property doesn't exist, you can use `null`:
If you want to wait for a property to be removed, you can use `null`:

```
// Checking that "property-name" doesn't exist.
Expand All @@ -2200,6 +2201,55 @@ wait-for-property: (
)
```

If you want to wait for any of the properties to not be the provided ones, take a look at [`wait-for-property-false`](#wait-for-property-false).

#### wait-for-property-false

**wait-for-property-false** command waits for any of the given element(s) to not 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).

Examples:

```
wait-for-property-false: ("#element", {"scrollTop": 10})
wait-for-property-false: ("#element", {"scrollTop": 10, "name": "hello"})
// Same with an XPath:
wait-for-property-false: ("//*[@id='element']", {"scrollTop": 10})
wait-for-property-false: ("//*[@id='element']", {"scrollTop": 10, "name": "hello"})
// You can also use object-paths:
wait-for-property-false: ("#element", {"key"."sub-key": "value"})
```

If you want to wait for a property to be created, you can use `null`:

```
// Checking that "property-name" doesn't exist.
wait-for-property-false: ("#id > .class", {"property-name": null})
```

You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH", "STARTS_WITH" or "NEAR".

```
wait-for-property-false: (
"#id",
{"className": "where", "title": "a title"},
STARTS_WITH,
)
```

You can even combine the checks:

```
wait-for-property-false: (
"#id",
{"className": "where", "title": "a title"},
[STARTS_WITH, ENDS_WITH, ALL],
)
```

If you want to wait for all properties to have the expected values, take a look at [`wait-for-property`](#wait-for-property).

#### wait-for-size

**wait-for-size** command wait for the given element(s) that either the "width" or the "height" (or both) have the expected value. Examples:
Expand Down
2 changes: 2 additions & 0 deletions src/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const ORDERS = {
'wait-for-position': commands.parseWaitForPosition,
'wait-for-position-false': commands.parseWaitForPositionFalse,
'wait-for-property': commands.parseWaitForProperty,
'wait-for-property-false': commands.parseWaitForPropertyFalse,
'wait-for-size': commands.parseWaitForSize,
'wait-for-text': commands.parseWaitForText,
'wait-for-window-property': commands.parseWaitForWindowProperty,
Expand Down Expand Up @@ -158,6 +159,7 @@ const FATAL_ERROR_COMMANDS = [
'wait-for-count',
'wait-for-count-false',
'wait-for-property',
'wait-for-property-false',
'wait-for-text',
'write',
'write-into',
Expand Down
1 change: 1 addition & 0 deletions src/commands/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ module.exports = {
'parseWaitForPosition': wait.parseWaitForPosition,
'parseWaitForPositionFalse': wait.parseWaitForPositionFalse,
'parseWaitForProperty': wait.parseWaitForProperty,
'parseWaitForPropertyFalse': wait.parseWaitForPropertyFalse,
'parseWaitForSize': wait.parseWaitForSize,
'parseWaitForText': wait.parseWaitForText,
'parseWaitForWindowProperty': wait.parseWaitForWindowProperty,
Expand Down
23 changes: 21 additions & 2 deletions src/commands/wait.js
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,17 @@ ${indentString(incr, 1)}
//
// * ("selector", {"property name": "expected property value"})
function parseWaitForProperty(parser) {
return parseWaitForPropertyInner(parser, false);
}

// Possible inputs:
//
// * ("selector", {"property name": "expected property value"})
function parseWaitForPropertyFalse(parser) {
return parseWaitForPropertyInner(parser, true);
}

function parseWaitForPropertyInner(parser, waitFalse) {
const identifiers = ['ALL', 'CONTAINS', 'ENDS_WITH', 'NEAR', 'STARTS_WITH'];
const ret = validator(parser, {
kind: 'tuple',
Expand Down Expand Up @@ -895,10 +906,17 @@ the check will be performed on the element itself`);
warnings.push(`Special checks (${k.join(', ')}) will be ignored for \`null\``);
}

let comp = '===';
let errorMessage = '"The following properties still don\'t match: [" + props + "]"';
if (waitFalse) {
comp = '!==';
errorMessage = '"All properties still match"';
}

const [init, looper] = waitForElement(selector, varName, {checkAll: enabledChecks.has('ALL')});
const incr = incrWait(`\
const props = nonMatchingProps.join(", ");
throw new Error("The following properties still don't match: [" + props + "]");`);
throw new Error(${errorMessage});`);

const instructions = `\
async function checkPropForElem(elem) {
Expand Down Expand Up @@ -940,7 +958,7 @@ ${init}
while (true) {
${indentString(looper, 1)}
${indentString(checker, 1)}
if (nonMatchingProps.length === 0) {
if (nonMatchingProps.length ${comp} 0) {
break;
}
${indentString(incr, 1)}
Expand Down Expand Up @@ -1283,6 +1301,7 @@ module.exports = {
'parseWaitForPosition': parseWaitForPosition,
'parseWaitForPositionFalse': parseWaitForPositionFalse,
'parseWaitForProperty': parseWaitForProperty,
'parseWaitForPropertyFalse': parseWaitForPropertyFalse,
'parseWaitForText': parseWaitForText,
'parseWaitForWindowProperty': parseWaitForWindowProperty,
'parseWaitForWindowPropertyFalse': parseWaitForWindowPropertyFalse,
Expand Down
88 changes: 88 additions & 0 deletions tests/api-output/parseWaitForPropertyFalse/basic-1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
instructions = [
"""async function checkPropForElem(elem) {
return await elem.evaluate(e => {
function checkObjectPaths(object, path, callback, notFoundCallback) {
const found = [];
for (const subPath of path) {
found.push(subPath);
if (object === undefined || object === null) {
notFoundCallback(found);
return;
}
object = object[subPath];
}
callback(object);
}
const nonMatchingProps = [];
const parseWaitForPropDict = [];
const nullProps = [];
for (const prop of nullProps) {
checkObjectPaths(e, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Expected property `\" + p + \"` to not exist, found: `\" + val + \"`\");
return;
}
}, _notFound => {
});
}
for (const [parseWaitForPropKey, parseWaitForPropValue] of parseWaitForPropDict) {
checkObjectPaths(e, parseWaitForPropKey, val => {
if (val === undefined) {
const p = parseWaitForPropKey.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Property `\" + p + \"` doesn't exist\");
return;
}
if (val !== parseWaitForPropValue) {
nonMatchingProps.push(\"expected `\" + parseWaitForPropValue + \"` for property `\" + parseWaitForPropKey + \"`, found `\" + val + \"`\");
}
}, _notFound => {
const p = parseWaitForPropKey.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Property `\" + p + \"` doesn't exist\");
});
}
return nonMatchingProps;
});
}
const timeLimit = page.getDefaultTimeout();
const timeAdd = 50;
let allTime = 0;
let parseWaitForProp = null;
while (true) {
while (true) {
parseWaitForProp = await page.$$(\"a\");
if (parseWaitForProp.length !== 0) {
parseWaitForProp = parseWaitForProp[0];
break;
}
await new Promise(r => setTimeout(r, timeAdd));
if (timeLimit === 0) {
continue;
}
allTime += timeAdd;
if (allTime >= timeLimit) {
throw new Error(\"The CSS selector \\\"a\\\" was not found\");
}
}
const nonMatchingProps = await checkPropForElem(parseWaitForProp);
if (nonMatchingProps.length !== 0) {
break;
}
await new Promise(r => setTimeout(r, timeAdd));
if (timeLimit === 0) {
continue;
}
allTime += timeAdd;
if (allTime >= timeLimit) {
const props = nonMatchingProps.join(\", \");
throw new Error(\"All properties still match\");
}
}""",
]
wait = false
warnings = [
]
checkResult = true
88 changes: 88 additions & 0 deletions tests/api-output/parseWaitForPropertyFalse/basic-2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
instructions = [
"""async function checkPropForElem(elem) {
return await elem.evaluate(e => {
function checkObjectPaths(object, path, callback, notFoundCallback) {
const found = [];
for (const subPath of path) {
found.push(subPath);
if (object === undefined || object === null) {
notFoundCallback(found);
return;
}
object = object[subPath];
}
callback(object);
}
const nonMatchingProps = [];
const parseWaitForPropDict = [[[\"x\"],\"1\"]];
const nullProps = [];
for (const prop of nullProps) {
checkObjectPaths(e, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Expected property `\" + p + \"` to not exist, found: `\" + val + \"`\");
return;
}
}, _notFound => {
});
}
for (const [parseWaitForPropKey, parseWaitForPropValue] of parseWaitForPropDict) {
checkObjectPaths(e, parseWaitForPropKey, val => {
if (val === undefined) {
const p = parseWaitForPropKey.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Property `\" + p + \"` doesn't exist\");
return;
}
if (val !== parseWaitForPropValue) {
nonMatchingProps.push(\"expected `\" + parseWaitForPropValue + \"` for property `\" + parseWaitForPropKey + \"`, found `\" + val + \"`\");
}
}, _notFound => {
const p = parseWaitForPropKey.map(p => `\"${p}\"`).join('.');
nonMatchingProps.push(\"Property `\" + p + \"` doesn't exist\");
});
}
return nonMatchingProps;
});
}
const timeLimit = page.getDefaultTimeout();
const timeAdd = 50;
let allTime = 0;
let parseWaitForProp = null;
while (true) {
while (true) {
parseWaitForProp = await page.$$(\"a\");
if (parseWaitForProp.length !== 0) {
parseWaitForProp = parseWaitForProp[0];
break;
}
await new Promise(r => setTimeout(r, timeAdd));
if (timeLimit === 0) {
continue;
}
allTime += timeAdd;
if (allTime >= timeLimit) {
throw new Error(\"The CSS selector \\\"a\\\" was not found\");
}
}
const nonMatchingProps = await checkPropForElem(parseWaitForProp);
if (nonMatchingProps.length !== 0) {
break;
}
await new Promise(r => setTimeout(r, timeAdd));
if (timeLimit === 0) {
continue;
}
allTime += timeAdd;
if (allTime >= timeLimit) {
const props = nonMatchingProps.join(\", \");
throw new Error(\"All properties still match\");
}
}""",
]
wait = false
warnings = [
]
checkResult = true
Loading

0 comments on commit e5d23cb

Please sign in to comment.