Skip to content

Commit

Permalink
Merge pull request #594 from GuillaumeGomez/store-property
Browse files Browse the repository at this point in the history
Add support for object-path in `store-property` command
  • Loading branch information
GuillaumeGomez authored Mar 31, 2024
2 parents a808337 + 9be3386 commit 22c955d
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 100 deletions.
7 changes: 5 additions & 2 deletions goml-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -1732,10 +1732,13 @@ For more information about variables, read the [variables section](#variables).
**store-property** command stores an element's property into a variable. Examples:

```
store-property: ("#button", {variable_name: "clientHeight"})
store-property: ("#button", {"clientHeight": variable_name})
assert-variable: (variable_name, 152)
// You can set multiple variables at once.
store-property: ("#button", {variable_name: "scrollHeight", another_var: "clientHeight"})
store-property: ("#button", {"scrollHeight": variable_name, "clientHeight": another_var})
// You can use object-path as well:
store-property: ("#button", {"firstChildElement"."clientHeight": variable_name})
```

For more information about variables, read the [variables section](#variables).
Expand Down
55 changes: 27 additions & 28 deletions src/commands/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
// All `compare*` commands.

const { getAndSetElements, indentString, getSizes } = require('./utils.js');
const {
getAndSetElements,
indentString,
getSizes,
generateCheckObjectPaths,
} = require('./utils.js');
const { validator } = require('../validator.js');
// Not the same `utils.js`!
const { RESERVED_VARIABLE_NAME, hasError } = require('../utils.js');
Expand Down Expand Up @@ -168,7 +173,7 @@ ${code.join('\n')}`;

// Possible inputs:
//
// * ("CSS selector" | "XPath", {"property": ident})
// * ("selector", {"property": ident})
function parseStoreProperty(parser) {
const ret = validator(parser, {
kind: 'tuple',
Expand All @@ -180,6 +185,7 @@ function parseStoreProperty(parser) {
kind: 'json',
keyTypes: {
string: [],
'object-path': [],
},
valueTypes: {
ident: {
Expand All @@ -198,18 +204,10 @@ function parseStoreProperty(parser) {
const selector = tuple[0].value;
const json = tuple[1].value.entries;

const code = [];
const getter = [];
for (const [key, value] of json) {
code.push(`arg.setVariable("${value.value}", data["${key}"]);`);
getter.push(`"${key}"`);
}

if (code.length === 0) {
return {
'instructions': [],
'wait': false,
};
const k_s = value.key.kind === 'object-path' ? key : `["${key}"]`;
getter.push(`[${k_s}, "${value.value}"]`);
}

const warnings = [];
Expand All @@ -219,36 +217,37 @@ function parseStoreProperty(parser) {
it will be performed on the element itself`);
}

if (code.length === 0) {
return {
'instructions': [],
'wait': false,
'warnings': warnings,
};
}

