Skip to content

Commit 6e68c88

Browse files
Merge pull request #629 from GuillaumeGomez/within-iframe
Add `within-iframe` command
2 parents f014d02 + 4045fc6 commit 6e68c88

File tree

300 files changed

+691
-552
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

300 files changed

+691
-552
lines changed

Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ COPY ./package*.json ./
3232

3333
RUN npm install
3434

35-
# never use the "--no-sandbox" outside of a container!
36-
ENTRYPOINT ["node", "./browser-ui-test-src/index.js", "--no-sandbox"]
35+
ENTRYPOINT ["node", "./browser-ui-test-src/index.js"]
3736
# to be able to pass arguments to index.js
3837
CMD []

goml-script.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ Here's the command list:
246246
* [`wait-for-size-false`](#wait-for-size-false)
247247
* [`wait-for-window-property`](#wait-for-window-property)
248248
* [`wait-for-window-property-false`](#wait-for-window-property-false)
249+
* [`within-iframe`](#within-iframe)
249250
* [`write`](#write)
250251
* [`write-into`](#write-into)
251252

@@ -2400,6 +2401,17 @@ wait-for-window-property-false: ({"key": "value", "key2": "value2"}, [ENDS_WITH,
24002401

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

2404+
#### within-iframe
2405+
2406+
**within-iframe** commands moves the context inside the provided `<iframe>` selector. Inside it, all selectors are relative to the `iframe`, same if you use `go-to`. Examples:
2407+
2408+
```
2409+
within-iframe: ("#iframe", block {
2410+
// We try to click on an element inside the `#iframe` element.
2411+
click: "#element"
2412+
})
2413+
```
2414+
24032415
#### write
24042416

24052417
**write** command sends keyboard inputs on the currently focused element. Examples:

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"browser-ui-test": "src/index.js"
1616
},
1717
"scripts": {
18-
"test": "node src/index.js --test-folder tests/full-check/ --failure-folder failures --variable DOC_PATH tests/html_files --no-sandbox",
18+
"test": "node src/index.js --test-folder tests/full-check/ --failure-folder failures --variable DOC_PATH tests/html_files",
1919
"all-test": "node tools/all.js",
2020
"api-test": "node tools/api.js",
2121
"ui-test": "node tools/ui.js",

src/commands.js

Lines changed: 19 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ const { AstLoader } = require('./ast.js');
22
const process = require('process');
33
const commands = require('./commands/all.js');
44
const consts = require('./consts.js');
5-
const path = require('path');
65
const { stripCommonPathsPrefix } = require('./utils.js');
76

87
const ORDERS = {
@@ -117,6 +116,7 @@ const ORDERS = {
117116
'wait-for-text-false': commands.parseWaitForTextFalse,
118117
'wait-for-window-property': commands.parseWaitForWindowProperty,
119118
'wait-for-window-property-false': commands.parseWaitForWindowPropertyFalse,
119+
'within-iframe': commands.parseWithinIFrame,
120120
'write': commands.parseWrite,
121121
'write-into': commands.parseWriteInto,
122122
};
@@ -164,6 +164,7 @@ const FATAL_ERROR_COMMANDS = [
164164
'wait-for-property-false',
165165
'wait-for-text',
166166
'wait-for-text-false',
167+
'within-iframe',
167168
'write',
168169
'write-into',
169170
];
@@ -262,64 +263,6 @@ class ParserWithContext {
262263
}
263264
}
264265

265-
setup_user_function_call(ast) {
266-
const ret = commands.parseCallFunction(this);
267-
if (ret.error !== undefined) {
268-
ret.line = this.get_current_command_line();
269-
if (ast.length !== 0) {
270-
ret.error += ` (from command \`${ast[0].getErrorText()}\`)`;
271-
}
272-
return ret;
273-
}
274-
const args = Object.create(null);
275-
const func = this.definedFunctions[ret['function']];
276-
for (const arg_name of func['arguments']) {
277-
const index = ret['args'].findIndex(arg => arg.key.value === arg_name);
278-
if (index === -1) {
279-
return {
280-
'error': `Missing argument "${arg_name}"`,
281-
'line': this.get_current_command_line(),
282-
'fatal_error': true,
283-
};
284-
}
285-
args[arg_name] = ret['args'][index].value;
286-
}
287-
const context = this.get_current_context();
288-
this.pushNewContext({
289-
'ast': context.ast,
290-
'commands': func.commands,
291-
'currentCommand': 0,
292-
'functionArgs': Object.assign({}, context.functionArgs, args),
293-
'filePath': func.filePath,
294-
});
295-
// We disable the `increasePos` in the context to prevent it to be done twice.
296-
return this.get_next_command(false);
297-
}
298-
299-
setup_include() {
300-
const ret = commands.parseInclude(this);
301-
if (ret.error !== undefined) {
302-
ret.line = this.get_current_command_line();
303-
if (ast.length !== 0) {
304-
ret.error += ` (from command \`${ast[0].getErrorText()}\`)`;
305-
}
306-
return ret;
307-
}
308-
const dirPath = path.dirname(this.get_current_context().ast.absolutePath);
309-
const ast = new AstLoader(ret.path, dirPath);
310-
if (ast.hasErrors()) {
311-
return {'errors': ast.errors};
312-
}
313-
this.pushNewContext({
314-
'ast': ast,
315-
'commands': ast.commands,
316-
'currentCommand': 0,
317-
'functionArgs': Object.create(null),
318-
});
319-
// We disable the `increasePos` in the context to prevent it to be done twice.
320-
return this.get_next_command(false);
321-
}
322-
323266
getCurrentFile() {
324267
const context = this.get_current_context();
325268
if (context === null) {
@@ -330,19 +273,12 @@ class ParserWithContext {
330273
return context.ast.absolutePath;
331274
}
332275

333-
run_order(order, ast) {
276+
run_order(pages, order, ast) {
334277
// This is needed because for now, all commands get access to the ast
335278
// through `ParserWithContext`.
336279
this.elems = ast;
337280

338-
if (order === 'call-function') {
339-
// We need to special-case `call-function` since it needs to access variables of this
340-
// class.
341-
return this.setup_user_function_call(ast);
342-
} else if (order === 'include') {
343-
// We need to special-case `include` since it needs to parse a new file when called.
344-
return this.setup_include();
345-
} else if (!Object.prototype.hasOwnProperty.call(ORDERS, order)) {
281+
if (!Object.prototype.hasOwnProperty.call(ORDERS, order)) {
346282
return {'error': `Unknown command "${order}"`, 'line': this.get_current_command_line()};
347283
}
348284
if (this.firstGotoParsed === false) {
@@ -367,10 +303,15 @@ class ParserWithContext {
367303
if (res.error !== undefined) {
368304
res.line = this.get_current_command_line();
369305
if (this.elems.length !== 0) {
370-
res.error += ` (from command \`${this.elems[0].getErrorText()}\`)`;
306+
res.error += ` (from command \`${order}: ${this.elems[0].getErrorText()}\`)`;
371307
}
372308
return res;
373309
}
310+
if (res.skipInstructions) {
311+
// We disable the `increasePos` in the context to prevent it to be done twice.
312+
return this.get_next_command(pages, false);
313+
}
314+
374315
return {
375316
'fatal_error': FATAL_ERROR_COMMANDS.indexOf(order) !== -1,
376317
'wait': res['wait'],
@@ -380,13 +321,18 @@ class ParserWithContext {
380321
'instructions': res['instructions'],
381322
'infos': res['infos'],
382323
'warnings': res['warnings'],
324+
'callback': res['callback'],
325+
'noPosIncrease': res['noPosIncrease'],
383326
};
384327
}
385328

386-
get_next_command(increasePos = true) {
329+
get_next_command(pages, increasePos = true) {
387330
let context = this.get_current_context();
388331
while (context !== null && context.currentCommand >= context.commands.length) {
389-
this.contexts.pop();
332+
const prevContext = this.contexts.pop();
333+
if (prevContext.dropCallback !== undefined) {
334+
prevContext.dropCallback(pages);
335+
}
390336
context = this.get_current_context();
391337
if (context !== null) {
392338
context.currentCommand += 1;
@@ -404,8 +350,8 @@ class ParserWithContext {
404350
'errors': inferred.errors,
405351
};
406352
}
407-
const ret = this.run_order(command.commandName, inferred.ast);
408-
if (increasePos) {
353+
const ret = this.run_order(pages, command.commandName, inferred.ast);
354+
if (increasePos && !ret['noPosIncrease']) {
409355
this.increase_context_pos();
410356
}
411357
return ret;

src/commands/all.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ module.exports = {
125125
'parseWaitForTextFalse': wait.parseWaitForTextFalse,
126126
'parseWaitForWindowProperty': wait.parseWaitForWindowProperty,
127127
'parseWaitForWindowPropertyFalse': wait.parseWaitForWindowPropertyFalse,
128+
'parseWithinIFrame': context_setters.parseWithinIFrame,
128129
'parseWrite': input.parseWrite,
129130
'parseWriteInto': input.parseWriteInto,
130131
};

src/commands/context_setters.js

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const consts = require('../consts.js');
44
const { validator } = require('../validator.js');
55
const { hasError } = require('../utils.js');
6+
const { getAndSetElements } = require('./utils.js');
67

78
// Possible inputs:
89
//
@@ -162,7 +163,7 @@ function parseSetTimeout(parser) {
162163
}
163164
return {
164165
'instructions': [
165-
`page.setDefaultTimeout(${ret.value.value})`,
166+
`pages[0].setDefaultTimeout(${ret.value.value})`,
166167
],
167168
'wait': false,
168169
'warnings': warnings.length > 0 ? warnings : undefined,
@@ -213,6 +214,67 @@ if (oldValue !== true) {
213214
};
214215
}
215216

217+
// Possible inputs:
218+
//
219+
// * (selector, block)
220+
function parseWithinIFrame(parser) {
221+
const ret = validator(parser,
222+
{
223+
kind: 'tuple',
224+
elements: [
225+
{
226+
kind: 'selector',
227+
},
228+
{
229+
kind: 'block',
230+
},
231+
],
232+
},
233+
);
234+
if (hasError(ret)) {
235+
return ret;
236+
}
237+
const tuple = ret.value.entries;
238+
const selector = tuple[0].value;
239+
const isPseudo = !selector.isXPath && selector.pseudo !== null;
240+
241+
if (isPseudo) {
242+
return {
243+
'error': 'Pseudo elements cannot be <iframe>',
244+
};
245+
}
246+
247+
const code = `\
248+
${getAndSetElements(selector, 'iframe', false)}
249+
await iframe.evaluate(el => {
250+
if (el.tagName !== "IFRAME") {
251+
throw "selector \`${selector.value}\` is not an \`<iframe>\` but a \`<" + \
252+
el.tagName.toLowerCase() + ">\`";
253+
}
254+
});
255+
pages.push(iframe);`;
256+
257+
return {
258+
'instructions': [code],
259+
'callback': () => {
260+
const context = parser.get_current_context();
261+
parser.pushNewContext({
262+
'ast': context.ast,
263+
'commands': tuple[1].value.value,
264+
'currentCommand': 0,
265+
'functionArgs': Object.create(null),
266+
'dropCallback': pages => {
267+
if (pages.length < 2) {
268+
throw new Error('`pages` is empty whereas it should never happen!');
269+
}
270+
pages.pop();
271+
},
272+
});
273+
},
274+
'noPosIncrease': true,
275+
};
276+
}
277+
216278
module.exports = {
217279
'parseDebug': parseDebug,
218280
'parseExpectFailure': parseExpectFailure,
@@ -223,4 +285,5 @@ module.exports = {
223285
'parseScreenshotOnFailure': parseScreenshotOnFailure,
224286
'parseShowText': parseShowText,
225287
'parseSetTimeout': parseSetTimeout,
288+
'parseWithinIFrame': parseWithinIFrame,
226289
};

src/commands/emulation.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ function parseSetWindowSize(parser) {
3434
return {
3535
'instructions': [
3636
`\
37-
const viewport = page.viewport();
37+
const viewport = pages[0].viewport();
3838
const newViewport = {
3939
...viewport,
4040
width: ${width},
4141
height: ${height},
4242
};
43-
await page.setViewport(newViewport);`,
43+
await pages[0].setViewport(newViewport);`,
4444
],
4545
};
4646
}
@@ -64,12 +64,12 @@ function parseSetDevicePixelRatio(parser) {
6464
return {
6565
'instructions': [
6666
`\
67-
const viewport = page.viewport();
67+
const viewport = pages[0].viewport();
6868
const newViewport = {
6969
...viewport,
7070
deviceScaleFactor: ${ret.value.getRaw()},
7171
};
72-
await page.setViewport(newViewport);`,
72+
await pages[0].setViewport(newViewport);`,
7373
],
7474
};
7575
}
@@ -97,7 +97,7 @@ if (arg.puppeteer.KnownDevices["${device}"] === undefined) {
9797
https://github.com/GoogleChrome/puppeteer/blob/master/lib/DeviceDescriptors.js or \
9898
you can use \`--show-devices\` option';
9999
} else {
100-
await page.emulate(arg.puppeteer.KnownDevices["${device}"]);
100+
await pages[0].emulate(arg.puppeteer.KnownDevices["${device}"]);
101101
}`,
102102
],
103103
};
@@ -128,7 +128,7 @@ function parseGeolocation(parser) {
128128
const tuple = ret.value.entries;
129129
return {
130130
'instructions': [
131-
`await page.setGeolocation(${tuple[0].value.getRaw()}, ${tuple[1].value.getRaw()});`,
131+
`await pages[0].setGeolocation(${tuple[0].value.getRaw()},${tuple[1].value.getRaw()});`,
132132
],
133133
};
134134
}
@@ -178,7 +178,7 @@ function parseJavascript(parser) {
178178

179179
return {
180180
'instructions': [
181-
`await page.setJavaScriptEnabled(${ret.value.getRaw()});`,
181+
`await pages[0].setJavaScriptEnabled(${ret.value.getRaw()});`,
182182
],
183183
};
184184
}
@@ -200,7 +200,7 @@ function parseSetFontSize(parser) {
200200

201201
return {
202202
'instructions': [`\
203-
const client = await page.target().createCDPSession();
203+
const client = await pages[0].target().createCDPSession();
204204
await client.send("Page.enable");
205205
await client.send("Page.setFontSizes", {
206206
fontSizes: {

0 commit comments

Comments
 (0)