Skip to content

Commit

Permalink
Merge pull request #596 from GuillaumeGomez/object-path
Browse files Browse the repository at this point in the history
Add support for object-path in `wait-for-document-property`, `wait-for-window-property` and `wait-for-property` commands
  • Loading branch information
GuillaumeGomez authored Mar 31, 2024
2 parents 3211e94 + 06fe3ed commit 0d1996e
Show file tree
Hide file tree
Showing 44 changed files with 1,609 additions and 452 deletions.
9 changes: 9 additions & 0 deletions goml-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -1914,6 +1914,9 @@ Examples:
```
wait-for-document-property: {"key": "value"}
wait-for-document-property: {"key": "value", "key2": "value2"}
// You can also use object-paths:
wait-for-document-property: {"key"."sub-key": "value"}
```

You can use more specific checks as well by using one of the following identifiers: CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
Expand Down Expand Up @@ -1962,6 +1965,9 @@ wait-for-property: ("#element", {"scrollTop": 10, "name": "hello"})
// Same with an XPath:
wait-for-property: ("//*[@id='element']", {"scrollTop": 10})
wait-for-property: ("//*[@id='element']", {"scrollTop": 10, "name": "hello"})
// You can also use object-paths:
wait-for-property: ("#element", {"key"."sub-key": "value"})
```

If you want to check that a property doesn't exist, you can use `null`:
Expand Down Expand Up @@ -2047,6 +2053,9 @@ Examples:
```
wait-for-window-property: {"key": "value"}
wait-for-window-property: {"key": "value", "key2": "value2"}
// You can also use object-paths:
wait-for-window-property: {"key"."sub-key": "value"}
```

You can use more specific checks as well by using one of the following identifiers: CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
Expand Down
96 changes: 60 additions & 36 deletions src/commands/wait.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
validatePositionDictV2,
commonPositionCheckCode,
commonSizeCheckCode,
generateCheckObjectPaths,
} = require('./utils.js');
const { validator } = require('../validator.js');
// Not the same `utils.js`!
Expand Down Expand Up @@ -254,6 +255,7 @@ function parseWaitForObjectProperty(parser, objName) {
kind: 'json',
keyTypes: {
string: [],
'object-path': [],
},
allowAllValues: true,
valueTypes: {
Expand Down Expand Up @@ -314,12 +316,14 @@ function parseWaitForObjectProperty(parser, objName) {
const undefProps = [];
const values = [];
for (const [key, value] of json) {
const k_s = value.key.kind === 'object-path' ? key : `["${key}"]`;
if (value.kind !== 'ident') {
values.push(`"${key}":"${value.parser.getStringValue()}"`);
values.push(`[${k_s},"${value.parser.getStringValue()}"]`);
} else {
undefProps.push(`"${key}"`);
undefProps.push(k_s);
}
}
// No element to check, we can return empty instructions.
if (values.length === 0 && undefProps.length === 0) {
return {
'instructions': [],
Expand All @@ -345,22 +349,32 @@ function parseWaitForObjectProperty(parser, objName) {
varName,
`\
${varName} = await page.evaluate(() => {
${indentString(generateCheckObjectPaths(), 1)}
const errors = [];
const ${varDict} = {${values.join(',')}};
const ${varDict} = [${values.join(',')}];
const undefProps = [${undefProps.join(',')}];
for (const prop of undefProps) {
if (${objName}[prop] !== undefined && ${objName}[prop] !== null) {
errors.push("Expected property \`" + prop + "\` to not exist, found: \
\`" + ${objName}[prop] + "\`");
continue;
}
checkObjectPaths(${objName}, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => \`"\${p}"\`).join('.');
errors.push("Expected property \`" + p + "\` to not exist, found: \`" + val + "\`");
}
}, _notFound => {},
);
}
for (const [${varKey}, ${varValue}] of Object.entries(${varDict})) {
if (${objName}[${varKey}] === undefined) {
errors.push("${objName} doesn't have a property named \`" + ${varKey} + "\`");
}
const ${varName} = String(${objName}[${varKey}]);
${indentString(checks.join('\n'), 2)}
for (const [${varKey}, ${varValue}] of ${varDict}) {
checkObjectPaths(${objName}, ${varKey}, val => {
if (val === undefined) {
const p = ${varKey}.map(p => \`"\${p}"\`).join('.');
errors.push("${objName} doesn't have a property \`" + p + "\`");
return;
}
const ${varName} = String(val);
${indentString(checks.join('\n'), 3)}
}, _notFound => {
const p = ${varKey}.map(p => \`"\${p}"\`).join('.');
errors.push("${objName} doesn't have a property \`" + p + "\`");
});
}
return errors;
});
Expand Down Expand Up @@ -686,8 +700,7 @@ ${indentString(incr, 1)}

// Possible inputs:
//
// * ("CSS selector", {"property name": "expected property value"})
// * ("XPath", {"property name": "expected property value"})
// * ("selector", {"property name": "expected property value"})
function parseWaitForProperty(parser) {
const identifiers = ['ALL', 'CONTAINS', 'ENDS_WITH', 'NEAR', 'STARTS_WITH'];
const ret = validator(parser, {
Expand All @@ -700,6 +713,7 @@ function parseWaitForProperty(parser) {
kind: 'json',
keyTypes: {
string: [],
'object-path': [],
},
valueTypes: {
string: {},
Expand Down Expand Up @@ -741,8 +755,7 @@ function parseWaitForProperty(parser) {
const warnings = [];

if (tuple.length > 2) {
const ret = fillEnabledChecksV2(
tuple[2], enabledChecks, warnings, 'third');
const ret = fillEnabledChecksV2(tuple[2], enabledChecks, warnings, 'third');
if (ret !== null) {
return ret;
}
Expand All @@ -754,7 +767,7 @@ function parseWaitForProperty(parser) {
const varValue = varName + 'Value';

const { checks, hasSpecialChecks } = makeExtendedChecks(
enabledChecks, false, 'nonMatchingProps', 'property', 'prop', varKey, varValue);
enabledChecks, false, 'nonMatchingProps', 'property', 'val', varKey, varValue);

let checker;
if (!enabledChecks.has('ALL')) {
Expand All @@ -781,10 +794,11 @@ the check will be performed on the element itself`);
const tests = [];
const nullProps = [];
for (const [key, value] of json) {
const k_s = value.key.kind === 'object-path' ? key : `["${key}"]`;
if (value.kind !== 'ident') {
tests.push(`"${key}":"${value.value}"`);
tests.push(`[${k_s},"${value.parser.getStringValue()}"]`);
} else {
nullProps.push(`"${key}"`);
nullProps.push(k_s);
}
}

Expand All @@ -803,24 +817,34 @@ throw new Error("The following properties still don't match: [" + props + "]");`
const instructions = `\
async function checkPropForElem(elem) {
return await elem.evaluate(e => {
${indentString(generateCheckObjectPaths(), 2)}
const nonMatchingProps = [];
const ${varDict} = {${tests.join(',')}};
const ${varDict} = [${tests.join(',')}];
const nullProps = [${nullProps.join(',')}];
for (const ${varKey} of nullProps) {
if (e[${varKey}] !== undefined && e[${varKey}] !== null) {
const prop = e[${varKey}];
nonMatchingProps.push("Expected property \`" + ${varKey} + "\` to not exist, \
found: \`" + prop + "\`");
continue;
}
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 [${varKey}, ${varValue}] of Object.entries(${varDict})) {
if (e[${varKey}] === undefined || e[${varKey}] === null) {
nonMatchingProps.push("Property named \`" + ${varKey} + "\` doesn't exist");
continue;
}
const prop = e[${varKey}];
${indentString(checks.join('\n'), 3)}
for (const [${varKey}, ${varValue}] of ${varDict}) {
checkObjectPaths(e, ${varKey}, val => {
if (val === undefined) {
const p = ${varKey}.map(p => \`"\${p}"\`).join('.');
nonMatchingProps.push("Property \`" + p + "\` doesn't exist");
return;
}
${indentString(checks.join('\n'), 4)}
}, _notFound => {
const p = ${varKey}.map(p => \`"\${p}"\`).join('.');
nonMatchingProps.push("Property \`" + p + "\` doesn't exist");
});
}
return nonMatchingProps;
});
Expand Down
49 changes: 36 additions & 13 deletions tests/api-output/parseWaitForDocumentProperty/basic-3.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,46 @@ let allTime = 0;
let property = null;
while (true) {
property = await page.evaluate(() => {
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 errors = [];
const propertyDict = {\"a\":\"b\"};
const propertyDict = [[[\"a\"],\"b\"]];
const undefProps = [];
for (const prop of undefProps) {
if (document[prop] !== undefined && document[prop] !== null) {
errors.push(\"Expected property `\" + prop + \"` to not exist, found: `\" + document[prop] + \"`\");
continue;
}
checkObjectPaths(document, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push(\"Expected property `\" + p + \"` to not exist, found: `\" + val + \"`\");
}
}, _notFound => {},
);
}
for (const [propertyKey, propertyValue] of Object.entries(propertyDict)) {
if (document[propertyKey] === undefined) {
errors.push(\"document doesn't have a property named `\" + propertyKey + \"`\");
}
const property = String(document[propertyKey]);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
for (const [propertyKey, propertyValue] of propertyDict) {
checkObjectPaths(document, propertyKey, val => {
if (val === undefined) {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
return;
}
const property = String(val);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
}, _notFound => {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
});
}
return errors;
});
Expand Down
49 changes: 36 additions & 13 deletions tests/api-output/parseWaitForDocumentProperty/basic-4.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,46 @@ let allTime = 0;
let property = null;
while (true) {
property = await page.evaluate(() => {
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 errors = [];
const propertyDict = {\"a\":\"b\"};
const propertyDict = [[[\"a\"],\"b\"]];
const undefProps = [];
for (const prop of undefProps) {
if (document[prop] !== undefined && document[prop] !== null) {
errors.push(\"Expected property `\" + prop + \"` to not exist, found: `\" + document[prop] + \"`\");
continue;
}
checkObjectPaths(document, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push(\"Expected property `\" + p + \"` to not exist, found: `\" + val + \"`\");
}
}, _notFound => {},
);
}
for (const [propertyKey, propertyValue] of Object.entries(propertyDict)) {
if (document[propertyKey] === undefined) {
errors.push(\"document doesn't have a property named `\" + propertyKey + \"`\");
}
const property = String(document[propertyKey]);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
for (const [propertyKey, propertyValue] of propertyDict) {
checkObjectPaths(document, propertyKey, val => {
if (val === undefined) {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
return;
}
const property = String(val);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
}, _notFound => {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
});
}
return errors;
});
Expand Down
49 changes: 36 additions & 13 deletions tests/api-output/parseWaitForDocumentProperty/basic-6.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,46 @@ let allTime = 0;
let property = null;
while (true) {
property = await page.evaluate(() => {
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 errors = [];
const propertyDict = {\"\\\"a\":\"\\'b\"};
const propertyDict = [[[\"\\\"a\"],\"\\'b\"]];
const undefProps = [];
for (const prop of undefProps) {
if (document[prop] !== undefined && document[prop] !== null) {
errors.push(\"Expected property `\" + prop + \"` to not exist, found: `\" + document[prop] + \"`\");
continue;
}
checkObjectPaths(document, prop, val => {
if (val !== undefined && val !== null) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push(\"Expected property `\" + p + \"` to not exist, found: `\" + val + \"`\");
}
}, _notFound => {},
);
}
for (const [propertyKey, propertyValue] of Object.entries(propertyDict)) {
if (document[propertyKey] === undefined) {
errors.push(\"document doesn't have a property named `\" + propertyKey + \"`\");
}
const property = String(document[propertyKey]);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
for (const [propertyKey, propertyValue] of propertyDict) {
checkObjectPaths(document, propertyKey, val => {
if (val === undefined) {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
return;
}
const property = String(val);
if (property !== propertyValue) {
errors.push(\"expected `\" + propertyValue + \"` for document property `\" + propertyKey + \"`, found `\" + property + \"`\");
}
}, _notFound => {
const p = propertyKey.map(p => `\"${p}\"`).join('.');
errors.push(\"document doesn't have a property `\" + p + \"`\");
});
}
return errors;
});
Expand Down
Loading

0 comments on commit 0d1996e

Please sign in to comment.