const varName = 'elem';
const instructions = `\
${getAndSetElements(selector, varName, false)}
const jsHandle = await ${varName}.evaluateHandle(e => {
${indentString(generateCheckObjectPaths(), 1)}
const props = [${getter.join(',')}];
const ret = Object.create(null);
const ret = [];
const errors = [];
for (const prop of props) {
if (e[prop] === undefined) {
errors.push('"No property named \`' + prop + '\`"');
} else {
ret[prop] = e[prop];
}
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => \`"\${p}"\`).join('.');
errors.push('"No property named \`' + p + '\`"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => \`"\${p}"\`).join('.');
errors.push('"No property named \`' + p + '\`"');
});
}
if (errors.length !== 0) {
throw "The following errors happened: [" + errors.join(", ") + "]";
}
return ret;
});
const data = await jsHandle.jsonValue();
${code.join('\n')}`;
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}`;

return {
'instructions': [instructions],
Expand Down
39 changes: 30 additions & 9 deletions tests/api-output/parseStoreProperty/basic-1.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,45 @@ instructions = [
"""let elem = await page.$(\"a\");
if (elem === null) { throw '\"a\" not found'; }
const jsHandle = await elem.evaluateHandle(e => {
const props = [\"ye\"];
const ret = Object.create(null);
const errors = [];
function checkObjectPaths(object, path, callback, notFoundCallback) {
const found = [];
for (const prop of props) {
if (e[prop] === undefined) {
errors.push('\"No property named `' + prop + '`\"');
} else {
ret[prop] = e[prop];
for (const subPath of path) {
found.push(subPath);
if (object === undefined || object === null) {
notFoundCallback(found);
return;
}
object = object[subPath];
}
callback(object);
}
const props = [[[\"ye\"], \"a\"]];
const ret = [];
const errors = [];
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
});
}
if (errors.length !== 0) {
throw \"The following errors happened: [\" + errors.join(\", \") + \"]\";
}
return ret;
});
const data = await jsHandle.jsonValue();
arg.setVariable(\"a\", data[\"ye\"]);""",
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}""",
]
wait = false
warnings = [
Expand Down
39 changes: 30 additions & 9 deletions tests/api-output/parseStoreProperty/basic-2.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,45 @@ instructions = [
"""let elem = await page.$(\"a\");
if (elem === null) { throw '\"a\" not found'; }
const jsHandle = await elem.evaluateHandle(e => {
const props = [\"\\\"ye\"];
const ret = Object.create(null);
const errors = [];
function checkObjectPaths(object, path, callback, notFoundCallback) {
const found = [];
for (const prop of props) {
if (e[prop] === undefined) {
errors.push('\"No property named `' + prop + '`\"');
} else {
ret[prop] = e[prop];
for (const subPath of path) {
found.push(subPath);
if (object === undefined || object === null) {
notFoundCallback(found);
return;
}
object = object[subPath];
}
callback(object);
}
const props = [[[\"\\\"ye\"], \"a\"]];
const ret = [];
const errors = [];
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
});
}
if (errors.length !== 0) {
throw \"The following errors happened: [\" + errors.join(\", \") + \"]\";
}
return ret;
});
const data = await jsHandle.jsonValue();
arg.setVariable(\"a\", data[\"\\\"ye\"]);""",
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}""",
]
wait = false
warnings = [
Expand Down
40 changes: 30 additions & 10 deletions tests/api-output/parseStoreProperty/basic-3.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,45 @@ instructions = [
"""let elem = await page.$(\"a\");
if (elem === null) { throw '\"a\" not found'; }
const jsHandle = await elem.evaluateHandle(e => {
const props = [\"yaya\",\"yiyi\"];
const ret = Object.create(null);
const errors = [];
function checkObjectPaths(object, path, callback, notFoundCallback) {
const found = [];
for (const prop of props) {
if (e[prop] === undefined) {
errors.push('\"No property named `' + prop + '`\"');
} else {
ret[prop] = e[prop];
for (const subPath of path) {
found.push(subPath);
if (object === undefined || object === null) {
notFoundCallback(found);
return;
}
object = object[subPath];
}
callback(object);
}
const props = [[[\"yaya\"], \"a\"],[[\"yiyi\"], \"b\"]];
const ret = [];
const errors = [];
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
});
}
if (errors.length !== 0) {
throw \"The following errors happened: [\" + errors.join(\", \") + \"]\";
}
return ret;
});
const data = await jsHandle.jsonValue();
arg.setVariable(\"a\", data[\"yaya\"]);
arg.setVariable(\"b\", data[\"yiyi\"]);""",
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}""",
]
wait = false
warnings = [
Expand Down
47 changes: 47 additions & 0 deletions tests/api-output/parseStoreProperty/object-path-1.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
instructions = [
"""let elem = await page.$(\"a\");
if (elem === null) { throw '\"a\" not found'; }
const jsHandle = await elem.evaluateHandle(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 props = [[[\"ye\",\"ya\"], \"a\"]];
const ret = [];
const errors = [];
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
});
}
if (errors.length !== 0) {
throw \"The following errors happened: [\" + errors.join(\", \") + \"]\";
}
return ret;
});
const data = await jsHandle.jsonValue();
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}""",
]
wait = false
warnings = [
]
47 changes: 47 additions & 0 deletions tests/api-output/parseStoreProperty/object-path-2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
instructions = [
"""let elem = await page.$(\"a\");
if (elem === null) { throw '\"a\" not found'; }
const jsHandle = await elem.evaluateHandle(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 props = [[[\"ye\",\"ya\"], \"a\"],[[\"yo\"], \"b\"]];
const ret = [];
const errors = [];
for (const [prop, varName] of props) {
checkObjectPaths(e, prop, found => {
if (found === undefined) {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
return;
}
ret.push([found, varName]);
}, _ => {
const p = prop.map(p => `\"${p}\"`).join('.');
errors.push('\"No property named `' + p + '`\"');
});
}
if (errors.length !== 0) {
throw \"The following errors happened: [\" + errors.join(\", \") + \"]\";
}
return ret;
});
const data = await jsHandle.jsonValue();
for (const [found, varName] of data) {
arg.setVariable(varName, found);
}""",
]
wait = false
warnings = [
]
Loading

0 comments on commit 22c955d

Please sign in to comment.