`);
+ const tc = await page.innerHTML('innerHTML=div');
+ expect(tc).toBe('Helloworld');
+ expect(await page.evaluate(() => document.querySelector('div').innerHTML)).toBe('modified');
+});
+it('getAttribute should be atomic', async ({ playwright, page }) => {
+ const createDummySelector = () => ({
+ create(root, target) { },
+ query(root, selector) {
+ const result = root.querySelector(selector);
+ if (result)
+ Promise.resolve().then(() => result.setAttribute('foo', 'modified'));
+ return result;
+ },
+ queryAll(root, selector) {
+ const result = Array.from(root.querySelectorAll(selector));
+ for (const e of result)
+ Promise.resolve().then(() => result.setAttribute('foo', 'modified'));
+ return result;
+ }
+ });
+ await utils.registerEngine(playwright, 'getAttribute', createDummySelector);
+ await page.setContent(``);
+ const tc = await page.getAttribute('getAttribute=div', 'foo');
+ expect(tc).toBe('hello');
+ expect(await page.evaluate(() => document.querySelector('div').getAttribute('foo'))).toBe('modified');
+});
diff --git a/test/elementhandle-eval-on-selector.spec.js b/test/elementhandle-eval-on-selector.spec.js
new file mode 100644
index 0000000000..283bc05ba1
--- /dev/null
+++ b/test/elementhandle-eval-on-selector.spec.js
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('should work', async({page, server}) => {
+ await page.setContent('
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const content = await elementHandle.$$eval('.a', nodes => nodes.map(n => n.innerText));
+ expect(content).toEqual(['a1-child-div', 'a2-child-div']);
+});
+
+it('should not throw in case of missing selector for all', async({page, server}) => {
+ const htmlContent = '
not-a-child-div
';
+ await page.setContent(htmlContent);
+ const elementHandle = await page.$('#myId');
+ const nodesLength = await elementHandle.$$eval('.a', nodes => nodes.length);
+ expect(nodesLength).toBe(0);
+});
diff --git a/test/elementhandle-misc.spec.js b/test/elementhandle-misc.spec.js
new file mode 100644
index 0000000000..e6486a82a8
--- /dev/null
+++ b/test/elementhandle-misc.spec.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should hover', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ const button = await page.$('#button-6');
+ await button.hover();
+ expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
+});
+
+it('should hover when Node is removed', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/scrollable.html');
+ await page.evaluate(() => delete window['Node']);
+ const button = await page.$('#button-6');
+ await button.hover();
+ expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6');
+});
+
+it('should fill input', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const handle = await page.$('input');
+ await handle.fill('some value');
+ expect(await page.evaluate(() => result)).toBe('some value');
+});
+
+it('should fill input when Node is removed', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ await page.evaluate(() => delete window['Node']);
+ const handle = await page.$('input');
+ await handle.fill('some value');
+ expect(await page.evaluate(() => result)).toBe('some value');
+});
+
+it('should check the box', async ({ page }) => {
+ await page.setContent(``);
+ const input = await page.$('input');
+ await input.check();
+ expect(await page.evaluate(() => checkbox.checked)).toBe(true);
+});
+
+it('should uncheck the box', async ({ page }) => {
+ await page.setContent(``);
+ const input = await page.$('input');
+ await input.uncheck();
+ expect(await page.evaluate(() => checkbox.checked)).toBe(false);
+});
+
+it('should select single option', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/select.html');
+ const select = await page.$('select');
+ await select.selectOption('blue');
+ expect(await page.evaluate(() => result.onInput)).toEqual(['blue']);
+ expect(await page.evaluate(() => result.onChange)).toEqual(['blue']);
+});
+
+it('should focus a button', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/button.html');
+ const button = await page.$('button');
+ expect(await button.evaluate(button => document.activeElement === button)).toBe(false);
+ await button.focus();
+ expect(await button.evaluate(button => document.activeElement === button)).toBe(true);
+});
diff --git a/test/elementhandle-owner-frame.spec.js b/test/elementhandle-owner-frame.spec.js
new file mode 100644
index 0000000000..7e6e850965
--- /dev/null
+++ b/test/elementhandle-owner-frame.spec.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should work', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ const elementHandle = await frame.evaluateHandle(() => document.body);
+ expect(await elementHandle.ownerFrame()).toBe(frame);
+});
+it('should work for cross-process iframes', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
+ const frame = page.frames()[1];
+ const elementHandle = await frame.evaluateHandle(() => document.body);
+ expect(await elementHandle.ownerFrame()).toBe(frame);
+});
+it('should work for document', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ const elementHandle = await frame.evaluateHandle(() => document);
+ expect(await elementHandle.ownerFrame()).toBe(frame);
+});
+it('should work for iframe elements', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1'));
+ expect(await elementHandle.ownerFrame()).toBe(frame);
+});
+it('should work for cross-frame evaluations', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1').contentWindow.document.body);
+ expect(await elementHandle.ownerFrame()).toBe(frame.childFrames()[0]);
+});
+it('should work for detached elements', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ const divHandle = await page.evaluateHandle(() => {
+ const div = document.createElement('div');
+ document.body.appendChild(div);
+ return div;
+ });
+ expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
+ await page.evaluate(() => {
+ const div = document.querySelector('div');
+ document.body.removeChild(div);
+ });
+ expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
+});
+it('should work for adopted elements', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ const [popup] = await Promise.all([
+ page.waitForEvent('popup'),
+ page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE),
+ ]);
+ const divHandle = await page.evaluateHandle(() => {
+ const div = document.createElement('div');
+ document.body.appendChild(div);
+ return div;
+ });
+ expect(await divHandle.ownerFrame()).toBe(page.mainFrame());
+ await popup.waitForLoadState('domcontentloaded');
+ await page.evaluate(() => {
+ const div = document.querySelector('div');
+ window.__popup.document.body.appendChild(div);
+ });
+ expect(await divHandle.ownerFrame()).toBe(popup.mainFrame());
+});
diff --git a/test/elementhandle-press.spec.js b/test/elementhandle-press.spec.js
new file mode 100644
index 0000000000..d2d99518e0
--- /dev/null
+++ b/test/elementhandle-press.spec.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should work', async ({ page }) => {
+ await page.setContent(``);
+ await page.press('input', 'h');
+ expect(await page.$eval('input', input => input.value)).toBe('h');
+});
+it('should not select existing value', async ({ page }) => {
+ await page.setContent(``);
+ await page.press('input', 'w');
+ expect(await page.$eval('input', input => input.value)).toBe('whello');
+});
+it('should reset selection when not focused', async ({ page }) => {
+ await page.setContent(`
text
`);
+ await page.$eval('input', input => {
+ input.selectionStart = 2;
+ input.selectionEnd = 4;
+ document.querySelector('div').focus();
+ });
+ await page.press('input', 'w');
+ expect(await page.$eval('input', input => input.value)).toBe('whello');
+});
+it('should not modify selection when focused', async ({ page }) => {
+ await page.setContent(``);
+ await page.$eval('input', input => {
+ input.focus();
+ input.selectionStart = 2;
+ input.selectionEnd = 4;
+ });
+ await page.press('input', 'w');
+ expect(await page.$eval('input', input => input.value)).toBe('hewo');
+});
+it('should work with number input', async ({ page }) => {
+ await page.setContent(``);
+ await page.press('input', '1');
+ expect(await page.$eval('input', input => input.value)).toBe('12');
+});
diff --git a/test/elementhandle-query-selector.spec.js b/test/elementhandle-query-selector.spec.js
new file mode 100644
index 0000000000..f1b19c0b10
--- /dev/null
+++ b/test/elementhandle-query-selector.spec.js
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('should query existing element', async({page, server}) => {
+ await page.goto(server.PREFIX + '/playground.html');
+ await page.setContent('
');
+ const html = await page.$('html');
+ const second = await html.$$(`xpath=/div[contains(@class, 'third')]`);
+ expect(second).toEqual([]);
+});
diff --git a/test/elementhandle-scroll-into-view.spec.js b/test/elementhandle-scroll-into-view.spec.js
new file mode 100644
index 0000000000..5a96830f48
--- /dev/null
+++ b/test/elementhandle-scroll-into-view.spec.js
@@ -0,0 +1,96 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should work', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/offscreenbuttons.html');
+ for (let i = 0; i < 11; ++i) {
+ const button = await page.$('#btn' + i);
+ const before = await button.evaluate(button => {
+ return button.getBoundingClientRect().right - window.innerWidth;
+ });
+ expect(before).toBe(10 * i);
+ await button.scrollIntoViewIfNeeded();
+ const after = await button.evaluate(button => {
+ return button.getBoundingClientRect().right - window.innerWidth;
+ });
+ expect(after <= 0).toBe(true);
+ await page.evaluate(() => window.scrollTo(0, 0));
+ }
+});
+it('should throw for detached element', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ const div = await page.$('div');
+ await div.evaluate(div => div.remove());
+ const error = await div.scrollIntoViewIfNeeded().catch(e => e);
+ expect(error.message).toContain('Element is not attached to the DOM');
+});
+
+async function testWaiting(page, after) {
+ const div = await page.$('div');
+ let done = false;
+ const promise = div.scrollIntoViewIfNeeded().then(() => done = true);
+ await page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
+ expect(done).toBe(false);
+ await div.evaluate(after);
+ await promise;
+ expect(done).toBe(true);
+}
+it('should wait for display:none to become visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ await testWaiting(page, div => div.style.display = 'block');
+});
+it('should wait for display:contents to become visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ await testWaiting(page, div => div.style.display = 'block');
+});
+it('should wait for visibility:hidden to become visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ await testWaiting(page, div => div.style.visibility = 'visible');
+});
+it('should wait for zero-sized element to become visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ await testWaiting(page, div => div.style.height = '100px');
+});
+it('should wait for nested display:none to become visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ await testWaiting(page, div => div.parentElement.style.display = 'block');
+});
+it('should wait for element to stop moving', async ({ page, server }) => {
+ await page.setContent(`
+
+
moving
+ `);
+ await testWaiting(page, div => div.classList.remove('animated'));
+});
+
+it('should timeout waiting for visible', async ({ page, server }) => {
+ await page.setContent('
Hello
');
+ const div = await page.$('div');
+ const error = await div.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(e => e);
+ expect(error.message).toContain('element is not visible');
+});
diff --git a/test/elementhandle-select-text.spec.js b/test/elementhandle-select-text.spec.js
new file mode 100644
index 0000000000..aad8c258fe
--- /dev/null
+++ b/test/elementhandle-select-text.spec.js
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should select textarea', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const textarea = await page.$('textarea');
+ await textarea.evaluate(textarea => textarea.value = 'some value');
+ await textarea.selectText();
+ if (FFOX) {
+ expect(await textarea.evaluate(el => el.selectionStart)).toBe(0);
+ expect(await textarea.evaluate(el => el.selectionEnd)).toBe(10);
+ } else {
+ expect(await page.evaluate(() => window.getSelection().toString())).toBe('some value');
+ }
+});
+it('should select input', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const input = await page.$('input');
+ await input.evaluate(input => input.value = 'some value');
+ await input.selectText();
+ if (FFOX) {
+ expect(await input.evaluate(el => el.selectionStart)).toBe(0);
+ expect(await input.evaluate(el => el.selectionEnd)).toBe(10);
+ } else {
+ expect(await page.evaluate(() => window.getSelection().toString())).toBe('some value');
+ }
+});
+it('should select plain div', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const div = await page.$('div.plain');
+ await div.selectText();
+ expect(await page.evaluate(() => window.getSelection().toString())).toBe('Plain div');
+});
+it('should timeout waiting for invisible element', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const textarea = await page.$('textarea');
+ await textarea.evaluate(e => e.style.display = 'none');
+ const error = await textarea.selectText({ timeout: 3000 }).catch(e => e);
+ expect(error.message).toContain('element is not visible');
+});
+it('should wait for visible', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/input/textarea.html');
+ const textarea = await page.$('textarea');
+ await textarea.evaluate(textarea => textarea.value = 'some value');
+ await textarea.evaluate(e => e.style.display = 'none');
+ let done = false;
+ const promise = textarea.selectText({ timeout: 3000 }).then(() => done = true);
+ await page.evaluate(() => new Promise(f => setTimeout(f, 1000)));
+ expect(done).toBe(false);
+ await textarea.evaluate(e => e.style.display = 'block');
+ await promise;
+});
diff --git a/test/elementhandle-type.spec.js b/test/elementhandle-type.spec.js
new file mode 100644
index 0000000000..a3e3384f1d
--- /dev/null
+++ b/test/elementhandle-type.spec.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const { FFOX, HEADLESS } = testOptions;
+
+it('should work', async ({ page }) => {
+ await page.setContent(``);
+ await page.type('input', 'hello');
+ expect(await page.$eval('input', input => input.value)).toBe('hello');
+});
+it('should not select existing value', async ({ page }) => {
+ await page.setContent(``);
+ await page.type('input', 'world');
+ expect(await page.$eval('input', input => input.value)).toBe('worldhello');
+});
+it('should reset selection when not focused', async ({ page }) => {
+ await page.setContent(`
text
`);
+ await page.$eval('input', input => {
+ input.selectionStart = 2;
+ input.selectionEnd = 4;
+ document.querySelector('div').focus();
+ });
+ await page.type('input', 'world');
+ expect(await page.$eval('input', input => input.value)).toBe('worldhello');
+});
+it('should not modify selection when focused', async ({ page }) => {
+ await page.setContent(``);
+ await page.$eval('input', input => {
+ input.focus();
+ input.selectionStart = 2;
+ input.selectionEnd = 4;
+ });
+ await page.type('input', 'world');
+ expect(await page.$eval('input', input => input.value)).toBe('heworldo');
+});
+it('should work with number input', async ({ page }) => {
+ await page.setContent(``);
+ await page.type('input', '13');
+ expect(await page.$eval('input', input => input.value)).toBe('132');
+});
diff --git a/test/elementhandle.jest.js b/test/elementhandle.jest.js
deleted file mode 100644
index 6db643dd17..0000000000
--- a/test/elementhandle.jest.js
+++ /dev/null
@@ -1,703 +0,0 @@
-/**
- * Copyright 2018 Google Inc. All rights reserved.
- * Modifications copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-const utils = require('./utils');
-const {FFOX, HEADLESS} = testOptions;
-
-describe('ElementHandle.boundingBox', function() {
- it.fail(FFOX && !HEADLESS)('should work', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const elementHandle = await page.$('.box:nth-of-type(13)');
- const box = await elementHandle.boundingBox();
- expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 });
- });
- it('should handle nested frames', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/frames/nested-frames.html');
- const nestedFrame = page.frames().find(frame => frame.name() === 'dos');
- const elementHandle = await nestedFrame.$('div');
- const box = await elementHandle.boundingBox();
- expect(box).toEqual({ x: 24, y: 224, width: 268, height: 18 });
- });
- it('should return null for invisible elements', async({page, server}) => {
- await page.setContent('
');
+ expect(await page.$eval('css=div >> *text="b"', e => e.outerHTML)).toBe('b');
+ expect(await page.$('*')).toBeTruthy();
+});
+
+it('should throw on multiple * captures', async({page, server}) => {
+ const error = await page.$eval('*css=div >> *css=span', e => e.outerHTML).catch(e => e);
+ expect(error.message).toContain('Only one of the selectors can capture using * modifier');
+});
+
+it('should throw on malformed * capture', async({page, server}) => {
+ const error = await page.$eval('*=div', e => e.outerHTML).catch(e => e);
+ expect(error.message).toContain('Unknown engine "" while parsing selector *=div');
+});
+
+it('should work with spaces in css attributes', async({page, server}) => {
+ await page.setContent('');
+ expect(await page.waitForSelector(`[placeholder="Select date"]`)).toBeTruthy();
+ expect(await page.waitForSelector(`[placeholder='Select date']`)).toBeTruthy();
+ expect(await page.waitForSelector(`input[placeholder="Select date"]`)).toBeTruthy();
+ expect(await page.waitForSelector(`input[placeholder='Select date']`)).toBeTruthy();
+ expect(await page.$(`[placeholder="Select date"]`)).toBeTruthy();
+ expect(await page.$(`[placeholder='Select date']`)).toBeTruthy();
+ expect(await page.$(`input[placeholder="Select date"]`)).toBeTruthy();
+ expect(await page.$(`input[placeholder='Select date']`)).toBeTruthy();
+ expect(await page.$eval(`[placeholder="Select date"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`[placeholder='Select date']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`input[placeholder="Select date"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`input[placeholder='Select date']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=[placeholder="Select date"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=[placeholder='Select date']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=input[placeholder="Select date"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=input[placeholder='Select date']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`div >> [placeholder="Select date"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`div >> [placeholder='Select date']`, e => e.outerHTML)).toBe('');
+});
+
+it('should work with quotes in css attributes', async({page, server}) => {
+ await page.setContent('');
+ expect(await page.$(`[placeholder="Select\\"date"]`)).toBeTruthy();
+ expect(await page.$(`[placeholder='Select"date']`)).toBeTruthy();
+ await page.setContent('');
+ expect(await page.$(`[placeholder="Select \\" date"]`)).toBeTruthy();
+ expect(await page.$(`[placeholder='Select " date']`)).toBeTruthy();
+ await page.setContent('');
+ expect(await page.$(`[placeholder="Select'date"]`)).toBeTruthy();
+ expect(await page.$(`[placeholder='Select\\'date']`)).toBeTruthy();
+ await page.setContent('');
+ expect(await page.$(`[placeholder="Select ' date"]`)).toBeTruthy();
+ expect(await page.$(`[placeholder='Select \\' date']`)).toBeTruthy();
+});
+
+it('should work with spaces in css attributes when missing', async({page, server}) => {
+ const inputPromise = page.waitForSelector(`[placeholder="Select date"]`);
+ expect(await page.$(`[placeholder="Select date"]`)).toBe(null);
+ await page.setContent('');
+ await inputPromise;
+});
+
+it('should work with quotes in css attributes when missing', async({page, server}) => {
+ const inputPromise = page.waitForSelector(`[placeholder="Select\\"date"]`);
+ expect(await page.$(`[placeholder="Select\\"date"]`)).toBe(null);
+ await page.setContent('');
+ await inputPromise;
+});
+
+it('should return complex values', async({page, server}) => {
+ await page.setContent('43543');
+ const idAttribute = await page.$eval('css=section', e => [{ id: e.id }]);
+ expect(idAttribute).toEqual([{ id: 'testAttribute' }]);
+});
diff --git a/test/evaluation.jest.js b/test/evaluation.jest.js
deleted file mode 100644
index ca2417f6b9..0000000000
--- a/test/evaluation.jest.js
+++ /dev/null
@@ -1,630 +0,0 @@
-/**
- * Copyright 2018 Google Inc. All rights reserved.
- * Modifications copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-const utils = require('./utils');
-const path = require('path');
-const {FFOX, CHROMIUM, WEBKIT, USES_HOOKS} = testOptions;
-
-describe('Page.evaluate', function() {
- it('should work', async({page, server}) => {
- const result = await page.evaluate(() => 7 * 3);
- expect(result).toBe(21);
- });
- it('should transfer NaN', async({page, server}) => {
- const result = await page.evaluate(a => a, NaN);
- expect(Object.is(result, NaN)).toBe(true);
- });
- it('should transfer -0', async({page, server}) => {
- const result = await page.evaluate(a => a, -0);
- expect(Object.is(result, -0)).toBe(true);
- });
- it('should transfer Infinity', async({page, server}) => {
- const result = await page.evaluate(a => a, Infinity);
- expect(Object.is(result, Infinity)).toBe(true);
- });
- it('should transfer -Infinity', async({page, server}) => {
- const result = await page.evaluate(a => a, -Infinity);
- expect(Object.is(result, -Infinity)).toBe(true);
- });
- it('should roundtrip unserializable values', async({page}) => {
- const value = {
- infinity: Infinity,
- nInfinity: -Infinity,
- nZero: -0,
- nan: NaN,
- };
- const result = await page.evaluate(value => value, value);
- expect(result).toEqual(value);
- });
- it('should roundtrip promise to value', async({page}) => {
- {
- const result = await page.evaluate(value => Promise.resolve(value), null);
- expect(result === null).toBeTruthy();
- }
- {
- const result = await page.evaluate(value => Promise.resolve(value), Infinity);
- expect(result === Infinity).toBeTruthy();
- }
- {
- const result = await page.evaluate(value => Promise.resolve(value), -0);
- expect(result === -0).toBeTruthy();
- }
- {
- const result = await page.evaluate(value => Promise.resolve(value), undefined);
- expect(result === undefined).toBeTruthy();
- }
- });
- it('should roundtrip promise to unserializable values', async({page}) => {
- const value = {
- infinity: Infinity,
- nInfinity: -Infinity,
- nZero: -0,
- nan: NaN,
- };
- const result = await page.evaluate(value => Promise.resolve(value), value);
- expect(result).toEqual(value);
- });
- it('should transfer arrays', async({page, server}) => {
- const result = await page.evaluate(a => a, [1, 2, 3]);
- expect(result).toEqual([1,2,3]);
- });
- it('should transfer arrays as arrays, not objects', async({page, server}) => {
- const result = await page.evaluate(a => Array.isArray(a), [1, 2, 3]);
- expect(result).toBe(true);
- });
- it('should transfer maps as empty objects', async({page, server}) => {
- const result = await page.evaluate(a => a.x.constructor.name + ' ' + JSON.stringify(a.x), {x: new Map([[1, 2]])});
- expect(result).toBe('Object {}');
- });
- it('should modify global environment', async({page}) => {
- await page.evaluate(() => window.globalVar = 123);
- expect(await page.evaluate('globalVar')).toBe(123);
- });
- it('should evaluate in the page context', async({page, server}) => {
- await page.goto(server.PREFIX + '/global-var.html');
- expect(await page.evaluate('globalVar')).toBe(123);
- });
- it('should return undefined for objects with symbols', async({page, server}) => {
- expect(await page.evaluate(() => [Symbol('foo4')])).toEqual([undefined]);
- expect(await page.evaluate(() => {
- const a = { };
- a[Symbol('foo4')] = 42;
- return a;
- })).toEqual({});
- expect(await page.evaluate(() => {
- return { foo: [{ a: Symbol('foo4') }] };
- })).toEqual({ foo: [ { a: undefined } ] });
- });
- it('should work with function shorthands', async({page, server}) => {
- const a = {
- sum([a, b]) { return a + b; },
- async mult([a, b]) { return a * b; }
- };
- expect(await page.evaluate(a.sum, [1, 2])).toBe(3);
- expect(await page.evaluate(a.mult, [2, 4])).toBe(8);
- });
- it('should work with unicode chars', async({page, server}) => {
- const result = await page.evaluate(a => a['中文字符'], {'中文字符': 42});
- expect(result).toBe(42);
- });
- it('should throw when evaluation triggers reload', async({page, server}) => {
- let error = null;
- await page.evaluate(() => {
- location.reload();
- return new Promise(() => {});
- }).catch(e => error = e);
- expect(error.message).toContain('navigation');
- });
- it('should await promise', async({page, server}) => {
- const result = await page.evaluate(() => Promise.resolve(8 * 7));
- expect(result).toBe(56);
- });
- it('should work right after framenavigated', async({page, server}) => {
- let frameEvaluation = null;
- page.on('framenavigated', async frame => {
- frameEvaluation = frame.evaluate(() => 6 * 7);
- });
- await page.goto(server.EMPTY_PAGE);
- expect(await frameEvaluation).toBe(42);
- });
- it('should work right after a cross-origin navigation', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- let frameEvaluation = null;
- page.on('framenavigated', async frame => {
- frameEvaluation = frame.evaluate(() => 6 * 7);
- });
- await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
- expect(await frameEvaluation).toBe(42);
- });
- it('should work from-inside an exposed function', async({page, server}) => {
- // Setup inpage callback, which calls Page.evaluate
- await page.exposeFunction('callController', async function(a, b) {
- return await page.evaluate(({ a, b }) => a * b, { a, b });
- });
- const result = await page.evaluate(async function() {
- return await callController(9, 3);
- });
- expect(result).toBe(27);
- });
- it('should reject promise with exception', async({page, server}) => {
- let error = null;
- await page.evaluate(() => not_existing_object.property).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('not_existing_object');
- });
- it('should support thrown strings as error messages', async({page, server}) => {
- let error = null;
- await page.evaluate(() => { throw 'qwerty'; }).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('qwerty');
- });
- it('should support thrown numbers as error messages', async({page, server}) => {
- let error = null;
- await page.evaluate(() => { throw 100500; }).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('100500');
- });
- it('should return complex objects', async({page, server}) => {
- const object = {foo: 'bar!'};
- const result = await page.evaluate(a => a, object);
- expect(result).not.toBe(object);
- expect(result).toEqual(object);
- });
- it('should return NaN', async({page, server}) => {
- const result = await page.evaluate(() => NaN);
- expect(Object.is(result, NaN)).toBe(true);
- });
- it('should return -0', async({page, server}) => {
- const result = await page.evaluate(() => -0);
- expect(Object.is(result, -0)).toBe(true);
- });
- it('should return Infinity', async({page, server}) => {
- const result = await page.evaluate(() => Infinity);
- expect(Object.is(result, Infinity)).toBe(true);
- });
- it('should return -Infinity', async({page, server}) => {
- const result = await page.evaluate(() => -Infinity);
- expect(Object.is(result, -Infinity)).toBe(true);
- });
- it('should work with overwritten Promise', async({page, server}) => {
- await page.evaluate(() => {
- const originalPromise = window.Promise;
- class Promise2 {
- static all(...arg) {
- return wrap(originalPromise.all(...arg));
- }
- static race(...arg) {
- return wrap(originalPromise.race(...arg));
- }
- static resolve(...arg) {
- return wrap(originalPromise.resolve(...arg));
- }
- constructor(f, r) {
- this._promise = new originalPromise(f, r);
- }
- then(f, r) {
- return wrap(this._promise.then(f, r));
- }
- catch(f) {
- return wrap(this._promise.catch(f));
- }
- finally(f) {
- return wrap(this._promise.finally(f));
- }
- };
- const wrap = p => {
- const result = new Promise2(() => {}, () => {});
- result._promise = p;
- return result;
- };
- window.Promise = Promise2;
- window.__Promise2 = Promise2;
- });
-
- // Sanity check.
- expect(await page.evaluate(() => {
- const p = Promise.all([Promise.race([]), new Promise(() => {}).then(() => {})]);
- return p instanceof window.__Promise2;
- })).toBe(true);
-
- // Now, the new promise should be awaitable.
- expect(await page.evaluate(() => Promise.resolve(42))).toBe(42);
- });
- it('should throw when passed more than one parameter', async({page, server}) => {
- const expectThrow = async f => {
- let error;
- await f().catch(e => error = e);
- expect('' + error).toContain('Too many arguments');
- }
- await expectThrow(() => page.evaluate((a, b) => false, 1, 2));
- await expectThrow(() => page.evaluateHandle((a, b) => false, 1, 2));
- await expectThrow(() => page.$eval('sel', (a, b) => false, 1, 2));
- await expectThrow(() => page.$$eval('sel', (a, b) => false, 1, 2));
- await expectThrow(() => page.evaluate((a, b) => false, 1, 2));
- const frame = page.mainFrame();
- await expectThrow(() => frame.evaluate((a, b) => false, 1, 2));
- await expectThrow(() => frame.evaluateHandle((a, b) => false, 1, 2));
- await expectThrow(() => frame.$eval('sel', (a, b) => false, 1, 2));
- await expectThrow(() => frame.$$eval('sel', (a, b) => false, 1, 2));
- await expectThrow(() => frame.evaluate((a, b) => false, 1, 2));
- });
- it('should accept "undefined" as one of multiple parameters', async({page, server}) => {
- const result = await page.evaluate(({ a, b }) => Object.is(a, undefined) && Object.is(b, 'foo'), { a: undefined, b: 'foo' });
- expect(result).toBe(true);
- });
- it('should properly serialize undefined arguments', async({page}) => {
- expect(await page.evaluate(x => ({a: x}), undefined)).toEqual({});
- });
- it('should properly serialize undefined fields', async({page}) => {
- expect(await page.evaluate(() => ({a: undefined}))).toEqual({});
- });
- it('should return undefined properties', async({page}) => {
- const value = await page.evaluate(() => ({a: undefined}));
- expect('a' in value).toBe(true);
- });
- it('should properly serialize null arguments', async({page}) => {
- expect(await page.evaluate(x => x, null)).toEqual(null);
- });
- it('should properly serialize null fields', async({page}) => {
- expect(await page.evaluate(() => ({a: null}))).toEqual({a: null});
- });
- it('should return undefined for non-serializable objects', async({page, server}) => {
- expect(await page.evaluate(() => window)).toBe(undefined);
- });
- it('should fail for circular object', async({page, server}) => {
- const result = await page.evaluate(() => {
- const a = {};
- const b = {a};
- a.b = b;
- return a;
- });
- expect(result).toBe(undefined);
- });
- it('should be able to throw a tricky error', async({page, server}) => {
- const windowHandle = await page.evaluateHandle(() => window);
- const errorText = await windowHandle.jsonValue().catch(e => e.message);
- const error = await page.evaluate(errorText => {
- throw new Error(errorText);
- }, errorText).catch(e => e);
- expect(error.message).toContain(errorText);
- });
- it('should accept a string', async({page, server}) => {
- const result = await page.evaluate('1 + 2');
- expect(result).toBe(3);
- });
- it('should accept a string with semi colons', async({page, server}) => {
- const result = await page.evaluate('1 + 5;');
- expect(result).toBe(6);
- });
- it('should accept a string with comments', async({page, server}) => {
- const result = await page.evaluate('2 + 5;\n// do some math!');
- expect(result).toBe(7);
- });
- it('should accept element handle as an argument', async({page, server}) => {
- await page.setContent('42');
- const element = await page.$('section');
- const text = await page.evaluate(e => e.textContent, element);
- expect(text).toBe('42');
- });
- it('should throw if underlying element was disposed', async({page, server}) => {
- await page.setContent('39');
- const element = await page.$('section');
- expect(element).toBeTruthy();
- await element.dispose();
- let error = null;
- await page.evaluate(e => e.textContent, element).catch(e => error = e);
- expect(error.message).toContain('JSHandle is disposed');
- });
- it('should simulate a user gesture', async({page, server}) => {
- const result = await page.evaluate(() => {
- document.body.appendChild(document.createTextNode('test'));
- document.execCommand('selectAll');
- return document.execCommand('copy');
- });
- expect(result).toBe(true);
- });
- it('should throw a nice error after a navigation', async({page, server}) => {
- const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e);
- await Promise.all([
- page.waitForNavigation(),
- page.evaluate(() => {
- window.location.reload();
- setTimeout(() => window.__resolve(42), 1000);
- })
- ]);
- const error = await errorPromise;
- expect(error.message).toContain('navigation');
- });
- it('should not throw an error when evaluation does a navigation', async({page, server}) => {
- await page.goto(server.PREFIX + '/one-style.html');
- const result = await page.evaluate(() => {
- window.location = '/empty.html';
- return [42];
- });
- expect(result).toEqual([42]);
- });
- it.fail(WEBKIT)('should not throw an error when evaluation does a synchronous navigation and returns an object', async({page, server}) => {
- // It is imporant to be on about:blank for sync reload.
- const result = await page.evaluate(() => {
- window.location.reload();
- return {a: 42};
- });
- expect(result).toEqual({a: 42});
- });
- it('should not throw an error when evaluation does a synchronous navigation and returns undefined', async({page, server}) => {
- // It is imporant to be on about:blank for sync reload.
- const result = await page.evaluate(() => {
- window.location.reload();
- return undefined;
- });
- expect(result).toBe(undefined);
- });
- it.fail(USES_HOOKS)('should transfer 100Mb of data from page to node.js', async({page}) => {
- // This does not use hooks, but is slow in wire channel.
- const a = await page.evaluate(() => Array(100 * 1024 * 1024 + 1).join('a'));
- expect(a.length).toBe(100 * 1024 * 1024);
- });
- it('should throw error with detailed information on exception inside promise ', async({page, server}) => {
- let error = null;
- await page.evaluate(() => new Promise(() => {
- throw new Error('Error in promise');
- })).catch(e => error = e);
- expect(error.message).toContain('Error in promise');
- });
- it('should work even when JSON is set to null', async ({ page }) => {
- await page.evaluate(() => { window.JSON.stringify = null; window.JSON = null; });
- const result = await page.evaluate(() => ({abc: 123}));
- expect(result).toEqual({abc: 123});
- });
- it.fail(FFOX)('should await promise from popup', async ({page, server}) => {
- // Something is wrong about the way Firefox waits for the chained promise
- await page.goto(server.EMPTY_PAGE);
- const result = await page.evaluate(() => {
- const win = window.open('about:blank');
- return new win.Promise(f => f(42));
- });
- expect(result).toBe(42);
- });
- it('should work with new Function() and CSP', async({page, server}) => {
- server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
- await page.goto(server.PREFIX + '/empty.html');
- expect(await page.evaluate(() => new Function('return true')())).toBe(true);
- });
- it('should work with non-strict expressions', async({page, server}) => {
- expect(await page.evaluate(() => {
- y = 3.14;
- return y;
- })).toBe(3.14);
- });
- it('should respect use strict expression', async({page, server}) => {
- const error = await page.evaluate(() => {
- "use strict";
- variableY = 3.14;
- return variableY;
- }).catch(e => e);
- expect(error.message).toContain('variableY');
- });
- it('should not leak utility script', async({page, server}) => {
- expect(await page.evaluate(() => this === window)).toBe(true);
- });
- it('should not leak handles', async({page, server}) => {
- const error = await page.evaluate(() => handles.length).catch(e => e);
- expect(error.message).toContain(' handles');
- });
- it('should work with CSP', async({page, server}) => {
- server.setCSP('/empty.html', `script-src 'self'`);
- await page.goto(server.EMPTY_PAGE);
- expect(await page.evaluate(() => 2 + 2)).toBe(4);
- });
- it('should evaluate exception', async({page, server}) => {
- const error = await page.evaluate(() => {
- return (function functionOnStack() {
- return new Error('error message');
- })();
- });
- expect(error).toContain('Error: error message');
- expect(error).toContain('functionOnStack');
- });
- it('should evaluate exception', async({page, server}) => {
- const error = await page.evaluate(`new Error('error message')`);
- expect(error).toContain('Error: error message');
- });
- it('should evaluate date', async({page}) => {
- const result = await page.evaluate(() => ({ date: new Date('2020-05-27T01:31:38.506Z') }));
- expect(result).toEqual({ date: new Date('2020-05-27T01:31:38.506Z') });
- });
- it('should roundtrip date', async({page}) => {
- const date = new Date('2020-05-27T01:31:38.506Z');
- const result = await page.evaluate(date => date, date);
- expect(result.toUTCString()).toEqual(date.toUTCString());
- });
- it('should roundtrip regex', async({page}) => {
- const regex = /hello/im;
- const result = await page.evaluate(regex => regex, regex);
- expect(result.toString()).toEqual(regex.toString());
- });
- it('should jsonValue() date', async({page}) => {
- const resultHandle = await page.evaluateHandle(() => ({ date: new Date('2020-05-27T01:31:38.506Z') }));
- expect(await resultHandle.jsonValue()).toEqual({ date: new Date('2020-05-27T01:31:38.506Z') });
- });
- it('should not use toJSON when evaluating', async({page, server}) => {
- const result = await page.evaluate(() => ({ toJSON: () => 'string', data: 'data' }));
- expect(result).toEqual({ data: 'data', toJSON: {} });
- });
- it('should not use toJSON in jsonValue', async({page, server}) => {
- const resultHandle = await page.evaluateHandle(() => ({ toJSON: () => 'string', data: 'data' }));
- expect(await resultHandle.jsonValue()).toEqual({ data: 'data', toJSON: {} });
- });
-});
-
-describe('Page.addInitScript', function() {
- it('should evaluate before anything else on the page', async({page, server}) => {
- await page.addInitScript(function(){
- window.injected = 123;
- });
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- });
- it('should work with a path', async({page, server}) => {
- await page.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- });
- it('should work with content', async({page, server}) => {
- await page.addInitScript({ content: 'window.injected = 123' });
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- });
- it('should throw without path and content', async({page, server}) => {
- const error = await page.addInitScript({ foo: 'bar' }).catch(e => e);
- expect(error.message).toContain('Either path or content property must be present');
- });
- it('should work with browser context scripts', async({browser, server}) => {
- const context = await browser.newContext();
- await context.addInitScript(() => window.temp = 123);
- const page = await context.newPage();
- await page.addInitScript(() => window.injected = window.temp);
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- await context.close();
- });
- it('should work with browser context scripts with a path', async({browser, server}) => {
- const context = await browser.newContext();
- await context.addInitScript({ path: path.join(__dirname, 'assets/injectedfile.js') });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- await context.close();
- });
- it('should work with browser context scripts for already created pages', async({browser, server}) => {
- const context = await browser.newContext();
- const page = await context.newPage();
- await context.addInitScript(() => window.temp = 123);
- await page.addInitScript(() => window.injected = window.temp);
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- await context.close();
- });
- it('should support multiple scripts', async({page, server}) => {
- await page.addInitScript(function(){
- window.script1 = 1;
- });
- await page.addInitScript(function(){
- window.script2 = 2;
- });
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.script1)).toBe(1);
- expect(await page.evaluate(() => window.script2)).toBe(2);
- });
- it('should work with CSP', async({page, server}) => {
- server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
- await page.addInitScript(function(){
- window.injected = 123;
- });
- await page.goto(server.PREFIX + '/empty.html');
- expect(await page.evaluate(() => window.injected)).toBe(123);
-
- // Make sure CSP works.
- await page.addScriptTag({content: 'window.e = 10;'}).catch(e => void e);
- expect(await page.evaluate(() => window.e)).toBe(undefined);
- });
- it('should work after a cross origin navigation', async({page, server}) => {
- await page.goto(server.CROSS_PROCESS_PREFIX);
- await page.addInitScript(function(){
- window.injected = 123;
- });
- await page.goto(server.PREFIX + '/tamperable.html');
- expect(await page.evaluate(() => window.result)).toBe(123);
- });
-});
-
-describe('Frame.evaluate', function() {
- it('should have different execution contexts', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- expect(page.frames().length).toBe(2);
- await page.frames()[0].evaluate(() => window.FOO = 'foo');
- await page.frames()[1].evaluate(() => window.FOO = 'bar');
- expect(await page.frames()[0].evaluate(() => window.FOO)).toBe('foo');
- expect(await page.frames()[1].evaluate(() => window.FOO)).toBe('bar');
- });
- it('should have correct execution contexts', async({page, server}) => {
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- expect(page.frames().length).toBe(2);
- expect(await page.frames()[0].evaluate(() => document.body.textContent.trim())).toBe('');
- expect(await page.frames()[1].evaluate(() => document.body.textContent.trim())).toBe(`Hi, I'm frame`);
- });
-
- function expectContexts(pageImpl, count) {
- if (CHROMIUM)
- expect(pageImpl._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
- else
- expect(pageImpl._delegate._contextIdToContext.size).toBe(count);
- }
- it.skip(USES_HOOKS)('should dispose context on navigation', async({page, server, toImpl}) => {
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- expect(page.frames().length).toBe(2);
- expectContexts(toImpl(page), 4);
- await page.goto(server.EMPTY_PAGE);
- expectContexts(toImpl(page), 2);
- });
- it.skip(USES_HOOKS)('should dispose context on cross-origin navigation', async({page, server, toImpl}) => {
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- expect(page.frames().length).toBe(2);
- expectContexts(toImpl(page), 4);
- await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
- expectContexts(toImpl(page), 2);
- });
-
- it('should execute after cross-site navigation', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const mainFrame = page.mainFrame();
- expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost');
- await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
- expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
- });
- it('should not allow cross-frame js handles', async({page, server}) => {
- // TODO: this should actually be possible because frames script each other,
- // but protocol implementations do not support this. For now, assume current
- // behavior.
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- const handle = await page.evaluateHandle(() => {
- const iframe = document.querySelector('iframe');
- const foo = { bar: 'baz' };
- iframe.contentWindow.__foo = foo;
- return foo;
- });
- const childFrame = page.mainFrame().childFrames()[0];
- const childResult = await childFrame.evaluate(() => window.__foo);
- expect(childResult).toEqual({ bar: 'baz' });
- const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e);
- expect(error.message).toContain('JSHandles can be evaluated only in the context they were created!');
- });
- it('should allow cross-frame element handles', async({page, server}) => {
- await page.goto(server.PREFIX + '/frames/one-frame.html');
- const bodyHandle = await page.mainFrame().childFrames()[0].$('body');
- const result = await page.evaluate(body => body.innerHTML, bodyHandle);
- expect(result.trim()).toBe('
Hi, I\'m frame
');
- });
- it('should not allow cross-frame element handles when frames do not script each other', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
- const bodyHandle = await frame.$('body');
- const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e);
- expect(error.message).toContain('Unable to adopt element handle from a different document');
- });
-});
diff --git a/test/focus.jest.js b/test/focus.jest.js
deleted file mode 100644
index 82f30f9ec3..0000000000
--- a/test/focus.jest.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Copyright Microsoft Corporation. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-const {FFOX, CHROMIUM, LINUX, WEBKIT, MAC} = testOptions;
-
-describe('Page.focus', function() {
- it('should work', async function({page, server}) {
- await page.setContent(``);
- expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
- await page.focus('#d1');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
- });
- it('should emit focus event', async function({page, server}) {
- await page.setContent(``);
- let focused = false;
- await page.exposeFunction('focusEvent', () => focused = true);
- await page.evaluate(() => d1.addEventListener('focus', focusEvent));
- await page.focus('#d1');
- expect(focused).toBe(true);
- });
- it('should emit blur event', async function({page, server}) {
- await page.setContent(`
DIV1
DIV2
`);
- await page.focus('#d1');
- let focused = false;
- let blurred = false;
- await page.exposeFunction('focusEvent', () => focused = true);
- await page.exposeFunction('blurEvent', () => blurred = true);
- await page.evaluate(() => d1.addEventListener('blur', blurEvent));
- await page.evaluate(() => d2.addEventListener('focus', focusEvent));
- await page.focus('#d2');
- expect(focused).toBe(true);
- expect(blurred).toBe(true);
- });
- it('should traverse focus', async function({page}) {
- await page.setContent(``);
- let focused = false;
- await page.exposeFunction('focusEvent', () => focused = true);
- await page.evaluate(() => i2.addEventListener('focus', focusEvent));
-
- await page.focus('#i1');
- await page.keyboard.type("First");
- await page.keyboard.press("Tab");
- await page.keyboard.type("Last");
-
- expect(focused).toBe(true);
- expect(await page.$eval('#i1', e => e.value)).toBe('First');
- expect(await page.$eval('#i2', e => e.value)).toBe('Last');
- });
- it('should traverse focus in all directions', async function({page}) {
- await page.setContent(``);
- await page.keyboard.press('Tab');
- expect(await page.evaluate(() => document.activeElement.value)).toBe('1');
- await page.keyboard.press('Tab');
- expect(await page.evaluate(() => document.activeElement.value)).toBe('2');
- await page.keyboard.press('Tab');
- expect(await page.evaluate(() => document.activeElement.value)).toBe('3');
- await page.keyboard.press('Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.value)).toBe('2');
- await page.keyboard.press('Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.value)).toBe('1');
- });
- // Chromium and WebKit both have settings for tab traversing all links, but
- // it is only on by default in WebKit.
- it.skip(!MAC || !WEBKIT)('should traverse only form elements', async function({page}) {
- await page.setContent(`
-
-
- link
-
- `);
- await page.keyboard.press('Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
- await page.keyboard.press('Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('input-2');
- await page.keyboard.press('Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
- await page.keyboard.press('Alt+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('button');
- await page.keyboard.press('Alt+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('link');
- await page.keyboard.press('Alt+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('input-2');
- await page.keyboard.press('Alt+Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('link');
- await page.keyboard.press('Alt+Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('button');
- await page.keyboard.press('Alt+Shift+Tab');
- expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
- });
-});
diff --git a/test/focus.spec.js b/test/focus.spec.js
new file mode 100644
index 0000000000..2bbd509df7
--- /dev/null
+++ b/test/focus.spec.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright Microsoft Corporation. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const {FFOX, CHROMIUM, LINUX, WEBKIT, MAC} = testOptions;
+
+it('should work', async function({page, server}) {
+ await page.setContent(``);
+ expect(await page.evaluate(() => document.activeElement.nodeName)).toBe('BODY');
+ await page.focus('#d1');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('d1');
+});
+it('should emit focus event', async function({page, server}) {
+ await page.setContent(``);
+ let focused = false;
+ await page.exposeFunction('focusEvent', () => focused = true);
+ await page.evaluate(() => d1.addEventListener('focus', focusEvent));
+ await page.focus('#d1');
+ expect(focused).toBe(true);
+});
+it('should emit blur event', async function({page, server}) {
+ await page.setContent(`
DIV1
DIV2
`);
+ await page.focus('#d1');
+ let focused = false;
+ let blurred = false;
+ await page.exposeFunction('focusEvent', () => focused = true);
+ await page.exposeFunction('blurEvent', () => blurred = true);
+ await page.evaluate(() => d1.addEventListener('blur', blurEvent));
+ await page.evaluate(() => d2.addEventListener('focus', focusEvent));
+ await page.focus('#d2');
+ expect(focused).toBe(true);
+ expect(blurred).toBe(true);
+});
+it('should traverse focus', async function({page}) {
+ await page.setContent(``);
+ let focused = false;
+ await page.exposeFunction('focusEvent', () => focused = true);
+ await page.evaluate(() => i2.addEventListener('focus', focusEvent));
+
+ await page.focus('#i1');
+ await page.keyboard.type("First");
+ await page.keyboard.press("Tab");
+ await page.keyboard.type("Last");
+
+ expect(focused).toBe(true);
+ expect(await page.$eval('#i1', e => e.value)).toBe('First');
+ expect(await page.$eval('#i2', e => e.value)).toBe('Last');
+});
+it('should traverse focus in all directions', async function({page}) {
+ await page.setContent(``);
+ await page.keyboard.press('Tab');
+ expect(await page.evaluate(() => document.activeElement.value)).toBe('1');
+ await page.keyboard.press('Tab');
+ expect(await page.evaluate(() => document.activeElement.value)).toBe('2');
+ await page.keyboard.press('Tab');
+ expect(await page.evaluate(() => document.activeElement.value)).toBe('3');
+ await page.keyboard.press('Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.value)).toBe('2');
+ await page.keyboard.press('Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.value)).toBe('1');
+});
+// Chromium and WebKit both have settings for tab traversing all links, but
+// it is only on by default in WebKit.
+it.skip(!MAC || !WEBKIT)('should traverse only form elements', async function({page}) {
+ await page.setContent(`
+
+
+ link
+
+ `);
+ await page.keyboard.press('Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
+ await page.keyboard.press('Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('input-2');
+ await page.keyboard.press('Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
+ await page.keyboard.press('Alt+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('button');
+ await page.keyboard.press('Alt+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('link');
+ await page.keyboard.press('Alt+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('input-2');
+ await page.keyboard.press('Alt+Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('link');
+ await page.keyboard.press('Alt+Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('button');
+ await page.keyboard.press('Alt+Shift+Tab');
+ expect(await page.evaluate(() => document.activeElement.id)).toBe('input-1');
+});
diff --git a/test/frame-evaluate.spec.js b/test/frame-evaluate.spec.js
new file mode 100644
index 0000000000..a6fc6a1022
--- /dev/null
+++ b/test/frame-evaluate.spec.js
@@ -0,0 +1,173 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const path = require('path');
+const { FFOX, CHROMIUM, WEBKIT, USES_HOOKS } = testOptions;
+
+it('should have different execution contexts', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ await page.frames()[0].evaluate(() => window.FOO = 'foo');
+ await page.frames()[1].evaluate(() => window.FOO = 'bar');
+ expect(await page.frames()[0].evaluate(() => window.FOO)).toBe('foo');
+ expect(await page.frames()[1].evaluate(() => window.FOO)).toBe('bar');
+});
+
+it('should have correct execution contexts', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames().length).toBe(2);
+ expect(await page.frames()[0].evaluate(() => document.body.textContent.trim())).toBe('');
+ expect(await page.frames()[1].evaluate(() => document.body.textContent.trim())).toBe(`Hi, I'm frame`);
+});
+
+function expectContexts(pageImpl, count) {
+ if (CHROMIUM)
+ expect(pageImpl._delegate._mainFrameSession._contextIdToContext.size).toBe(count);
+ else
+ expect(pageImpl._delegate._contextIdToContext.size).toBe(count);
+}
+
+it.skip(USES_HOOKS)('should dispose context on navigation', async ({ page, server, toImpl }) => {
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames().length).toBe(2);
+ expectContexts(toImpl(page), 4);
+ await page.goto(server.EMPTY_PAGE);
+ expectContexts(toImpl(page), 2);
+});
+
+it.skip(USES_HOOKS)('should dispose context on cross-origin navigation', async ({ page, server, toImpl }) => {
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ expect(page.frames().length).toBe(2);
+ expectContexts(toImpl(page), 4);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expectContexts(toImpl(page), 2);
+});
+
+it('should execute after cross-site navigation', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost');
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(await mainFrame.evaluate(() => window.location.href)).toContain('127');
+});
+
+it('should not allow cross-frame js handles', async ({ page, server }) => {
+ // TODO: this should actually be possible because frames script each other,
+ // but protocol implementations do not support this. For now, assume current
+ // behavior.
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ const handle = await page.evaluateHandle(() => {
+ const iframe = document.querySelector('iframe');
+ const foo = { bar: 'baz' };
+ iframe.contentWindow.__foo = foo;
+ return foo;
+ });
+ const childFrame = page.mainFrame().childFrames()[0];
+ const childResult = await childFrame.evaluate(() => window.__foo);
+ expect(childResult).toEqual({ bar: 'baz' });
+ const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e);
+ expect(error.message).toContain('JSHandles can be evaluated only in the context they were created!');
+});
+
+it('should allow cross-frame element handles', async ({ page, server }) => {
+ await page.goto(server.PREFIX + '/frames/one-frame.html');
+ const bodyHandle = await page.mainFrame().childFrames()[0].$('body');
+ const result = await page.evaluate(body => body.innerHTML, bodyHandle);
+ expect(result.trim()).toBe('
Hi, I\'m frame
');
+});
+
+it('should not allow cross-frame element handles when frames do not script each other', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html');
+ const bodyHandle = await frame.$('body');
+ const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e);
+ expect(error.message).toContain('Unable to adopt element handle from a different document');
+});
+
+it('should throw for detached frames', async({page, server}) => {
+ const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.detachFrame(page, 'frame1');
+ let error = null;
+ await frame1.evaluate(() => 7 * 8).catch(e => error = e);
+ expect(error.message).toContain('Execution Context is not available in detached frame');
+});
+
+it('should be isolated between frames', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ const [frame1, frame2] = page.frames();
+ expect(frame1 !== frame2).toBeTruthy();
+
+ await Promise.all([
+ frame1.evaluate(() => window.a = 1),
+ frame2.evaluate(() => window.a = 2)
+ ]);
+ const [a1, a2] = await Promise.all([
+ frame1.evaluate(() => window.a),
+ frame2.evaluate(() => window.a)
+ ]);
+ expect(a1).toBe(1);
+ expect(a2).toBe(2);
+});
+
+it.fail(CHROMIUM || FFOX)('should work in iframes that failed initial navigation', async({page, server}) => {
+ // - Firefox does not report domcontentloaded for the iframe.
+ // - Chromium and Firefox report empty url.
+ // - Chromium does not report main/utility worlds for the iframe.
+ await page.setContent(`
+
+
+ `, { waitUntil: 'domcontentloaded' });
+ // Note: Chromium/Firefox never report 'load' event for the iframe.
+ await page.evaluate(() => {
+ const iframe = document.querySelector('iframe');
+ const div = iframe.contentDocument.createElement('div');
+ iframe.contentDocument.body.appendChild(div);
+ });
+ expect(page.frames()[1].url()).toBe('about:blank');
+ // Main world should work.
+ expect(await page.frames()[1].evaluate(() => window.location.href)).toBe('about:blank');
+ // Utility world should work.
+ expect(await page.frames()[1].$('div')).toBeTruthy();
+});
+
+it.fail(CHROMIUM)('should work in iframes that interrupted initial javascript url navigation', async({page, server}) => {
+ // Chromium does not report isolated world for the iframe.
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ const iframe = document.createElement('iframe');
+ iframe.src = 'javascript:""';
+ document.body.appendChild(iframe);
+ iframe.contentDocument.open();
+ iframe.contentDocument.write('
hello
');
+ iframe.contentDocument.close();
+ });
+ // Main world should work.
+ expect(await page.frames()[1].evaluate(() => window.top.location.href)).toBe(server.EMPTY_PAGE);
+ // Utility world should work.
+ expect(await page.frames()[1].$('div')).toBeTruthy();
+});
+
+it('evaluateHandle should work', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ const windowHandle = await mainFrame.evaluateHandle(() => window);
+ expect(windowHandle).toBeTruthy();
+});
diff --git a/test/frame-frame-element.spec.js b/test/frame-frame-element.spec.js
new file mode 100644
index 0000000000..bca18af9f6
--- /dev/null
+++ b/test/frame-frame-element.spec.js
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT} = testOptions;
+
+it('should work', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame2 = await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE);
+ const frame1handle1 = await page.$('#frame1');
+ const frame1handle2 = await frame1.frameElement();
+ const frame3handle1 = await page.$('#frame3');
+ const frame3handle2 = await frame3.frameElement();
+ expect(await frame1handle1.evaluate((a, b) => a === b, frame1handle2)).toBe(true);
+ expect(await frame3handle1.evaluate((a, b) => a === b, frame3handle2)).toBe(true);
+ expect(await frame1handle1.evaluate((a, b) => a === b, frame3handle1)).toBe(false);
+});
+
+it('should work with contentFrame', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const handle = await frame.frameElement();
+ const contentFrame = await handle.contentFrame();
+ expect(contentFrame).toBe(frame);
+});
+
+it('should throw when detached', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await page.$eval('#frame1', e => e.remove());
+ const error = await frame1.frameElement().catch(e => e);
+ expect(error.message).toContain('Frame has been detached.');
+});
diff --git a/test/frame-hierarcy.spec.js b/test/frame-hierarcy.spec.js
new file mode 100644
index 0000000000..da424f5b4e
--- /dev/null
+++ b/test/frame-hierarcy.spec.js
@@ -0,0 +1,179 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT} = testOptions;
+
+it('should handle nested frames', async({page, server}) => {
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ expect(utils.dumpFrames(page.mainFrame())).toEqual([
+ 'http://localhost:/frames/nested-frames.html',
+ ' http://localhost:/frames/frame.html (aframe)',
+ ' http://localhost:/frames/two-frames.html (2frames)',
+ ' http://localhost:/frames/frame.html (dos)',
+ ' http://localhost:/frames/frame.html (uno)',
+ ]);
+});
+it('should send events when frames are manipulated dynamically', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ // validate frameattached events
+ const attachedFrames = [];
+ page.on('frameattached', frame => attachedFrames.push(frame));
+ await utils.attachFrame(page, 'frame1', './assets/frame.html');
+ expect(attachedFrames.length).toBe(1);
+ expect(attachedFrames[0].url()).toContain('/assets/frame.html');
+
+ // validate framenavigated events
+ const navigatedFrames = [];
+ page.on('framenavigated', frame => navigatedFrames.push(frame));
+ await page.evaluate(() => {
+ const frame = document.getElementById('frame1');
+ frame.src = './empty.html';
+ return new Promise(x => frame.onload = x);
+ });
+ expect(navigatedFrames.length).toBe(1);
+ expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE);
+
+ // validate framedetached events
+ const detachedFrames = [];
+ page.on('framedetached', frame => detachedFrames.push(frame));
+ await utils.detachFrame(page, 'frame1');
+ expect(detachedFrames.length).toBe(1);
+ expect(detachedFrames[0].isDetached()).toBe(true);
+});
+it('should send "framenavigated" when navigating on anchor URLs', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ await Promise.all([
+ page.goto(server.EMPTY_PAGE + '#foo'),
+ page.waitForEvent('framenavigated')
+ ]);
+ expect(page.url()).toBe(server.EMPTY_PAGE + '#foo');
+});
+it('should persist mainFrame on cross-process navigation', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const mainFrame = page.mainFrame();
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(page.mainFrame() === mainFrame).toBeTruthy();
+});
+it('should not send attach/detach events for main frame', async({page, server}) => {
+ let hasEvents = false;
+ page.on('frameattached', frame => hasEvents = true);
+ page.on('framedetached', frame => hasEvents = true);
+ await page.goto(server.EMPTY_PAGE);
+ expect(hasEvents).toBe(false);
+});
+it('should detach child frames on navigation', async({page, server}) => {
+ let attachedFrames = [];
+ let detachedFrames = [];
+ let navigatedFrames = [];
+ page.on('frameattached', frame => attachedFrames.push(frame));
+ page.on('framedetached', frame => detachedFrames.push(frame));
+ page.on('framenavigated', frame => navigatedFrames.push(frame));
+ await page.goto(server.PREFIX + '/frames/nested-frames.html');
+ expect(attachedFrames.length).toBe(4);
+ expect(detachedFrames.length).toBe(0);
+ expect(navigatedFrames.length).toBe(5);
+
+ attachedFrames = [];
+ detachedFrames = [];
+ navigatedFrames = [];
+ await page.goto(server.EMPTY_PAGE);
+ expect(attachedFrames.length).toBe(0);
+ expect(detachedFrames.length).toBe(4);
+ expect(navigatedFrames.length).toBe(1);
+});
+it('should support framesets', async({page, server}) => {
+ let attachedFrames = [];
+ let detachedFrames = [];
+ let navigatedFrames = [];
+ page.on('frameattached', frame => attachedFrames.push(frame));
+ page.on('framedetached', frame => detachedFrames.push(frame));
+ page.on('framenavigated', frame => navigatedFrames.push(frame));
+ await page.goto(server.PREFIX + '/frames/frameset.html');
+ expect(attachedFrames.length).toBe(4);
+ expect(detachedFrames.length).toBe(0);
+ expect(navigatedFrames.length).toBe(5);
+
+ attachedFrames = [];
+ detachedFrames = [];
+ navigatedFrames = [];
+ await page.goto(server.EMPTY_PAGE);
+ expect(attachedFrames.length).toBe(0);
+ expect(detachedFrames.length).toBe(4);
+ expect(navigatedFrames.length).toBe(1);
+});
+it('should report frame from-inside shadow DOM', async({page, server}) => {
+ await page.goto(server.PREFIX + '/shadow.html');
+ await page.evaluate(async url => {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ document.body.shadowRoot.appendChild(frame);
+ await new Promise(x => frame.onload = x);
+ }, server.EMPTY_PAGE);
+ expect(page.frames().length).toBe(2);
+ expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
+});
+it('should report frame.name()', async({page, server}) => {
+ await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
+ await page.evaluate(url => {
+ const frame = document.createElement('iframe');
+ frame.name = 'theFrameName';
+ frame.src = url;
+ document.body.appendChild(frame);
+ return new Promise(x => frame.onload = x);
+ }, server.EMPTY_PAGE);
+ expect(page.frames()[0].name()).toBe('');
+ expect(page.frames()[1].name()).toBe('theFrameId');
+ expect(page.frames()[2].name()).toBe('theFrameName');
+});
+it('should report frame.parent()', async({page, server}) => {
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ expect(page.frames()[0].parentFrame()).toBe(null);
+ expect(page.frames()[1].parentFrame()).toBe(page.mainFrame());
+ expect(page.frames()[2].parentFrame()).toBe(page.mainFrame());
+});
+it('should report different frame instance when frame re-attaches', async({page, server}) => {
+ const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await page.evaluate(() => {
+ window.frame = document.querySelector('#frame1');
+ window.frame.remove();
+ });
+ expect(frame1.isDetached()).toBe(true);
+ const [frame2] = await Promise.all([
+ page.waitForEvent('frameattached'),
+ page.evaluate(() => document.body.appendChild(window.frame)),
+ ]);
+ expect(frame2.isDetached()).toBe(false);
+ expect(frame1).not.toBe(frame2);
+});
+it.fail(FFOX)('should refuse to display x-frame-options:deny iframe', async({page, server}) => {
+ server.setRoute('/x-frame-options-deny.html', async (req, res) => {
+ res.setHeader('Content-Type', 'text/html');
+ res.setHeader('X-Frame-Options', 'DENY');
+ res.end(`login
dangerous login page
`);
+ });
+ await page.goto(server.EMPTY_PAGE);
+ const refusalText = new Promise(f => {
+ page.on('console', msg => {
+ if (msg.text().match(/Refused to display/i))
+ f(msg.text());
+ });
+ });
+ await page.setContent(``);
+ expect(await refusalText).toMatch(/Refused to display 'http.*\/x-frame-options-deny\.html' in a frame because it set 'X-Frame-Options' to 'deny'./i)
+});
diff --git a/test/frame.jest.js b/test/frame.jest.js
deleted file mode 100644
index d52fbd281a..0000000000
--- a/test/frame.jest.js
+++ /dev/null
@@ -1,284 +0,0 @@
-/**
- * Copyright 2018 Google Inc. All rights reserved.
- * Modifications copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-const utils = require('./utils');
-const {FFOX, CHROMIUM, WEBKIT} = testOptions;
-
-describe('Frame.evaluateHandle', function() {
- it('should work', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const mainFrame = page.mainFrame();
- const windowHandle = await mainFrame.evaluateHandle(() => window);
- expect(windowHandle).toBeTruthy();
- });
-});
-
-describe('Frame.frameElement', function() {
- it('should work', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- const frame2 = await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
- const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE);
- const frame1handle1 = await page.$('#frame1');
- const frame1handle2 = await frame1.frameElement();
- const frame3handle1 = await page.$('#frame3');
- const frame3handle2 = await frame3.frameElement();
- expect(await frame1handle1.evaluate((a, b) => a === b, frame1handle2)).toBe(true);
- expect(await frame3handle1.evaluate((a, b) => a === b, frame3handle2)).toBe(true);
- expect(await frame1handle1.evaluate((a, b) => a === b, frame3handle1)).toBe(false);
- });
- it('should work with contentFrame', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- const handle = await frame.frameElement();
- const contentFrame = await handle.contentFrame();
- expect(contentFrame).toBe(frame);
- });
- it('should throw when detached', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- await page.$eval('#frame1', e => e.remove());
- const error = await frame1.frameElement().catch(e => e);
- expect(error.message).toContain('Frame has been detached.');
- });
-});
-
-describe('Frame.evaluate', function() {
- it('should throw for detached frames', async({page, server}) => {
- const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- await utils.detachFrame(page, 'frame1');
- let error = null;
- await frame1.evaluate(() => 7 * 8).catch(e => error = e);
- expect(error.message).toContain('Execution Context is not available in detached frame');
- });
- it('should be isolated between frames', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- expect(page.frames().length).toBe(2);
- const [frame1, frame2] = page.frames();
- expect(frame1 !== frame2).toBeTruthy();
-
- await Promise.all([
- frame1.evaluate(() => window.a = 1),
- frame2.evaluate(() => window.a = 2)
- ]);
- const [a1, a2] = await Promise.all([
- frame1.evaluate(() => window.a),
- frame2.evaluate(() => window.a)
- ]);
- expect(a1).toBe(1);
- expect(a2).toBe(2);
- });
- it.fail(CHROMIUM || FFOX)('should work in iframes that failed initial navigation', async({page, server}) => {
- // - Firefox does not report domcontentloaded for the iframe.
- // - Chromium and Firefox report empty url.
- // - Chromium does not report main/utility worlds for the iframe.
- await page.setContent(`
-
-
- `, { waitUntil: 'domcontentloaded' });
- // Note: Chromium/Firefox never report 'load' event for the iframe.
- await page.evaluate(() => {
- const iframe = document.querySelector('iframe');
- const div = iframe.contentDocument.createElement('div');
- iframe.contentDocument.body.appendChild(div);
- });
- expect(page.frames()[1].url()).toBe('about:blank');
- // Main world should work.
- expect(await page.frames()[1].evaluate(() => window.location.href)).toBe('about:blank');
- // Utility world should work.
- expect(await page.frames()[1].$('div')).toBeTruthy();
- });
- it.fail(CHROMIUM)('should work in iframes that interrupted initial javascript url navigation', async({page, server}) => {
- // Chromium does not report isolated world for the iframe.
- await page.goto(server.EMPTY_PAGE);
- await page.evaluate(() => {
- const iframe = document.createElement('iframe');
- iframe.src = 'javascript:""';
- document.body.appendChild(iframe);
- iframe.contentDocument.open();
- iframe.contentDocument.write('
');
+ const elements = await page.$$('div');
+ expect(elements.length).toBe(2);
+ const promises = elements.map(element => page.evaluate(e => e.textContent, element));
+ expect(await Promise.all(promises)).toEqual(['A', 'B']);
+});
+
+it('should return empty array if nothing is found', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const elements = await page.$$('div');
+ expect(elements.length).toBe(0);
+});
+
+it('xpath should query existing element', async({page, server}) => {
+ await page.setContent('test');
+ const elements = await page.$$('xpath=/html/body/section');
+ expect(elements[0]).toBeTruthy();
+ expect(elements.length).toBe(1);
+});
+
+it('xpath should return empty array for non-existing element', async({page, server}) => {
+ const element = await page.$$('//html/body/non-existing-element');
+ expect(element).toEqual([]);
+});
+
+it('xpath should return multiple elements', async({page, server}) => {
+ await page.setContent('');
+ const elements = await page.$$('xpath=/html/body/div');
+ expect(elements.length).toBe(2);
+});
diff --git a/test/selectors-css.spec.js b/test/selectors-css.spec.js
new file mode 100644
index 0000000000..9533a9289d
--- /dev/null
+++ b/test/selectors-css.spec.js
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('should work for open shadow roots', async({page, server}) => {
+ await page.goto(server.PREFIX + '/deep-shadow.html');
+ expect(await page.$eval(`css=span`, e => e.textContent)).toBe('Hello from root1');
+ expect(await page.$eval(`css=[attr="value\\ space"]`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$eval(`css=[attr='value\\ \\space']`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$eval(`css=div div span`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$eval(`css=div span + span`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$eval(`css=span + [attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$eval(`css=[data-testid="foo"] + [attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$eval(`css=#target`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$eval(`css=div #target`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$eval(`css=div div #target`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$(`css=div div div #target`)).toBe(null);
+ expect(await page.$eval(`css=section > div div span`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$eval(`css=section > div div span:nth-child(2)`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await page.$(`css=section div div div div`)).toBe(null);
+
+ const root2 = await page.$(`css=div div`);
+ expect(await root2.$eval(`css=#target`, e => e.textContent)).toBe('Hello from root2');
+ expect(await root2.$(`css:light=#target`)).toBe(null);
+ const root2Shadow = await root2.evaluateHandle(r => r.shadowRoot);
+ expect(await root2Shadow.$eval(`css:light=#target`, e => e.textContent)).toBe('Hello from root2');
+ const root3 = (await page.$$(`css=div div`))[1];
+ expect(await root3.$eval(`text=root3`, e => e.textContent)).toBe('Hello from root3');
+ expect(await root3.$eval(`css=[attr*="value"]`, e => e.textContent)).toBe('Hello from root3 #2');
+ expect(await root3.$(`css:light=[attr*="value"]`)).toBe(null);
+});
+
+it('should work with > combinator and spaces', async({page, server}) => {
+ await page.setContent(`
`);
+ expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"]>span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"]>span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(``);
+ expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(``);
+});
+
+it('should work with comma separated list', async({page, server}) => {
+ await page.goto(server.PREFIX + '/deep-shadow.html');
+ expect(await page.$$eval(`css=span,section #root1`, els => els.length)).toBe(5);
+ expect(await page.$$eval(`css=section #root1, div span`, els => els.length)).toBe(5);
+ expect(await page.$eval(`css=doesnotexist , section #root1`, e => e.id)).toBe('root1');
+ expect(await page.$$eval(`css=doesnotexist ,section #root1`, els => els.length)).toBe(1);
+ expect(await page.$$eval(`css=span,div span`, els => els.length)).toBe(4);
+ expect(await page.$$eval(`css=span,div span`, els => els.length)).toBe(4);
+ expect(await page.$$eval(`css=span,div span,div div span`, els => els.length)).toBe(4);
+ expect(await page.$$eval(`css=#target,[attr="value\\ space"]`, els => els.length)).toBe(2);
+ expect(await page.$$eval(`css=#target,[data-testid="foo"],[attr="value\\ space"]`, els => els.length)).toBe(4);
+ expect(await page.$$eval(`css=#target,[data-testid="foo"],[attr="value\\ space"],span`, els => els.length)).toBe(4);
+});
+
+it('should keep dom order with comma separated list', async({page}) => {
+ await page.setContent(`
`);
+ expect(await page.$$eval(`css=span,div`, els => els.map(e => e.nodeName).join(','))).toBe('SPAN,DIV');
+ expect(await page.$$eval(`css=div,span`, els => els.map(e => e.nodeName).join(','))).toBe('SPAN,DIV');
+ expect(await page.$$eval(`css=span div, div`, els => els.map(e => e.nodeName).join(','))).toBe('DIV');
+ expect(await page.$$eval(`*css=section >> css=div,span`, els => els.map(e => e.nodeName).join(','))).toBe('SECTION');
+ expect(await page.$$eval(`css=section >> *css=div >> css=x,y`, els => els.map(e => e.nodeName).join(','))).toBe('DIV');
+ expect(await page.$$eval(`css=section >> *css=div,span >> css=x,y`, els => els.map(e => e.nodeName).join(','))).toBe('SPAN,DIV');
+ expect(await page.$$eval(`css=section >> *css=div,span >> css=y`, els => els.map(e => e.nodeName).join(','))).toBe('SPAN,DIV');
+});
+
+it('should work with comma inside text', async({page}) => {
+ await page.setContent(``);
+ expect(await page.$eval(`css=div[attr="hello,world!"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=[attr="hello,world!"]`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=div[attr='hello,world!']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('');
+ expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe('');
+});
+
+it('should work with attribute selectors', async({page}) => {
+ await page.setContent(`
`);
+ await page.evaluate(() => window.div = document.querySelector('div'));
+ const selectors = [
+ `[attr="hello world"]`,
+ `[attr = "hello world"]`,
+ `[attr ~= world]`,
+ `[attr ^=hello ]`,
+ `[attr $= world ]`,
+ `[attr *= "llo wor" ]`,
+ `[attr2 |= hello]`,
+ `[attr = "Hello World" i ]`,
+ `[attr *= "llo WOR"i]`,
+ `[attr $= woRLD i]`,
+ `[attr2 = "hello-''>>foo=bar[]"]`,
+ `[attr2 $="foo=bar[]"]`,
+ ];
+ for (const selector of selectors)
+ expect(await page.$eval(selector, e => e === div)).toBe(true);
+ expect(await page.$eval(`[attr*=hello] span`, e => e.parentNode === div)).toBe(true);
+ expect(await page.$eval(`[attr*=hello] >> span`, e => e.parentNode === div)).toBe(true);
+ expect(await page.$eval(`[attr3="] span"] >> span`, e => e.parentNode === div)).toBe(true);
+});
diff --git a/test/selectors-misc.spec.js b/test/selectors-misc.spec.js
new file mode 100644
index 0000000000..bf0e492fdd
--- /dev/null
+++ b/test/selectors-misc.spec.js
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('should work for open shadow roots', async({page, server}) => {
+ await page.goto(server.PREFIX + '/deep-shadow.html');
+ expect(await page.$eval(`id=target`, e => e.textContent)).toBe('Hello from root2');
+ expect(await page.$eval(`data-testid=foo`, e => e.textContent)).toBe('Hello from root1');
+ expect(await page.$$eval(`data-testid=foo`, els => els.length)).toBe(3);
+ expect(await page.$(`id:light=target`)).toBe(null);
+ expect(await page.$(`data-testid:light=foo`)).toBe(null);
+ expect(await page.$$(`data-testid:light=foo`)).toEqual([]);
+});
diff --git a/test/selectors-register.spec.js b/test/selectors-register.spec.js
new file mode 100644
index 0000000000..ff1151096c
--- /dev/null
+++ b/test/selectors-register.spec.js
@@ -0,0 +1,112 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('should work', async ({playwright, page}) => {
+ const createTagSelector = () => ({
+ create(root, target) {
+ return target.nodeName;
+ },
+ query(root, selector) {
+ return root.querySelector(selector);
+ },
+ queryAll(root, selector) {
+ return Array.from(root.querySelectorAll(selector));
+ }
+ });
+ await utils.registerEngine(playwright, 'tag', `(${createTagSelector.toString()})()`);
+ await page.setContent('
');
+ await page.evaluate(() => window.__answer = document.querySelector('span'));
+ // Works in main if asked.
+ expect(await page.$eval('main=ignored', e => e.nodeName)).toBe('SPAN');
+ expect(await page.$eval('css=div >> main=ignored', e => e.nodeName)).toBe('SPAN');
+ expect(await page.$$eval('main=ignored', es => window.__answer !== undefined)).toBe(true);
+ expect(await page.$$eval('main=ignored', es => es.filter(e => e).length)).toBe(3);
+ // Works in isolated by default.
+ expect(await page.$('isolated=ignored')).toBe(null);
+ expect(await page.$('css=div >> isolated=ignored')).toBe(null);
+ // $$eval always works in main, to avoid adopting nodes one by one.
+ expect(await page.$$eval('isolated=ignored', es => window.__answer !== undefined)).toBe(true);
+ expect(await page.$$eval('isolated=ignored', es => es.filter(e => e).length)).toBe(3);
+ // At least one engine in main forces all to be in main.
+ expect(await page.$eval('main=ignored >> isolated=ignored', e => e.nodeName)).toBe('SPAN');
+ expect(await page.$eval('isolated=ignored >> main=ignored', e => e.nodeName)).toBe('SPAN');
+ // Can be chained to css.
+ expect(await page.$eval('main=ignored >> css=section', e => e.nodeName)).toBe('SECTION');
+});
+
+it('should handle errors', async ({playwright, page}) => {
+ let error = await page.$('neverregister=ignored').catch(e => e);
+ expect(error.message).toContain('Unknown engine "neverregister" while parsing selector neverregister=ignored');
+
+ const createDummySelector = () => ({
+ create(root, target) {
+ return target.nodeName;
+ },
+ query(root, selector) {
+ return root.querySelector('dummy');
+ },
+ queryAll(root, selector) {
+ return Array.from(root.querySelectorAll('dummy'));
+ }
+ });
+
+ error = await playwright.selectors.register('$', createDummySelector).catch(e => e);
+ expect(error.message).toBe('Selector engine name may only contain [a-zA-Z0-9_] characters');
+
+ // Selector names are case-sensitive.
+ await utils.registerEngine(playwright, 'dummy', createDummySelector);
+ await utils.registerEngine(playwright, 'duMMy', createDummySelector);
+
+ error = await playwright.selectors.register('dummy', createDummySelector).catch(e => e);
+ expect(error.message).toBe('"dummy" selector engine has been already registered');
+
+ error = await playwright.selectors.register('css', createDummySelector).catch(e => e);
+ expect(error.message).toBe('"css" is a predefined selector engine');
+});
diff --git a/test/selectors-text.spec.js b/test/selectors-text.spec.js
new file mode 100644
index 0000000000..bef71cf63b
--- /dev/null
+++ b/test/selectors-text.spec.js
@@ -0,0 +1,208 @@
+/**
+ * Copyright 2018 Google Inc. All rights reserved.
+ * Modifications copyright (c) Microsoft Corporation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions;
+
+it('query', async ({page}) => {
+ await page.setContent(`
yo
ya
\nye
`);
+ expect(await page.$eval(`text=ya`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text="ya"`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text=/^[ay]+$/`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text=/Ya/i`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text=ye`, e => e.outerHTML)).toBe('
\nye
');
+
+ await page.setContent(`
ye
ye
`);
+ expect(await page.$eval(`text="ye"`, e => e.outerHTML)).toBe('
ye
');
+
+ await page.setContent(`
yo
"ya
hello world!
`);
+ expect(await page.$eval(`text="\\"ya"`, e => e.outerHTML)).toBe('
"ya
');
+ expect(await page.$eval(`text=/hello/`, e => e.outerHTML)).toBe('
hello world!
');
+ expect(await page.$eval(`text=/^\\s*heLLo/i`, e => e.outerHTML)).toBe('
hello world!
');
+
+ await page.setContent(`
yo
ya
hey
hey
`);
+ expect(await page.$eval(`text=hey`, e => e.outerHTML)).toBe('
yo
ya
hey
hey
');
+ expect(await page.$eval(`text="yo">>text="ya"`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text='yo'>> text="ya"`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text="yo" >>text='ya'`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`text='yo' >> text='ya'`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`'yo'>>"ya"`, e => e.outerHTML)).toBe('
ya
');
+ expect(await page.$eval(`"yo" >> 'ya'`, e => e.outerHTML)).toBe('
ya
');
+
+ await page.setContent(`
yo
yo
`);
+ expect(await page.$$eval(`text=yo`, es => es.map(e => e.outerHTML).join('\n'))).toBe('
yo
\n
yo
');
+
+ await page.setContent(`
'
"
\\
x
`);
+ expect(await page.$eval(`text='\\''`, e => e.outerHTML)).toBe('
\'
');
+ expect(await page.$eval(`text='"'`, e => e.outerHTML)).toBe('
"
');
+ expect(await page.$eval(`text="\\""`, e => e.outerHTML)).toBe('
"
');
+ expect(await page.$eval(`text="'"`, e => e.outerHTML)).toBe('
\'
');
+ expect(await page.$eval(`text="\\x"`, e => e.outerHTML)).toBe('
x
');
+ expect(await page.$eval(`text='\\x'`, e => e.outerHTML)).toBe('
x
');
+ expect(await page.$eval(`text='\\\\'`, e => e.outerHTML)).toBe('
\\
');
+ expect(await page.$eval(`text="\\\\"`, e => e.outerHTML)).toBe('
\\
');
+ expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('
"
');
+ expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('
\'
');
+ expect(await page.$eval(`"x"`, e => e.outerHTML)).toBe('
x
');
+ expect(await page.$eval(`'x'`, e => e.outerHTML)).toBe('