From bb267356fdfd2c76203ffa0eae2d0472880717fe Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 3 Aug 2020 15:23:53 -0700 Subject: [PATCH] test: remove describes (2) (#3276) --- test/elementhandle-bounding-box.spec.js | 110 +++ test/elementhandle-click.spec.js | 87 ++ test/elementhandle-content-frame.spec.js | 55 ++ test/elementhandle-convenience.spec.js | 154 ++++ test/elementhandle-eval-on-selector.spec.js | 66 ++ test/elementhandle-misc.spec.js | 79 ++ test/elementhandle-owner-frame.spec.js | 88 ++ test/elementhandle-press.spec.js | 55 ++ test/elementhandle-query-selector.spec.js | 97 +++ test/elementhandle-scroll-into-view.spec.js | 96 +++ test/elementhandle-select-text.spec.js | 69 ++ test/elementhandle-type.spec.js | 55 ++ test/elementhandle.jest.js | 703 ---------------- test/environments.js | 282 ------- test/eval-on-selector-all.spec.js | 71 ++ test/eval-on-selector.spec.js | 204 +++++ test/evaluation.jest.js | 630 --------------- test/focus.jest.js | 103 --- test/focus.spec.js | 101 +++ test/frame-evaluate.spec.js | 173 ++++ test/frame-frame-element.spec.js | 49 ++ test/frame-hierarcy.spec.js | 179 ++++ test/frame.jest.js | 284 ------- test/geolocation.jest.js | 150 ---- test/geolocation.spec.js | 148 ++++ test/headful.jest.js | 164 ---- test/headful.spec.js | 162 ++++ test/ignorehttpserrors.jest.js | 91 --- test/ignorehttpserrors.spec.js | 89 ++ test/keyboard.jest.js | 410 ---------- test/keyboard.spec.js | 408 ++++++++++ test/logger.jest.js | 53 -- test/logger.spec.js | 51 ++ test/mouse.jest.js | 181 ----- test/mouse.spec.js | 187 +++++ test/page-add-init-script.spec.js | 99 +++ test/page-evaluate.spec.js | 470 +++++++++++ test/page-popup-event.spec.js | 184 +++++ test/{pdf.jest.js => pdf.spec.js} | 18 +- test/permissions.jest.js | 136 ---- test/permissions.spec.js | 146 ++++ test/popup.jest.js | 417 ---------- test/popup.spec.js | 241 ++++++ test/proxy.jest.js | 119 --- test/proxy.spec.js | 115 +++ test/queryselector.jest.js | 852 -------------------- test/queryselector.spec.js | 113 +++ test/selectors-css.spec.js | 124 +++ test/selectors-misc.spec.js | 30 + test/selectors-register.spec.js | 112 +++ test/selectors-text.spec.js | 208 +++++ 51 files changed, 4682 insertions(+), 4586 deletions(-) create mode 100644 test/elementhandle-bounding-box.spec.js create mode 100644 test/elementhandle-click.spec.js create mode 100644 test/elementhandle-content-frame.spec.js create mode 100644 test/elementhandle-convenience.spec.js create mode 100644 test/elementhandle-eval-on-selector.spec.js create mode 100644 test/elementhandle-misc.spec.js create mode 100644 test/elementhandle-owner-frame.spec.js create mode 100644 test/elementhandle-press.spec.js create mode 100644 test/elementhandle-query-selector.spec.js create mode 100644 test/elementhandle-scroll-into-view.spec.js create mode 100644 test/elementhandle-select-text.spec.js create mode 100644 test/elementhandle-type.spec.js delete mode 100644 test/elementhandle.jest.js delete mode 100644 test/environments.js create mode 100644 test/eval-on-selector-all.spec.js create mode 100644 test/eval-on-selector.spec.js delete mode 100644 test/evaluation.jest.js delete mode 100644 test/focus.jest.js create mode 100644 test/focus.spec.js create mode 100644 test/frame-evaluate.spec.js create mode 100644 test/frame-frame-element.spec.js create mode 100644 test/frame-hierarcy.spec.js delete mode 100644 test/frame.jest.js delete mode 100644 test/geolocation.jest.js create mode 100644 test/geolocation.spec.js delete mode 100644 test/headful.jest.js create mode 100644 test/headful.spec.js delete mode 100644 test/ignorehttpserrors.jest.js create mode 100644 test/ignorehttpserrors.spec.js delete mode 100644 test/keyboard.jest.js create mode 100644 test/keyboard.spec.js delete mode 100644 test/logger.jest.js create mode 100644 test/logger.spec.js delete mode 100644 test/mouse.jest.js create mode 100644 test/mouse.spec.js create mode 100644 test/page-add-init-script.spec.js create mode 100644 test/page-evaluate.spec.js create mode 100644 test/page-popup-event.spec.js rename test/{pdf.jest.js => pdf.spec.js} (62%) delete mode 100644 test/permissions.jest.js create mode 100644 test/permissions.spec.js delete mode 100644 test/popup.jest.js create mode 100644 test/popup.spec.js delete mode 100644 test/proxy.jest.js create mode 100644 test/proxy.spec.js delete mode 100644 test/queryselector.jest.js create mode 100644 test/queryselector.spec.js create mode 100644 test/selectors-css.spec.js create mode 100644 test/selectors-misc.spec.js create mode 100644 test/selectors-register.spec.js create mode 100644 test/selectors-text.spec.js diff --git a/test/elementhandle-bounding-box.spec.js b/test/elementhandle-bounding-box.spec.js new file mode 100644 index 0000000000..4cfc3bda88 --- /dev/null +++ b/test/elementhandle-bounding-box.spec.js @@ -0,0 +1,110 @@ +/** + * 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.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('
hi
'); + const element = await page.$('div'); + expect(await element.boundingBox()).toBe(null); +}); +it('should force a layout', async ({ page, server }) => { + await page.setViewportSize({ width: 500, height: 500 }); + await page.setContent('
hello
'); + const elementHandle = await page.$('div'); + await page.evaluate(element => element.style.height = '200px', elementHandle); + const box = await elementHandle.boundingBox(); + expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); +}); +it('should work with SVG nodes', async ({ page, server }) => { + await page.setContent(` + + + + `); + const element = await page.$('#therect'); + const pwBoundingBox = await element.boundingBox(); + const webBoundingBox = await page.evaluate(e => { + const rect = e.getBoundingClientRect(); + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; + }, element); + expect(pwBoundingBox).toEqual(webBoundingBox); +}); +it.skip(FFOX)('should work with page scale', async ({ browser, server }) => { + const context = await browser.newContext({ viewport: { width: 400, height: 400, isMobile: true } }); + const page = await context.newPage(); + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await button.evaluate(button => { + document.body.style.margin = '0'; + button.style.borderWidth = '0'; + button.style.width = '200px'; + button.style.height = '20px'; + button.style.marginLeft = '17px'; + button.style.marginTop = '23px'; + }); + const box = await button.boundingBox(); + expect(Math.round(box.x * 100)).toBe(17 * 100); + expect(Math.round(box.y * 100)).toBe(23 * 100); + expect(Math.round(box.width * 100)).toBe(200 * 100); + expect(Math.round(box.height * 100)).toBe(20 * 100); + await context.close(); +}); +it('should work when inline box child is outside of viewport', async ({ page, server }) => { + await page.setContent(` + + woofdoggo + `); + const handle = await page.$('span'); + const box = await handle.boundingBox(); + const webBoundingBox = await handle.evaluate(e => { + const rect = e.getBoundingClientRect(); + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; + }); + const round = box => ({ + x: Math.round(box.x * 100), + y: Math.round(box.y * 100), + width: Math.round(box.width * 100), + height: Math.round(box.height * 100), + }); + expect(round(box)).toEqual(round(webBoundingBox)); +}); diff --git a/test/elementhandle-click.spec.js b/test/elementhandle-click.spec.js new file mode 100644 index 0000000000..122060b115 --- /dev/null +++ b/test/elementhandle-click.spec.js @@ -0,0 +1,87 @@ +/** + * 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 + '/input/button.html'); + const button = await page.$('button'); + await button.click(); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); +it('should work with Node removed', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => delete window['Node']); + const button = await page.$('button'); + await button.click(); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); +it('should work for Shadow DOM v1', async ({ page, server }) => { + await page.goto(server.PREFIX + '/shadow.html'); + const buttonHandle = await page.evaluateHandle(() => button); + await buttonHandle.click(); + expect(await page.evaluate(() => clicked)).toBe(true); +}); +it('should work for TextNodes', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); + await buttonTextNode.click(); + expect(await page.evaluate(() => result)).toBe('Clicked'); +}); +it('should throw for detached nodes', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.remove(), button); + let error = null; + await button.click().catch(err => error = err); + expect(error.message).toContain('Element is not attached to the DOM'); +}); +it('should throw for hidden nodes with force', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.style.display = 'none', button); + const error = await button.click({ force: true }).catch(err => err); + expect(error.message).toContain('Element is not visible'); +}); +it('should throw for recursively hidden nodes with force', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.parentElement.style.display = 'none', button); + const error = await button.click({ force: true }).catch(err => err); + expect(error.message).toContain('Element is not visible'); +}); +it('should throw for
elements with force', async ({ page, server }) => { + await page.setContent('hello
goodbye'); + const br = await page.$('br'); + const error = await br.click({ force: true }).catch(err => err); + expect(error.message).toContain('Element is outside of the viewport'); +}); +it('should double click the button', async ({ page, server }) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + window.double = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window.double = true; + }); + }); + const button = await page.$('button'); + await button.dblclick(); + expect(await page.evaluate('double')).toBe(true); + expect(await page.evaluate('result')).toBe('Clicked'); +}); diff --git a/test/elementhandle-content-frame.spec.js b/test/elementhandle-content-frame.spec.js new file mode 100644 index 0000000000..f22d11fe81 --- /dev/null +++ b/test/elementhandle-content-frame.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, server }) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const elementHandle = await page.$('#frame1'); + const frame = await elementHandle.contentFrame(); + expect(frame).toBe(page.frames()[1]); +}); +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 elementHandle = await page.$('#frame1'); + const frame = await elementHandle.contentFrame(); + expect(frame).toBe(page.frames()[1]); +}); +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.frames()[1]; + const elementHandle = await frame.evaluateHandle(() => window.top.document.querySelector('#frame1')); + expect(await elementHandle.contentFrame()).toBe(frame); +}); +it('should return null for non-iframes', 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.contentFrame()).toBe(null); +}); +it('should return null for document.documentElement', 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.documentElement); + expect(await elementHandle.contentFrame()).toBe(null); +}); diff --git a/test/elementhandle-convenience.spec.js b/test/elementhandle-convenience.spec.js new file mode 100644 index 0000000000..bad03e27e3 --- /dev/null +++ b/test/elementhandle-convenience.spec.js @@ -0,0 +1,154 @@ +/** + * 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 have a nice preview', async ({ page, server }) => { + await page.goto(`${server.PREFIX}/dom.html`); + const outer = await page.$('#outer'); + const inner = await page.$('#inner'); + const check = await page.$('#check'); + const text = await inner.evaluateHandle(e => e.firstChild); + await page.evaluate(() => 1); // Give them a chance to calculate the preview. + expect(String(outer)).toBe('JSHandle@
'); + expect(String(inner)).toBe('JSHandle@
Text,↵more text
'); + expect(String(text)).toBe('JSHandle@#text=Text,↵more text'); + expect(String(check)).toBe('JSHandle@'); +}); +it('getAttribute should work', async ({ page, server }) => { + await page.goto(`${server.PREFIX}/dom.html`); + const handle = await page.$('#outer'); + expect(await handle.getAttribute('name')).toBe('value'); + expect(await handle.getAttribute('foo')).toBe(null); + expect(await page.getAttribute('#outer', 'name')).toBe('value'); + expect(await page.getAttribute('#outer', 'foo')).toBe(null); +}); +it('innerHTML should work', async ({ page, server }) => { + await page.goto(`${server.PREFIX}/dom.html`); + const handle = await page.$('#outer'); + expect(await handle.innerHTML()).toBe('
Text,\nmore text
'); + expect(await page.innerHTML('#outer')).toBe('
Text,\nmore text
'); +}); +it('innerText should work', async ({ page, server }) => { + await page.goto(`${server.PREFIX}/dom.html`); + const handle = await page.$('#inner'); + expect(await handle.innerText()).toBe('Text, more text'); + expect(await page.innerText('#inner')).toBe('Text, more text'); +}); +it('innerText should throw', async ({ page, server }) => { + await page.setContent(`text`); + const error1 = await page.innerText('svg').catch(e => e); + expect(error1.message).toContain('Not an HTMLElement'); + const handle = await page.$('svg'); + const error2 = await handle.innerText().catch(e => e); + expect(error2.message).toContain('Not an HTMLElement'); +}); +it('textContent should work', async ({ page, server }) => { + await page.goto(`${server.PREFIX}/dom.html`); + const handle = await page.$('#inner'); + expect(await handle.textContent()).toBe('Text,\nmore text'); + expect(await page.textContent('#inner')).toBe('Text,\nmore text'); +}); +it('textContent 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.textContent = 'modified'); + return result; + }, + queryAll(root, selector) { + const result = Array.from(root.querySelectorAll(selector)); + for (const e of result) + Promise.resolve().then(() => result.textContent = 'modified'); + return result; + } + }); + await utils.registerEngine(playwright, 'textContent', createDummySelector); + await page.setContent(`
Hello
`); + const tc = await page.textContent('textContent=div'); + expect(tc).toBe('Hello'); + expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('modified'); +}); +it('innerText 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.textContent = 'modified'); + return result; + }, + queryAll(root, selector) { + const result = Array.from(root.querySelectorAll(selector)); + for (const e of result) + Promise.resolve().then(() => result.textContent = 'modified'); + return result; + } + }); + await utils.registerEngine(playwright, 'innerText', createDummySelector); + await page.setContent(`
Hello
`); + const tc = await page.innerText('innerText=div'); + expect(tc).toBe('Hello'); + expect(await page.evaluate(() => document.querySelector('div').innerText)).toBe('modified'); +}); +it('innerHTML 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.textContent = 'modified'); + return result; + }, + queryAll(root, selector) { + const result = Array.from(root.querySelectorAll(selector)); + for (const e of result) + Promise.resolve().then(() => result.textContent = 'modified'); + return result; + } + }); + await utils.registerEngine(playwright, 'innerHTML', createDummySelector); + await page.setContent(`
Helloworld
`); + 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('
10
'); + const tweet = await page.$('.tweet'); + const content = await tweet.$eval('.like', node => node.innerText); + expect(content).toBe('100'); +}); + +it('should retrieve content from subtree', async({page, server}) => { + const htmlContent = '
not-a-child-div
a-child-div
'; + await page.setContent(htmlContent); + const elementHandle = await page.$('#myId'); + const content = await elementHandle.$eval('.a', node => node.innerText); + expect(content).toBe('a-child-div'); +}); + +it('should throw in case of missing selector', async({page, server}) => { + const htmlContent = '
not-a-child-div
'; + await page.setContent(htmlContent); + const elementHandle = await page.$('#myId'); + const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message); + expect(errorMessage).toContain(`Error: failed to find element matching selector ".a"`); +}); + +it('should work for all', async({page, server}) => { + await page.setContent('
'); + const tweet = await page.$('.tweet'); + const content = await tweet.$$eval('.like', nodes => nodes.map(n => n.innerText)); + expect(content).toEqual(['100', '10']); +}); + +it('should retrieve content from subtree for all', async({page, server}) => { + const htmlContent = '
not-a-child-div
a1-child-div
a2-child-div
'; + 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('
A
'); + const html = await page.$('html'); + const second = await html.$('.second'); + const inner = await second.$('.inner'); + const content = await page.evaluate(e => e.textContent, inner); + expect(content).toBe('A'); +}); + +it('should return null for non-existing element', async({page, server}) => { + await page.setContent('
B
'); + const html = await page.$('html'); + const second = await html.$('.third'); + expect(second).toBe(null); +}); + +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); + const span = document.createElement('span'); + span.textContent = 'hello'; + div.appendChild(span); + return div; + }); + expect(await divHandle.$('span')).toBeTruthy(); + expect(await divHandle.$eval('span', e => e.textContent)).toBe('hello'); + + await popup.waitForLoadState('domcontentloaded'); + await page.evaluate(() => { + const div = document.querySelector('div'); + window.__popup.document.body.appendChild(div); + }); + expect(await divHandle.$('span')).toBeTruthy(); + expect(await divHandle.$eval('span', e => e.textContent)).toBe('hello'); +}); + +it('should query existing elements', async({page, server}) => { + await page.setContent('
A

B
'); + const html = await page.$('html'); + const elements = await html.$$('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 for non-existing elements', async({page, server}) => { + await page.setContent('A
B'); + const html = await page.$('html'); + const elements = await html.$$('div'); + expect(elements.length).toBe(0); +}); + + +it('xpath should query existing element', async({page, server}) => { + await page.goto(server.PREFIX + '/playground.html'); + await page.setContent('
A
'); + const html = await page.$('html'); + const second = await html.$$(`xpath=./body/div[contains(@class, 'second')]`); + const inner = await second[0].$$(`xpath=./div[contains(@class, 'inner')]`); + const content = await page.evaluate(e => e.textContent, inner[0]); + expect(content).toBe('A'); +}); + +it('xpath should return null for non-existing element', async({page, server}) => { + await page.setContent('
B
'); + 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('
hi
'); - const element = await page.$('div'); - expect(await element.boundingBox()).toBe(null); - }); - it('should force a layout', async({page, server}) => { - await page.setViewportSize({ width: 500, height: 500 }); - await page.setContent('
hello
'); - const elementHandle = await page.$('div'); - await page.evaluate(element => element.style.height = '200px', elementHandle); - const box = await elementHandle.boundingBox(); - expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); - }); - it('should work with SVG nodes', async({page, server}) => { - await page.setContent(` - - - - `); - const element = await page.$('#therect'); - const pwBoundingBox = await element.boundingBox(); - const webBoundingBox = await page.evaluate(e => { - const rect = e.getBoundingClientRect(); - return {x: rect.x, y: rect.y, width: rect.width, height: rect.height}; - }, element); - expect(pwBoundingBox).toEqual(webBoundingBox); - }); - it.skip(FFOX)('should work with page scale', async({browser, server}) => { - const context = await browser.newContext({ viewport: { width: 400, height: 400, isMobile: true} }); - const page = await context.newPage(); - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await button.evaluate(button => { - document.body.style.margin = '0'; - button.style.borderWidth = '0'; - button.style.width = '200px'; - button.style.height = '20px'; - button.style.marginLeft = '17px'; - button.style.marginTop = '23px'; - }); - const box = await button.boundingBox(); - expect(Math.round(box.x * 100)).toBe(17 * 100); - expect(Math.round(box.y * 100)).toBe(23 * 100); - expect(Math.round(box.width * 100)).toBe(200 * 100); - expect(Math.round(box.height * 100)).toBe(20 * 100); - await context.close(); - }); - it('should work when inline box child is outside of viewport', async({page, server}) => { - await page.setContent(` - - woofdoggo - `); - const handle = await page.$('span'); - const box = await handle.boundingBox(); - const webBoundingBox = await handle.evaluate(e => { - const rect = e.getBoundingClientRect(); - return {x: rect.x, y: rect.y, width: rect.width, height: rect.height}; - }); - const round = box => ({ - x: Math.round(box.x * 100), - y: Math.round(box.y * 100), - width: Math.round(box.width * 100), - height: Math.round(box.height * 100), - }); - expect(round(box)).toEqual(round(webBoundingBox)); - }); -}); - -describe('ElementHandle.contentFrame', function() { - it('should work', async({page,server}) => { - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const elementHandle = await page.$('#frame1'); - const frame = await elementHandle.contentFrame(); - expect(frame).toBe(page.frames()[1]); - }); - 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 elementHandle = await page.$('#frame1'); - const frame = await elementHandle.contentFrame(); - expect(frame).toBe(page.frames()[1]); - }); - 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.frames()[1]; - const elementHandle = await frame.evaluateHandle(() => window.top.document.querySelector('#frame1')); - expect(await elementHandle.contentFrame()).toBe(frame); - }); - it('should return null for non-iframes', 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.contentFrame()).toBe(null); - }); - it('should return null for document.documentElement', 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.documentElement); - expect(await elementHandle.contentFrame()).toBe(null); - }); -}); - -describe('ElementHandle.ownerFrame', function() { - 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()); - }); -}); - -describe('ElementHandle.click', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await button.click(); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should work with Node removed', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => delete window['Node']); - const button = await page.$('button'); - await button.click(); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should work for Shadow DOM v1', async({page, server}) => { - await page.goto(server.PREFIX + '/shadow.html'); - const buttonHandle = await page.evaluateHandle(() => button); - await buttonHandle.click(); - expect(await page.evaluate(() => clicked)).toBe(true); - }); - it('should work for TextNodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); - await buttonTextNode.click(); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should throw for detached nodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.remove(), button); - let error = null; - await button.click().catch(err => error = err); - expect(error.message).toContain('Element is not attached to the DOM'); - }); - it('should throw for hidden nodes with force', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.style.display = 'none', button); - const error = await button.click({ force: true }).catch(err => err); - expect(error.message).toContain('Element is not visible'); - }); - it('should throw for recursively hidden nodes with force', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.parentElement.style.display = 'none', button); - const error = await button.click({ force: true }).catch(err => err); - expect(error.message).toContain('Element is not visible'); - }); - it('should throw for
elements with force', async({page, server}) => { - await page.setContent('hello
goodbye'); - const br = await page.$('br'); - const error = await br.click({ force: true }).catch(err => err); - expect(error.message).toContain('Element is outside of the viewport'); - }); - it('should double click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => { - window.double = false; - const button = document.querySelector('button'); - button.addEventListener('dblclick', event => { - window.double = true; - }); - }); - const button = await page.$('button'); - await button.dblclick(); - expect(await page.evaluate('double')).toBe(true); - expect(await page.evaluate('result')).toBe('Clicked'); - }); -}); - -describe('ElementHandle.hover', function() { - it('should work', 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 work 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'); - }); -}); - -describe('ElementHandle.scrollIntoViewIfNeeded', function() { - 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'); - }); -}); - -describe('ElementHandle.fill', function() { - 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'); - }); -}); - -describe('ElementHandle.selectText', function() { - 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; - }); -}); - - -describe('ElementHandle convenience API', function() { - it('should have a nice preview', async({page, server}) => { - await page.goto(`${server.PREFIX}/dom.html`); - const outer = await page.$('#outer'); - const inner = await page.$('#inner'); - const check = await page.$('#check'); - const text = await inner.evaluateHandle(e => e.firstChild); - await page.evaluate(() => 1); // Give them a chance to calculate the preview. - expect(String(outer)).toBe('JSHandle@
'); - expect(String(inner)).toBe('JSHandle@
Text,↵more text
'); - expect(String(text)).toBe('JSHandle@#text=Text,↵more text'); - expect(String(check)).toBe('JSHandle@'); - }); - it('getAttribute should work', async({page, server}) => { - await page.goto(`${server.PREFIX}/dom.html`); - const handle = await page.$('#outer'); - expect(await handle.getAttribute('name')).toBe('value'); - expect(await handle.getAttribute('foo')).toBe(null); - expect(await page.getAttribute('#outer', 'name')).toBe('value'); - expect(await page.getAttribute('#outer', 'foo')).toBe(null); - }); - it('innerHTML should work', async({page, server}) => { - await page.goto(`${server.PREFIX}/dom.html`); - const handle = await page.$('#outer'); - expect(await handle.innerHTML()).toBe('
Text,\nmore text
'); - expect(await page.innerHTML('#outer')).toBe('
Text,\nmore text
'); - }); - it('innerText should work', async({page, server}) => { - await page.goto(`${server.PREFIX}/dom.html`); - const handle = await page.$('#inner'); - expect(await handle.innerText()).toBe('Text, more text'); - expect(await page.innerText('#inner')).toBe('Text, more text'); - }); - it('innerText should throw', async({page, server}) => { - await page.setContent(`text`); - const error1 = await page.innerText('svg').catch(e => e); - expect(error1.message).toContain('Not an HTMLElement'); - const handle = await page.$('svg'); - const error2 = await handle.innerText().catch(e => e); - expect(error2.message).toContain('Not an HTMLElement'); - }); - it('textContent should work', async({page, server}) => { - await page.goto(`${server.PREFIX}/dom.html`); - const handle = await page.$('#inner'); - expect(await handle.textContent()).toBe('Text,\nmore text'); - expect(await page.textContent('#inner')).toBe('Text,\nmore text'); - }); - it('textContent 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.textContent = 'modified'); - return result; - }, - queryAll(root, selector) { - const result = Array.from(root.querySelectorAll(selector)); - for (const e of result) - Promise.resolve().then(() => result.textContent = 'modified'); - return result; - } - }); - await utils.registerEngine(playwright, 'textContent', createDummySelector); - await page.setContent(`
Hello
`); - const tc = await page.textContent('textContent=div'); - expect(tc).toBe('Hello'); - expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('modified'); - }); - it('innerText 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.textContent = 'modified'); - return result; - }, - queryAll(root, selector) { - const result = Array.from(root.querySelectorAll(selector)); - for (const e of result) - Promise.resolve().then(() => result.textContent = 'modified'); - return result; - } - }); - await utils.registerEngine(playwright, 'innerText', createDummySelector); - await page.setContent(`
Hello
`); - const tc = await page.innerText('innerText=div'); - expect(tc).toBe('Hello'); - expect(await page.evaluate(() => document.querySelector('div').innerText)).toBe('modified'); - }); - it('innerHTML 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.textContent = 'modified'); - return result; - }, - queryAll(root, selector) { - const result = Array.from(root.querySelectorAll(selector)); - for (const e of result) - Promise.resolve().then(() => result.textContent = 'modified'); - return result; - } - }); - await utils.registerEngine(playwright, 'innerHTML', createDummySelector); - await page.setContent(`
Helloworld
`); - 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'); - }); -}); - -describe('ElementHandle.check', () => { - 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); - }); -}); - -describe('ElementHandle.selectOption', function() { - 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']); - }); -}); - -describe('ElementHandle.focus', function() { - 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); - }); -}); - -describe('ElementHandle.type', function() { - 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'); - }); -}); - -describe('ElementHandle.press', function() { - 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/environments.js b/test/environments.js deleted file mode 100644 index ca5cbf84e9..0000000000 --- a/test/environments.js +++ /dev/null @@ -1,282 +0,0 @@ -/** - * Copyright 2019 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 fs = require('fs'); -const path = require('path'); -const rm = require('rimraf').sync; -const childProcess = require('child_process'); -const {TestServer} = require('../utils/testserver/'); -const { DispatcherConnection } = require('../lib/rpc/server/dispatcher'); -const { Connection } = require('../lib/rpc/client/connection'); -const { Transport } = require('../lib/rpc/transport'); -const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher'); -const { setUseApiName } = require('../lib/progress'); - -class ServerEnvironment { - async beforeAll(state) { - const assetsPath = path.join(__dirname, 'assets'); - const cachedPath = path.join(__dirname, 'assets', 'cached'); - - const port = 8907 + state.parallelIndex * 2; - state.server = await TestServer.create(assetsPath, port); - state.server.enableHTTPCache(cachedPath); - state.server.PORT = port; - state.server.PREFIX = `http://localhost:${port}`; - state.server.CROSS_PROCESS_PREFIX = `http://127.0.0.1:${port}`; - state.server.EMPTY_PAGE = `http://localhost:${port}/empty.html`; - - const httpsPort = port + 1; - state.httpsServer = await TestServer.createHTTPS(assetsPath, httpsPort); - state.httpsServer.enableHTTPCache(cachedPath); - state.httpsServer.PORT = httpsPort; - state.httpsServer.PREFIX = `https://localhost:${httpsPort}`; - state.httpsServer.CROSS_PROCESS_PREFIX = `https://127.0.0.1:${httpsPort}`; - state.httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`; - } - - async afterAll({server, httpsServer}) { - await Promise.all([ - server.stop(), - httpsServer.stop(), - ]); - } - - async beforeEach(state) { - state.server.reset(); - state.httpsServer.reset(); - } -} - -class DefaultBrowserOptionsEnvironment { - constructor(defaultBrowserOptions, dumpLogOnFailure, playwrightPath) { - this._defaultBrowserOptions = defaultBrowserOptions; - this._dumpLogOnFailure = dumpLogOnFailure; - this._playwrightPath = playwrightPath; - this._loggerSymbol = Symbol('DefaultBrowserOptionsEnvironment.logger'); - } - - async beforeAll(state) { - state[this._loggerSymbol] = utils.createTestLogger(this._dumpLogOnFailure, null, 'extra'); - state.defaultBrowserOptions = { - ...this._defaultBrowserOptions, - logger: state[this._loggerSymbol], - }; - state.playwrightPath = this._playwrightPath; - } - - async beforeEach(state, testRun) { - state[this._loggerSymbol].setTestRun(testRun); - } - - async afterEach(state) { - state[this._loggerSymbol].setTestRun(null); - } -} - -// simulate globalSetup per browserType that happens only once regardless of TestWorker. -const hasBeenCleaned = new Set(); - -class GoldenEnvironment { - async beforeAll(state) { - const { OUTPUT_DIR, GOLDEN_DIR } = utils.testOptions(state.browserType); - if (!hasBeenCleaned.has(state.browserType)) { - hasBeenCleaned.add(state.browserType); - if (fs.existsSync(OUTPUT_DIR)) - rm(OUTPUT_DIR); - fs.mkdirSync(OUTPUT_DIR, { recursive: true }); - } - state.golden = goldenName => ({ goldenPath: GOLDEN_DIR, outputPath: OUTPUT_DIR, goldenName }); - } - - async afterAll(state) { - delete state.golden; - } - - async afterEach(state, testRun) { - if (state.browser && state.browser.contexts().length !== 0) { - if (testRun.ok()) - console.warn(`\nWARNING: test "${testRun.test().fullName()}" (${testRun.test().location()}) did not close all created contexts!\n`); - await Promise.all(state.browser.contexts().map(context => context.close())); - } - } -} - -class TraceTestEnvironment { - static enableForTest(test) { - test.setTimeout(100000000); - test.addEnvironment(new TraceTestEnvironment()); - } - - constructor() { - this._session = null; - } - - async beforeEach(state, testRun) { - const t = testRun.test(); - const inspector = require('inspector'); - const fs = require('fs'); - const util = require('util'); - const url = require('url'); - const readFileAsync = util.promisify(fs.readFile.bind(fs)); - this._session = new inspector.Session(); - this._session.connect(); - const postAsync = util.promisify(this._session.post.bind(this._session)); - await postAsync('Debugger.enable'); - const setBreakpointCommands = []; - const N = t.body().toString().split('\n').length; - const location = t.location(); - const lines = (await readFileAsync(location.filePath(), 'utf8')).split('\n'); - for (let line = 0; line < N; ++line) { - const lineNumber = line + location.lineNumber(); - setBreakpointCommands.push(postAsync('Debugger.setBreakpointByUrl', { - url: url.pathToFileURL(location.filePath()), - lineNumber, - condition: `console.log('${String(lineNumber + 1).padStart(6, ' ')} | ' + ${JSON.stringify(lines[lineNumber])})`, - }).catch(e => {})); - } - await Promise.all(setBreakpointCommands); - } - - async afterEach() { - this._session.disconnect(); - } -} - -class PlaywrightEnvironment { - constructor(playwright) { - this._playwright = playwright; - this.spawnedProcess = undefined; - this.onExit = undefined; - } - - name() { return 'Playwright'; }; - - async beforeAll(state) { - if (process.env.PWCHANNEL) { - setUseApiName(false); - const connection = new Connection(); - if (process.env.PWCHANNEL === 'wire') { - this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], { - stdio: 'pipe', - detached: true, - }); - this.spawnedProcess.unref(); - this.onExit = (exitCode, signal) => { - throw new Error(`Server closed with exitCode=${exitCode} signal=${signal}`); - }; - this.spawnedProcess.once('exit', this.onExit); - const transport = new Transport(this.spawnedProcess.stdin, this.spawnedProcess.stdout); - connection.onmessage = message => transport.send(JSON.stringify(message)); - transport.onmessage = message => connection.dispatch(JSON.parse(message)); - } else { - const dispatcherConnection = new DispatcherConnection(); - dispatcherConnection.onmessage = async message => { - setImmediate(() => connection.dispatch(message)); - }; - connection.onmessage = async message => { - const result = await dispatcherConnection.dispatch(message); - await new Promise(f => setImmediate(f)); - return result; - }; - new PlaywrightDispatcher(dispatcherConnection.rootDispatcher(), this._playwright); - state.toImpl = x => dispatcherConnection._dispatchers.get(x._guid)._object; - } - state.playwright = await connection.waitForObjectWithKnownName('Playwright'); - } else { - state.toImpl = x => x; - state.playwright = this._playwright; - } - } - - async afterAll(state) { - if (this.spawnedProcess) { - this.spawnedProcess.removeListener('exit', this.onExit); - this.spawnedProcess.stdin.destroy(); - this.spawnedProcess.stdout.destroy(); - this.spawnedProcess.stderr.destroy(); - } - delete state.playwright; - } -} - -class BrowserTypeEnvironment { - constructor(browserName) { - this._browserName = browserName; - } - - async beforeAll(state) { - state.browserType = state.playwright[this._browserName]; - } - - async afterAll(state) { - delete state.browserType; - } -} - -class BrowserEnvironment { - constructor(launchOptions, dumpLogOnFailure) { - this._launchOptions = launchOptions; - this._dumpLogOnFailure = dumpLogOnFailure; - this._loggerSymbol = Symbol('BrowserEnvironment.logger'); - } - - async beforeAll(state) { - state[this._loggerSymbol] = utils.createTestLogger(this._dumpLogOnFailure); - state.browser = await state.browserType.launch({ - ...this._launchOptions, - logger: state[this._loggerSymbol], - }); - } - - async afterAll(state) { - await state.browser.close(); - delete state.browser; - } - - async beforeEach(state, testRun) { - state[this._loggerSymbol].setTestRun(testRun); - } - - async afterEach(state, testRun) { - state[this._loggerSymbol].setTestRun(null); - } -} - -class PageEnvironment { - async beforeEach(state) { - state.context = await state.browser.newContext(); - state.page = await state.context.newPage(); - } - - async afterEach(state) { - await state.context.close(); - state.context = null; - state.page = null; - } -} - -module.exports = { - ServerEnvironment, - GoldenEnvironment, - TraceTestEnvironment, - DefaultBrowserOptionsEnvironment, - PlaywrightEnvironment, - BrowserTypeEnvironment, - BrowserEnvironment, - PageEnvironment, -}; diff --git a/test/eval-on-selector-all.spec.js b/test/eval-on-selector-all.spec.js new file mode 100644 index 0000000000..d6506cf8d9 --- /dev/null +++ b/test/eval-on-selector-all.spec.js @@ -0,0 +1,71 @@ +/** + * 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 with css selector', async({page, server}) => { + await page.setContent('
hello
beautiful
world!
'); + const divsCount = await page.$$eval('css=div', divs => divs.length); + expect(divsCount).toBe(3); +}); +it('should work with text selector', async({page, server}) => { + await page.setContent('
hello
beautiful
beautiful
world!
'); + const divsCount = await page.$$eval('text="beautiful"', divs => divs.length); + expect(divsCount).toBe(2); +}); +it('should work with xpath selector', async({page, server}) => { + await page.setContent('
hello
beautiful
world!
'); + const divsCount = await page.$$eval('xpath=/html/body/div', divs => divs.length); + expect(divsCount).toBe(3); +}); +it('should auto-detect css selector', async({page, server}) => { + await page.setContent('
hello
beautiful
world!
'); + const divsCount = await page.$$eval('div', divs => divs.length); + expect(divsCount).toBe(3); +}); +it('should support >> syntax', async({page, server}) => { + await page.setContent('
hello
beautiful
world!
Not this one'); + const spansCount = await page.$$eval('css=div >> css=span', spans => spans.length); + expect(spansCount).toBe(3); +}); +it('should support * capture', async({page, server}) => { + await page.setContent('
a
b
'); + expect(await page.$$eval('*css=div >> "b"', els => els.length)).toBe(1); + expect(await page.$$eval('section >> *css=div >> "b"', els => els.length)).toBe(1); + expect(await page.$$eval('section >> *', els => els.length)).toBe(4); + + await page.setContent('
aa
'); + expect(await page.$$eval('*css=div >> "a"', els => els.length)).toBe(1); + expect(await page.$$eval('section >> *css=div >> "a"', els => els.length)).toBe(1); + + await page.setContent('
a
a
a
'); + expect(await page.$$eval('*css=div >> "a"', els => els.length)).toBe(3); + expect(await page.$$eval('section >> *css=div >> "a"', els => els.length)).toBe(1); +}); +it('should support * capture when multiple paths match', async({page, server}) => { + await page.setContent('
'); + expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2); + await page.setContent('
'); + expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2); +}); +it('should return complex values', async({page, server}) => { + await page.setContent('
hello
beautiful
world!
'); + const texts = await page.$$eval('css=div', divs => divs.map(div => div.textContent)); + expect(texts).toEqual(['hello', 'beautiful', 'world!']); +}); diff --git a/test/eval-on-selector.spec.js b/test/eval-on-selector.spec.js new file mode 100644 index 0000000000..96a9b16f90 --- /dev/null +++ b/test/eval-on-selector.spec.js @@ -0,0 +1,204 @@ +/** + * 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 with css selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('css=section', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with id selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('id=testAttribute', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with data-test selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-test=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with data-testid selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-testid=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with data-test-id selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('data-test-id=foo', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with text selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('text="43543"', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with xpath selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('xpath=/html/body/section', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should work with text selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('text=43543', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should auto-detect css selector', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('section', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should auto-detect css selector with attributes', async({page, server}) => { + await page.setContent('
43543
'); + const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id); + expect(idAttribute).toBe('testAttribute'); +}); + +it('should auto-detect nested selectors', async({page, server}) => { + await page.setContent('
43543Hello
'); + const idAttribute = await page.$eval('div[foo=bar] > section >> "Hello" >> div', e => e.id); + expect(idAttribute).toBe('target'); +}); + +it('should accept arguments', async({page, server}) => { + await page.setContent('
hello
'); + const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!'); + expect(text).toBe('hello world!'); +}); + +it('should accept ElementHandles as arguments', async({page, server}) => { + await page.setContent('
hello
world
'); + const divHandle = await page.$('div'); + const text = await page.$eval('section', (e, div) => e.textContent + div.textContent, divHandle); + expect(text).toBe('hello world'); +}); + +it('should throw error if no element is found', async({page, server}) => { + let error = null; + await page.$eval('section', e => e.id).catch(e => error = e); + expect(error.message).toContain('failed to find element matching selector "section"'); +}); + +it('should support >> syntax', async({page, server}) => { + await page.setContent('
hello
'); + const text = await page.$eval('css=section >> css=div', (e, suffix) => e.textContent + suffix, ' world!'); + expect(text).toBe('hello world!'); +}); + +it('should support >> syntax with different engines', async({page, server}) => { + await page.setContent('
hello
'); + const text = await page.$eval('xpath=/html/body/section >> css=div >> text="hello"', (e, suffix) => e.textContent + suffix, ' world!'); + expect(text).toBe('hello world!'); +}); + +it('should support spaces with >> syntax', async({page, server}) => { + await page.goto(server.PREFIX + '/deep-shadow.html'); + const text = await page.$eval(' css = div >>css=div>>css = span ', e => e.textContent); + expect(text).toBe('Hello from root2'); +}); + +it('should not stop at first failure with >> syntax', async({page, server}) => { + await page.setContent('
Next
'); + const html = await page.$eval('button >> "Next"', e => e.outerHTML); + expect(html).toBe(''); +}); + +it('should support * capture', async({page, server}) => { + await page.setContent('
a
b
'); + expect(await page.$eval('*css=div >> "b"', e => e.outerHTML)).toBe('
b
'); + expect(await page.$eval('section >> *css=div >> "b"', e => e.outerHTML)).toBe('
b
'); + 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('
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(); - }); -}); - -describe('Frame Management', function() { - 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/geolocation.jest.js b/test/geolocation.jest.js deleted file mode 100644 index 10700ce538..0000000000 --- a/test/geolocation.jest.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright 2017 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; - -describe('Overrides.setGeolocation', function() { - it('should work', async({page, server, context}) => { - await context.grantPermissions(['geolocation']); - await page.goto(server.EMPTY_PAGE); - await context.setGeolocation({longitude: 10, latitude: 10}); - const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { - resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); - }))); - expect(geolocation).toEqual({ - latitude: 10, - longitude: 10 - }); - }); - it('should throw when invalid longitude', async({context}) => { - let error = null; - try { - await context.setGeolocation({longitude: 200, latitude: 10}); - } catch (e) { - error = e; - } - expect(error.message).toContain('geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.'); - }); - it('should isolate contexts', async({page, server, context, browser}) => { - await context.grantPermissions(['geolocation']); - await context.setGeolocation({longitude: 10, latitude: 10}); - await page.goto(server.EMPTY_PAGE); - - const context2 = await browser.newContext({ - permissions: ['geolocation'], - geolocation: {longitude: 20, latitude: 20} - }); - const page2 = await context2.newPage(); - await page2.goto(server.EMPTY_PAGE); - - const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { - resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); - }))); - expect(geolocation).toEqual({ - latitude: 10, - longitude: 10 - }); - - const geolocation2 = await page2.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { - resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); - }))); - expect(geolocation2).toEqual({ - latitude: 20, - longitude: 20 - }); - - await context2.close(); - }); - it('should throw with missing latitude', async({context}) => { - let error = null; - try { - await context.setGeolocation({longitude: 10}); - } catch (e) { - error = e; - } - expect(error.message).toContain('geolocation.latitude: expected number, got undefined'); - }); - it('should not modify passed default options object', async({browser}) => { - const geolocation = { longitude: 10, latitude: 10 }; - const options = { geolocation }; - const context = await browser.newContext(options); - await context.setGeolocation({ longitude: 20, latitude: 20 }); - expect(options.geolocation).toBe(geolocation); - await context.close(); - }); - it('should throw with missing longitude in default options', async({browser}) => { - let error = null; - try { - const context = await browser.newContext({ geolocation: {latitude: 10} }); - await context.close(); - } catch (e) { - error = e; - } - expect(error.message).toContain('geolocation.longitude: expected number, got undefined'); - }); - it('should use context options', async({browser, server}) => { - const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: ['geolocation'] }; - const context = await browser.newContext(options); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - - const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { - resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); - }))); - expect(geolocation).toEqual({ - latitude: 10, - longitude: 10 - }); - await context.close(); - }); - it('watchPosition should be notified', async({page, server, context}) => { - await context.grantPermissions(['geolocation']); - await page.goto(server.EMPTY_PAGE); - const messages = []; - page.on('console', message => messages.push(message.text())); - - await context.setGeolocation({latitude: 0, longitude: 0}); - await page.evaluate(() => { - navigator.geolocation.watchPosition(pos => { - const coords = pos.coords; - console.log(`lat=${coords.latitude} lng=${coords.longitude}`); - }, err => {}); - }); - await context.setGeolocation({latitude: 0, longitude: 10}); - await page.waitForEvent('console', message => message.text().includes('lat=0 lng=10')); - await context.setGeolocation({latitude: 20, longitude: 30}); - await page.waitForEvent('console', message => message.text().includes('lat=20 lng=30')); - await context.setGeolocation({latitude: 40, longitude: 50}); - await page.waitForEvent('console', message => message.text().includes('lat=40 lng=50')); - - const allMessages = messages.join('|'); - expect(allMessages).toContain('lat=0 lng=10'); - expect(allMessages).toContain('lat=20 lng=30'); - expect(allMessages).toContain('lat=40 lng=50'); - }); - it('should use context options for popup', async({page, context, server}) => { - await context.grantPermissions(['geolocation']); - await context.setGeolocation({ longitude: 10, latitude: 10 }); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/geolocation.html'), - ]); - await popup.waitForLoadState(); - const geolocation = await popup.evaluate(() => window.geolocationPromise); - expect(geolocation).toEqual({ longitude: 10, latitude: 10 }); - }); -}); diff --git a/test/geolocation.spec.js b/test/geolocation.spec.js new file mode 100644 index 0000000000..f598f68403 --- /dev/null +++ b/test/geolocation.spec.js @@ -0,0 +1,148 @@ +/** + * Copyright 2017 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 {FFOX, CHROMIUM, WEBKIT} = testOptions; + +it('should work', async({page, server, context}) => { + await context.grantPermissions(['geolocation']); + await page.goto(server.EMPTY_PAGE); + await context.setGeolocation({longitude: 10, latitude: 10}); + const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { + resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); + }))); + expect(geolocation).toEqual({ + latitude: 10, + longitude: 10 + }); +}); +it('should throw when invalid longitude', async({context}) => { + let error = null; + try { + await context.setGeolocation({longitude: 200, latitude: 10}); + } catch (e) { + error = e; + } + expect(error.message).toContain('geolocation.longitude: precondition -180 <= LONGITUDE <= 180 failed.'); +}); +it('should isolate contexts', async({page, server, context, browser}) => { + await context.grantPermissions(['geolocation']); + await context.setGeolocation({longitude: 10, latitude: 10}); + await page.goto(server.EMPTY_PAGE); + + const context2 = await browser.newContext({ + permissions: ['geolocation'], + geolocation: {longitude: 20, latitude: 20} + }); + const page2 = await context2.newPage(); + await page2.goto(server.EMPTY_PAGE); + + const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { + resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); + }))); + expect(geolocation).toEqual({ + latitude: 10, + longitude: 10 + }); + + const geolocation2 = await page2.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { + resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); + }))); + expect(geolocation2).toEqual({ + latitude: 20, + longitude: 20 + }); + + await context2.close(); +}); +it('should throw with missing latitude', async({context}) => { + let error = null; + try { + await context.setGeolocation({longitude: 10}); + } catch (e) { + error = e; + } + expect(error.message).toContain('geolocation.latitude: expected number, got undefined'); +}); +it('should not modify passed default options object', async({browser}) => { + const geolocation = { longitude: 10, latitude: 10 }; + const options = { geolocation }; + const context = await browser.newContext(options); + await context.setGeolocation({ longitude: 20, latitude: 20 }); + expect(options.geolocation).toBe(geolocation); + await context.close(); +}); +it('should throw with missing longitude in default options', async({browser}) => { + let error = null; + try { + const context = await browser.newContext({ geolocation: {latitude: 10} }); + await context.close(); + } catch (e) { + error = e; + } + expect(error.message).toContain('geolocation.longitude: expected number, got undefined'); +}); +it('should use context options', async({browser, server}) => { + const options = { geolocation: { longitude: 10, latitude: 10 }, permissions: ['geolocation'] }; + const context = await browser.newContext(options); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + + const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { + resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); + }))); + expect(geolocation).toEqual({ + latitude: 10, + longitude: 10 + }); + await context.close(); +}); +it('watchPosition should be notified', async({page, server, context}) => { + await context.grantPermissions(['geolocation']); + await page.goto(server.EMPTY_PAGE); + const messages = []; + page.on('console', message => messages.push(message.text())); + + await context.setGeolocation({latitude: 0, longitude: 0}); + await page.evaluate(() => { + navigator.geolocation.watchPosition(pos => { + const coords = pos.coords; + console.log(`lat=${coords.latitude} lng=${coords.longitude}`); + }, err => {}); + }); + await context.setGeolocation({latitude: 0, longitude: 10}); + await page.waitForEvent('console', message => message.text().includes('lat=0 lng=10')); + await context.setGeolocation({latitude: 20, longitude: 30}); + await page.waitForEvent('console', message => message.text().includes('lat=20 lng=30')); + await context.setGeolocation({latitude: 40, longitude: 50}); + await page.waitForEvent('console', message => message.text().includes('lat=40 lng=50')); + + const allMessages = messages.join('|'); + expect(allMessages).toContain('lat=0 lng=10'); + expect(allMessages).toContain('lat=20 lng=30'); + expect(allMessages).toContain('lat=40 lng=50'); +}); +it('should use context options for popup', async({page, context, server}) => { + await context.grantPermissions(['geolocation']); + await context.setGeolocation({ longitude: 10, latitude: 10 }); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/geolocation.html'), + ]); + await popup.waitForLoadState(); + const geolocation = await popup.evaluate(() => window.geolocationPromise); + expect(geolocation).toEqual({ longitude: 10, latitude: 10 }); +}); diff --git a/test/headful.jest.js b/test/headful.jest.js deleted file mode 100644 index 0f0c863174..0000000000 --- a/test/headful.jest.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright 2018 Google Inc. 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 utils = require('./utils'); -const { makeUserDataDir, removeUserDataDir } = utils; -const {FFOX, CHROMIUM, WEBKIT, WIN, MAC} = testOptions; - -describe('Headful', function() { - it('should have default url when launching browser', async ({browserType, defaultBrowserOptions}) => { - const userDataDir = await makeUserDataDir(); - const browserContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false }); - const urls = browserContext.pages().map(page => page.url()); - expect(urls).toEqual(['about:blank']); - await browserContext.close(); - await removeUserDataDir(userDataDir); - }); - it.fail(WIN && CHROMIUM).slow()('headless should be able to read cookies written by headful', async({browserType, defaultBrowserOptions, server}) => { - // see https://github.com/microsoft/playwright/issues/717 - const userDataDir = await makeUserDataDir(); - // Write a cookie in headful chrome - const headfulContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false}); - const headfulPage = await headfulContext.newPage(); - await headfulPage.goto(server.EMPTY_PAGE); - await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); - await headfulContext.close(); - // Read the cookie from headless chrome - const headlessContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: true}); - const headlessPage = await headlessContext.newPage(); - await headlessPage.goto(server.EMPTY_PAGE); - const cookie = await headlessPage.evaluate(() => document.cookie); - await headlessContext.close(); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - expect(cookie).toBe('foo=true'); - }); - it.slow()('should close browser with beforeunload page', async({browserType, defaultBrowserOptions, server}) => { - const userDataDir = await makeUserDataDir(); - const browserContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false}); - const page = await browserContext.newPage(); - await page.goto(server.PREFIX + '/beforeunload.html'); - // We have to interact with a page so that 'beforeunload' handlers - // fire. - await page.click('body'); - await browserContext.close(); - await removeUserDataDir(userDataDir); - }); - it('should not crash when creating second context', async ({browserType, defaultBrowserOptions, server}) => { - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - { - const browserContext = await browser.newContext(); - const page = await browserContext.newPage(); - await browserContext.close(); - } - { - const browserContext = await browser.newContext(); - const page = await browserContext.newPage(); - await browserContext.close(); - } - await browser.close(); - }); - it('should click background tab', async({browserType, defaultBrowserOptions, server}) => { - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - const page = await browser.newPage(); - await page.setContent(`empty.html`); - await page.click('a'); - await page.click('button'); - await browser.close(); - }); - it('should close browser after context menu was triggered', async({browserType, defaultBrowserOptions, server}) => { - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - const page = await browser.newPage(); - await page.goto(server.PREFIX + '/grid.html'); - await page.click('body', {button: 'right'}); - await browser.close(); - }); - it('should(not) block third party cookies', async({browserType, defaultBrowserOptions, server}) => { - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - const page = await browser.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(src => { - let fulfill; - const promise = new Promise(x => fulfill = x); - const iframe = document.createElement('iframe'); - document.body.appendChild(iframe); - iframe.onload = fulfill; - iframe.src = src; - return promise; - }, server.CROSS_PROCESS_PREFIX + '/grid.html'); - const documentCookie = await page.frames()[1].evaluate(() => { - document.cookie = 'username=John Doe'; - return document.cookie; - }); - await page.waitForTimeout(2000); - const allowsThirdParty = CHROMIUM || FFOX; - expect(documentCookie).toBe(allowsThirdParty ? 'username=John Doe' : ''); - const cookies = await page.context().cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); - if (allowsThirdParty) { - expect(cookies).toEqual([ - { - "domain": "127.0.0.1", - "expires": -1, - "httpOnly": false, - "name": "username", - "path": "/", - "sameSite": "None", - "secure": false, - "value": "John Doe" - } - ]); - } else { - expect(cookies).toEqual([]); - } - await browser.close(); - }); - it.fail(WEBKIT)('should not override viewport size when passed null', async function({browserType, defaultBrowserOptions, server}) { - // Our WebKit embedder does not respect window features. - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - const context = await browser.newContext({ viewport: null }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => { - const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0'); - win.resizeTo(500, 450); - }), - ]); - await popup.waitForLoadState(); - await popup.waitForFunction(() => window.outerWidth === 500 && window.outerHeight === 450); - await context.close(); - await browser.close(); - }); - it('Page.bringToFront should work', async ({browserType, defaultBrowserOptions}) => { - const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); - const page1 = await browser.newPage(); - await page1.setContent('Page1') - const page2 = await browser.newPage(); - await page2.setContent('Page2') - - await page1.bringToFront(); - expect(await page1.evaluate('document.visibilityState')).toBe('visible'); - expect(await page2.evaluate('document.visibilityState')).toBe('visible'); - - await page2.bringToFront(); - expect(await page1.evaluate('document.visibilityState')).toBe('visible'); - expect(await page2.evaluate('document.visibilityState')).toBe( - 'visible' - ); - await browser.close(); - }); -}); diff --git a/test/headful.spec.js b/test/headful.spec.js new file mode 100644 index 0000000000..8a2d3b06eb --- /dev/null +++ b/test/headful.spec.js @@ -0,0 +1,162 @@ +/** + * Copyright 2018 Google Inc. 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 utils = require('./utils'); +const { makeUserDataDir, removeUserDataDir } = utils; +const {FFOX, CHROMIUM, WEBKIT, WIN, MAC} = testOptions; + +it('should have default url when launching browser', async ({browserType, defaultBrowserOptions}) => { + const userDataDir = await makeUserDataDir(); + const browserContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false }); + const urls = browserContext.pages().map(page => page.url()); + expect(urls).toEqual(['about:blank']); + await browserContext.close(); + await removeUserDataDir(userDataDir); +}); +it.fail(WIN && CHROMIUM).slow()('headless should be able to read cookies written by headful', async({browserType, defaultBrowserOptions, server}) => { + // see https://github.com/microsoft/playwright/issues/717 + const userDataDir = await makeUserDataDir(); + // Write a cookie in headful chrome + const headfulContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false}); + const headfulPage = await headfulContext.newPage(); + await headfulPage.goto(server.EMPTY_PAGE); + await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); + await headfulContext.close(); + // Read the cookie from headless chrome + const headlessContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: true}); + const headlessPage = await headlessContext.newPage(); + await headlessPage.goto(server.EMPTY_PAGE); + const cookie = await headlessPage.evaluate(() => document.cookie); + await headlessContext.close(); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await removeUserDataDir(userDataDir); + expect(cookie).toBe('foo=true'); +}); +it.slow()('should close browser with beforeunload page', async({browserType, defaultBrowserOptions, server}) => { + const userDataDir = await makeUserDataDir(); + const browserContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false}); + const page = await browserContext.newPage(); + await page.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers + // fire. + await page.click('body'); + await browserContext.close(); + await removeUserDataDir(userDataDir); +}); +it('should not crash when creating second context', async ({browserType, defaultBrowserOptions, server}) => { + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + { + const browserContext = await browser.newContext(); + const page = await browserContext.newPage(); + await browserContext.close(); + } + { + const browserContext = await browser.newContext(); + const page = await browserContext.newPage(); + await browserContext.close(); + } + await browser.close(); +}); +it('should click background tab', async({browserType, defaultBrowserOptions, server}) => { + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + const page = await browser.newPage(); + await page.setContent(`empty.html`); + await page.click('a'); + await page.click('button'); + await browser.close(); +}); +it('should close browser after context menu was triggered', async({browserType, defaultBrowserOptions, server}) => { + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + const page = await browser.newPage(); + await page.goto(server.PREFIX + '/grid.html'); + await page.click('body', {button: 'right'}); + await browser.close(); +}); +it('should(not) block third party cookies', async({browserType, defaultBrowserOptions, server}) => { + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + const page = await browser.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }, server.CROSS_PROCESS_PREFIX + '/grid.html'); + const documentCookie = await page.frames()[1].evaluate(() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }); + await page.waitForTimeout(2000); + const allowsThirdParty = CHROMIUM || FFOX; + expect(documentCookie).toBe(allowsThirdParty ? 'username=John Doe' : ''); + const cookies = await page.context().cookies(server.CROSS_PROCESS_PREFIX + '/grid.html'); + if (allowsThirdParty) { + expect(cookies).toEqual([ + { + "domain": "127.0.0.1", + "expires": -1, + "httpOnly": false, + "name": "username", + "path": "/", + "sameSite": "None", + "secure": false, + "value": "John Doe" + } + ]); + } else { + expect(cookies).toEqual([]); + } + await browser.close(); +}); +it.fail(WEBKIT)('should not override viewport size when passed null', async function({browserType, defaultBrowserOptions, server}) { + // Our WebKit embedder does not respect window features. + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + const context = await browser.newContext({ viewport: null }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0'); + win.resizeTo(500, 450); + }), + ]); + await popup.waitForLoadState(); + await popup.waitForFunction(() => window.outerWidth === 500 && window.outerHeight === 450); + await context.close(); + await browser.close(); +}); +it('Page.bringToFront should work', async ({browserType, defaultBrowserOptions}) => { + const browser = await browserType.launch({...defaultBrowserOptions, headless: false }); + const page1 = await browser.newPage(); + await page1.setContent('Page1') + const page2 = await browser.newPage(); + await page2.setContent('Page2') + + await page1.bringToFront(); + expect(await page1.evaluate('document.visibilityState')).toBe('visible'); + expect(await page2.evaluate('document.visibilityState')).toBe('visible'); + + await page2.bringToFront(); + expect(await page1.evaluate('document.visibilityState')).toBe('visible'); + expect(await page2.evaluate('document.visibilityState')).toBe( + 'visible' + ); + await browser.close(); +}); diff --git a/test/ignorehttpserrors.jest.js b/test/ignorehttpserrors.jest.js deleted file mode 100644 index 3c83091844..0000000000 --- a/test/ignorehttpserrors.jest.js +++ /dev/null @@ -1,91 +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 {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; - -describe('ignoreHTTPSErrors', function() { - it('should work', async({browser, httpsServer}) => { - let error = null; - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expect(error).toBe(null); - expect(response.ok()).toBe(true); - await context.close(); - }); - it('should isolate contexts', async({browser, httpsServer}) => { - { - let error = null; - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expect(error).toBe(null); - expect(response.ok()).toBe(true); - await context.close(); - } - { - let error = null; - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expect(error).not.toBe(null); - await context.close(); - } - }); - it('should work with mixed content', async({browser, server, httpsServer}) => { - httpsServer.setRoute('/mixedcontent.html', (req, res) => { - res.end(``); - }); - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'domcontentloaded'}); - expect(page.frames().length).toBe(2); - // Make sure blocked iframe has functional execution context - // @see https://github.com/GoogleChrome/puppeteer/issues/2709 - expect(await page.frames()[0].evaluate('1 + 2')).toBe(3); - expect(await page.frames()[1].evaluate('2 + 3')).toBe(5); - await context.close(); - }); - it('should work with WebSocket', async({browser, httpsServer}) => { - const context = await browser.newContext({ ignoreHTTPSErrors: true }); - const page = await context.newPage(); - const value = await page.evaluate(endpoint => { - let cb; - const result = new Promise(f => cb = f); - const ws = new WebSocket(endpoint); - ws.addEventListener('message', data => { ws.close(); cb(data.data); }); - ws.addEventListener('error', error => cb('Error')); - return result; - }, httpsServer.PREFIX.replace(/https/, 'wss') + '/ws'); - expect(value).toBe('incoming'); - await context.close(); - }); - it('should fail with WebSocket if not ignored', async({browser, httpsServer}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const value = await page.evaluate(endpoint => { - let cb; - const result = new Promise(f => cb = f); - const ws = new WebSocket(endpoint); - ws.addEventListener('message', data => { ws.close(); cb(data.data); }); - ws.addEventListener('error', error => cb('Error')); - return result; - }, httpsServer.PREFIX.replace(/https/, 'wss') + '/ws'); - expect(value).toBe('Error'); - await context.close(); - }); -}); diff --git a/test/ignorehttpserrors.spec.js b/test/ignorehttpserrors.spec.js new file mode 100644 index 0000000000..9e0bf70a47 --- /dev/null +++ b/test/ignorehttpserrors.spec.js @@ -0,0 +1,89 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; + +it('should work', async({browser, httpsServer}) => { + let error = null; + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expect(error).toBe(null); + expect(response.ok()).toBe(true); + await context.close(); +}); +it('should isolate contexts', async({browser, httpsServer}) => { + { + let error = null; + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expect(error).toBe(null); + expect(response.ok()).toBe(true); + await context.close(); + } + { + let error = null; + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expect(error).not.toBe(null); + await context.close(); + } +}); +it('should work with mixed content', async({browser, server, httpsServer}) => { + httpsServer.setRoute('/mixedcontent.html', (req, res) => { + res.end(``); + }); + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'domcontentloaded'}); + expect(page.frames().length).toBe(2); + // Make sure blocked iframe has functional execution context + // @see https://github.com/GoogleChrome/puppeteer/issues/2709 + expect(await page.frames()[0].evaluate('1 + 2')).toBe(3); + expect(await page.frames()[1].evaluate('2 + 3')).toBe(5); + await context.close(); +}); +it('should work with WebSocket', async({browser, httpsServer}) => { + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + const value = await page.evaluate(endpoint => { + let cb; + const result = new Promise(f => cb = f); + const ws = new WebSocket(endpoint); + ws.addEventListener('message', data => { ws.close(); cb(data.data); }); + ws.addEventListener('error', error => cb('Error')); + return result; + }, httpsServer.PREFIX.replace(/https/, 'wss') + '/ws'); + expect(value).toBe('incoming'); + await context.close(); +}); +it('should fail with WebSocket if not ignored', async({browser, httpsServer}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const value = await page.evaluate(endpoint => { + let cb; + const result = new Promise(f => cb = f); + const ws = new WebSocket(endpoint); + ws.addEventListener('message', data => { ws.close(); cb(data.data); }); + ws.addEventListener('error', error => cb('Error')); + return result; + }, httpsServer.PREFIX.replace(/https/, 'wss') + '/ws'); + expect(value).toBe('Error'); + await context.close(); +}); diff --git a/test/keyboard.jest.js b/test/keyboard.jest.js deleted file mode 100644 index 7f67b50545..0000000000 --- a/test/keyboard.jest.js +++ /dev/null @@ -1,410 +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, WEBKIT, CHROMIUM, MAC} = testOptions; - -describe('Keyboard', function() { - it('should type into a textarea', async ({page, server}) => { - await page.evaluate(() => { - const textarea = document.createElement('textarea'); - document.body.appendChild(textarea); - textarea.focus(); - }); - const text = 'Hello world. I am the text that was typed!'; - await page.keyboard.type(text); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe(text); - }); - it('should move with the arrow keys', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.type('textarea', 'Hello World!'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); - for (let i = 0; i < 'World!'.length; i++) - await page.keyboard.press('ArrowLeft'); - await page.keyboard.type('inserted '); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!'); - await page.keyboard.down('Shift'); - for (let i = 0; i < 'inserted '.length; i++) - await page.keyboard.press('ArrowLeft'); - await page.keyboard.up('Shift'); - await page.keyboard.press('Backspace'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); - }); - it('should send a character with ElementHandle.press', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.press('a'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); - - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); - - await textarea.press('b'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); - }); - it('should send a character with sendCharacter', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - await page.keyboard.insertText('嗨'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨'); - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); - await page.keyboard.insertText('a'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); - }); - it('insertText should only emit input event', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - page.on('console', m => console.log(m.text())); - const events = await page.evaluateHandle(() => { - const events = []; - document.addEventListener('keydown', e => events.push(e.type)); - document.addEventListener('keyup', e => events.push(e.type)); - document.addEventListener('keypress', e => events.push(e.type)); - document.addEventListener('input', e => events.push(e.type)); - return events; - }); - await page.keyboard.insertText('hello world'); - expect(await events.jsonValue()).toEqual(['input']); - }); - it.fail(FFOX && MAC)('should report shiftKey', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17}; - for (const modifierKey in codeForKey) { - await keyboard.down(modifierKey); - expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']'); - await keyboard.down('!'); - // Shift+! will generate a keypress - if (modifierKey === 'Shift') - expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 [' + modifierKey + ']'); - else - expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); - - await keyboard.up('!'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); - await keyboard.up(modifierKey); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []'); - } - }); - it('should report multiple modifiers', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - await keyboard.down('Control'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]'); - await keyboard.down('Alt'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: Alt AltLeft 18 [Alt Control]'); - await keyboard.down(';'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Alt Control]'); - await keyboard.up(';'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Alt Control]'); - await keyboard.up('Control'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Alt]'); - await keyboard.up('Alt'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: Alt AltLeft 18 []'); - }); - it('should send proper codes while typing', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.type('!'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: ! Digit1 49 []', - 'Keypress: ! Digit1 33 33 []', - 'Keyup: ! Digit1 49 []'].join('\n')); - await page.keyboard.type('^'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: ^ Digit6 54 []', - 'Keypress: ^ Digit6 94 94 []', - 'Keyup: ^ Digit6 54 []'].join('\n')); - }); - it('should send proper codes while typing with shift', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - await keyboard.down('Shift'); - await page.keyboard.type('~'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode - 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode - 'Keyup: ~ Backquote 192 [Shift]'].join('\n')); - await keyboard.up('Shift'); - }); - it('should not type canceled events', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - await page.evaluate(() => { - window.addEventListener('keydown', event => { - event.stopPropagation(); - event.stopImmediatePropagation(); - if (event.key === 'l') - event.preventDefault(); - if (event.key === 'o') - event.preventDefault(); - }, false); - }); - await page.keyboard.type('Hello World!'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe('He Wrd!'); - }); - it('should press plus', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.press('+'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: + Equal 187 []', // 192 is ` keyCode - 'Keypress: + Equal 43 43 []', // 126 is ~ charCode - 'Keyup: + Equal 187 []'].join('\n')); - }); - it('should press shift plus', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.press('Shift++'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: + Equal 187 [Shift]', // 192 is ` keyCode - 'Keypress: + Equal 43 43 [Shift]', // 126 is ~ charCode - 'Keyup: + Equal 187 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); - }); - it('should support plus-separated modifiers', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.press('Shift+~'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode - 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode - 'Keyup: ~ Backquote 192 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); - }); - it('should support multiple plus-separated modifiers', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.press('Control+Shift+~'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Control ControlLeft 17 [Control]', - 'Keydown: Shift ShiftLeft 16 [Control Shift]', - 'Keydown: ~ Backquote 192 [Control Shift]', // 192 is ` keyCode - 'Keyup: ~ Backquote 192 [Control Shift]', - 'Keyup: Shift ShiftLeft 16 [Control]', - 'Keyup: Control ControlLeft 17 []'].join('\n')); - }); - it('should shift raw codes', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.press('Shift+Digit3'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: # Digit3 51 [Shift]', // 51 is # keyCode - 'Keypress: # Digit3 35 35 [Shift]', // 35 is # charCode - 'Keyup: # Digit3 51 [Shift]', - 'Keyup: Shift ShiftLeft 16 []'].join('\n')); - }); - it('should specify repeat property', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const lastEvent = await captureLastKeydown(page); - await page.keyboard.down('a'); - expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); - await page.keyboard.press('a'); - expect(await lastEvent.evaluate(e => e.repeat)).toBe(true); - - await page.keyboard.down('b'); - expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); - await page.keyboard.down('b'); - expect(await lastEvent.evaluate(e => e.repeat)).toBe(true); - - await page.keyboard.up('a'); - await page.keyboard.down('a'); - expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); - }); - it('should type all kinds of characters', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const text = 'This text goes onto two lines.\nThis character is 嗨.'; - await page.keyboard.type(text); - expect(await page.$eval('textarea', t => t.value)).toBe(text); - }); - it('should specify location', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const lastEvent = await captureLastKeydown(page); - const textarea = await page.$('textarea'); - - await textarea.press('Digit5'); - expect(await lastEvent.evaluate(e => e.location)).toBe(0); - - await textarea.press('ControlLeft'); - expect(await lastEvent.evaluate(e => e.location)).toBe(1); - - await textarea.press('ControlRight'); - expect(await lastEvent.evaluate(e => e.location)).toBe(2); - - await textarea.press('NumpadSubtract'); - expect(await lastEvent.evaluate(e => e.location)).toBe(3); - }); - it('should press Enter', async ({page, server}) => { - await page.setContent(''); - await page.focus('textarea'); - const lastEventHandle = await captureLastKeydown(page); - await testEnterKey('Enter', 'Enter', 'Enter'); - await testEnterKey('NumpadEnter', 'Enter', 'NumpadEnter'); - await testEnterKey('\n', 'Enter', 'Enter'); - await testEnterKey('\r', 'Enter', 'Enter'); - - async function testEnterKey(key, expectedKey, expectedCode) { - await page.keyboard.press(key); - const lastEvent = await lastEventHandle.jsonValue(); - expect(lastEvent.key).toBe(expectedKey, `${JSON.stringify(key)} had the wrong key: ${lastEvent.key}`); - expect(lastEvent.code).toBe(expectedCode, `${JSON.stringify(key)} had the wrong code: ${lastEvent.code}`); - const value = await page.$eval('textarea', t => t.value); - expect(value).toBe('\n', `${JSON.stringify(key)} failed to create a newline: ${JSON.stringify(value)}`); - await page.$eval('textarea', t => t.value = ''); - } - }); - it('should throw on unknown keys', async ({page, server}) => { - let error = await page.keyboard.press('NotARealKey').catch(e => e); - expect(error.message).toBe('Unknown key: "NotARealKey"'); - - error = await page.keyboard.press('ё').catch(e => e); - expect(error && error.message).toBe('Unknown key: "ё"'); - - error = await page.keyboard.press('😊').catch(e => e); - expect(error && error.message).toBe('Unknown key: "😊"'); - }); - it('should type emoji', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.type('textarea', '👹 Tokyo street Japan 🇯🇵'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵'); - }); - it('should type emoji into an iframe', async ({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html'); - const frame = page.frames()[1]; - const textarea = await frame.$('textarea'); - await textarea.type('👹 Tokyo street Japan 🇯🇵'); - expect(await frame.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵'); - }); - it('should handle selectAll', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.type('some text'); - const modifier = MAC ? 'Meta' : 'Control'; - await page.keyboard.down(modifier); - await page.keyboard.press('a'); - await page.keyboard.up(modifier); - await page.keyboard.press('Backspace'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe(''); - }); - it('should be able to prevent selectAll', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.type('some text'); - await page.$eval('textarea', textarea => { - textarea.addEventListener('keydown', event => { - if (event.key === 'a' && (event.metaKey || event.ctrlKey)) - event.preventDefault(); - }, false); - }); - const modifier = MAC ? 'Meta' : 'Control'; - await page.keyboard.down(modifier); - await page.keyboard.press('a'); - await page.keyboard.up(modifier); - await page.keyboard.press('Backspace'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some tex'); - }); - it.skip(!MAC)('should support MacOS shortcuts', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.type('some text'); - // select one word backwards - await page.keyboard.press('Shift+Control+Alt+KeyB'); - await page.keyboard.press('Backspace'); - expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some '); - }); - it('should press the meta key', async ({page}) => { - const lastEvent = await captureLastKeydown(page); - await page.keyboard.press('Meta'); - const {key, code, metaKey} = await lastEvent.jsonValue(); - if (FFOX && !MAC) - expect(key).toBe('OS'); - else - expect(key).toBe('Meta'); - - if (FFOX) - expect(code).toBe('OSLeft'); - else - expect(code).toBe('MetaLeft'); - - if (FFOX && !MAC) - expect(metaKey).toBe(false); - else - expect(metaKey).toBe(true); - - }); - it('should work after a cross origin navigation', async ({page, server}) => { - await page.goto(server.PREFIX + '/empty.html'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - const lastEvent = await captureLastKeydown(page); - await page.keyboard.press('a'); - expect(await lastEvent.evaluate(l => l.key)).toBe('a'); - }); - - // event.keyIdentifier has been removed from all browsers except WebKit - it.skip(!WEBKIT)('should expose keyIdentifier in webkit', async ({page, server}) => { - const lastEvent = await captureLastKeydown(page); - const keyMap = { - 'ArrowUp': 'Up', - 'ArrowDown': 'Down', - 'ArrowLeft': 'Left', - 'ArrowRight': 'Right', - 'Backspace': 'U+0008', - 'Tab': 'U+0009', - 'Delete': 'U+007F', - 'a': 'U+0041', - 'b': 'U+0042', - 'F12': 'F12', - }; - for (const [key, keyIdentifier] of Object.entries(keyMap)) { - await page.keyboard.press(key); - expect(await lastEvent.evaluate(e => e.keyIdentifier)).toBe(keyIdentifier); - } - }); - it('should scroll with PageDown', async ({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - // A click is required for WebKit to send the event into the body. - await page.click('body'); - await page.keyboard.press('PageDown'); - // We can't wait for the scroll to finish, so just wait for it to start. - await page.waitForFunction(() => scrollY > 0); - }); -}); - -async function captureLastKeydown(page) { - const lastEvent = await page.evaluateHandle(() => { - const lastEvent = { - repeat: false, - location: -1, - code: '', - key: '', - metaKey: false, - keyIdentifier: 'unsupported' - }; - document.addEventListener('keydown', e => { - lastEvent.repeat = e.repeat; - lastEvent.location = e.location; - lastEvent.key = e.key; - lastEvent.code = e.code; - lastEvent.metaKey = e.metaKey; - // keyIdentifier only exists in WebKit, and isn't in TypeScript's lib. - lastEvent.keyIdentifier = 'keyIdentifier' in e && e.keyIdentifier; - }, true); - return lastEvent; - }); - return lastEvent; -} diff --git a/test/keyboard.spec.js b/test/keyboard.spec.js new file mode 100644 index 0000000000..b2063c42ff --- /dev/null +++ b/test/keyboard.spec.js @@ -0,0 +1,408 @@ +/** + * 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, WEBKIT, CHROMIUM, MAC} = testOptions; + +it('should type into a textarea', async ({page, server}) => { + await page.evaluate(() => { + const textarea = document.createElement('textarea'); + document.body.appendChild(textarea); + textarea.focus(); + }); + const text = 'Hello world. I am the text that was typed!'; + await page.keyboard.type(text); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe(text); +}); +it('should move with the arrow keys', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.type('textarea', 'Hello World!'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); + for (let i = 0; i < 'World!'.length; i++) + await page.keyboard.press('ArrowLeft'); + await page.keyboard.type('inserted '); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!'); + await page.keyboard.down('Shift'); + for (let i = 0; i < 'inserted '.length; i++) + await page.keyboard.press('ArrowLeft'); + await page.keyboard.up('Shift'); + await page.keyboard.press('Backspace'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); +}); +it('should send a character with ElementHandle.press', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.press('a'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); + + await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + + await textarea.press('b'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('a'); +}); +it('should send a character with sendCharacter', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + await page.keyboard.insertText('嗨'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨'); + await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + await page.keyboard.insertText('a'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); +}); +it('insertText should only emit input event', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + page.on('console', m => console.log(m.text())); + const events = await page.evaluateHandle(() => { + const events = []; + document.addEventListener('keydown', e => events.push(e.type)); + document.addEventListener('keyup', e => events.push(e.type)); + document.addEventListener('keypress', e => events.push(e.type)); + document.addEventListener('input', e => events.push(e.type)); + return events; + }); + await page.keyboard.insertText('hello world'); + expect(await events.jsonValue()).toEqual(['input']); +}); +it.fail(FFOX && MAC)('should report shiftKey', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + const codeForKey = {'Shift': 16, 'Alt': 18, 'Control': 17}; + for (const modifierKey in codeForKey) { + await keyboard.down(modifierKey); + expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']'); + await keyboard.down('!'); + // Shift+! will generate a keypress + if (modifierKey === 'Shift') + expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 [' + modifierKey + ']'); + else + expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); + + await keyboard.up('!'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); + await keyboard.up(modifierKey); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []'); + } +}); +it('should report multiple modifiers', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + await keyboard.down('Control'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]'); + await keyboard.down('Alt'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: Alt AltLeft 18 [Alt Control]'); + await keyboard.down(';'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Alt Control]'); + await keyboard.up(';'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Alt Control]'); + await keyboard.up('Control'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Alt]'); + await keyboard.up('Alt'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: Alt AltLeft 18 []'); +}); +it('should send proper codes while typing', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.type('!'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: ! Digit1 49 []', + 'Keypress: ! Digit1 33 33 []', + 'Keyup: ! Digit1 49 []'].join('\n')); + await page.keyboard.type('^'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: ^ Digit6 54 []', + 'Keypress: ^ Digit6 94 94 []', + 'Keyup: ^ Digit6 54 []'].join('\n')); +}); +it('should send proper codes while typing with shift', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + await keyboard.down('Shift'); + await page.keyboard.type('~'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Shift ShiftLeft 16 [Shift]', + 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode + 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode + 'Keyup: ~ Backquote 192 [Shift]'].join('\n')); + await keyboard.up('Shift'); +}); +it('should not type canceled events', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + await page.evaluate(() => { + window.addEventListener('keydown', event => { + event.stopPropagation(); + event.stopImmediatePropagation(); + if (event.key === 'l') + event.preventDefault(); + if (event.key === 'o') + event.preventDefault(); + }, false); + }); + await page.keyboard.type('Hello World!'); + expect(await page.$eval('textarea', textarea => textarea.value)).toBe('He Wrd!'); +}); +it('should press plus', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.press('+'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: + Equal 187 []', // 192 is ` keyCode + 'Keypress: + Equal 43 43 []', // 126 is ~ charCode + 'Keyup: + Equal 187 []'].join('\n')); +}); +it('should press shift plus', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.press('Shift++'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Shift ShiftLeft 16 [Shift]', + 'Keydown: + Equal 187 [Shift]', // 192 is ` keyCode + 'Keypress: + Equal 43 43 [Shift]', // 126 is ~ charCode + 'Keyup: + Equal 187 [Shift]', + 'Keyup: Shift ShiftLeft 16 []'].join('\n')); +}); +it('should support plus-separated modifiers', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.press('Shift+~'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Shift ShiftLeft 16 [Shift]', + 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode + 'Keypress: ~ Backquote 126 126 [Shift]', // 126 is ~ charCode + 'Keyup: ~ Backquote 192 [Shift]', + 'Keyup: Shift ShiftLeft 16 []'].join('\n')); +}); +it('should support multiple plus-separated modifiers', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.press('Control+Shift+~'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Control ControlLeft 17 [Control]', + 'Keydown: Shift ShiftLeft 16 [Control Shift]', + 'Keydown: ~ Backquote 192 [Control Shift]', // 192 is ` keyCode + 'Keyup: ~ Backquote 192 [Control Shift]', + 'Keyup: Shift ShiftLeft 16 [Control]', + 'Keyup: Control ControlLeft 17 []'].join('\n')); +}); +it('should shift raw codes', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.press('Shift+Digit3'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Shift ShiftLeft 16 [Shift]', + 'Keydown: # Digit3 51 [Shift]', // 51 is # keyCode + 'Keypress: # Digit3 35 35 [Shift]', // 35 is # charCode + 'Keyup: # Digit3 51 [Shift]', + 'Keyup: Shift ShiftLeft 16 []'].join('\n')); +}); +it('should specify repeat property', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const lastEvent = await captureLastKeydown(page); + await page.keyboard.down('a'); + expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); + await page.keyboard.press('a'); + expect(await lastEvent.evaluate(e => e.repeat)).toBe(true); + + await page.keyboard.down('b'); + expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); + await page.keyboard.down('b'); + expect(await lastEvent.evaluate(e => e.repeat)).toBe(true); + + await page.keyboard.up('a'); + await page.keyboard.down('a'); + expect(await lastEvent.evaluate(e => e.repeat)).toBe(false); +}); +it('should type all kinds of characters', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const text = 'This text goes onto two lines.\nThis character is 嗨.'; + await page.keyboard.type(text); + expect(await page.$eval('textarea', t => t.value)).toBe(text); +}); +it('should specify location', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const lastEvent = await captureLastKeydown(page); + const textarea = await page.$('textarea'); + + await textarea.press('Digit5'); + expect(await lastEvent.evaluate(e => e.location)).toBe(0); + + await textarea.press('ControlLeft'); + expect(await lastEvent.evaluate(e => e.location)).toBe(1); + + await textarea.press('ControlRight'); + expect(await lastEvent.evaluate(e => e.location)).toBe(2); + + await textarea.press('NumpadSubtract'); + expect(await lastEvent.evaluate(e => e.location)).toBe(3); +}); +it('should press Enter', async ({page, server}) => { + await page.setContent(''); + await page.focus('textarea'); + const lastEventHandle = await captureLastKeydown(page); + await testEnterKey('Enter', 'Enter', 'Enter'); + await testEnterKey('NumpadEnter', 'Enter', 'NumpadEnter'); + await testEnterKey('\n', 'Enter', 'Enter'); + await testEnterKey('\r', 'Enter', 'Enter'); + + async function testEnterKey(key, expectedKey, expectedCode) { + await page.keyboard.press(key); + const lastEvent = await lastEventHandle.jsonValue(); + expect(lastEvent.key).toBe(expectedKey, `${JSON.stringify(key)} had the wrong key: ${lastEvent.key}`); + expect(lastEvent.code).toBe(expectedCode, `${JSON.stringify(key)} had the wrong code: ${lastEvent.code}`); + const value = await page.$eval('textarea', t => t.value); + expect(value).toBe('\n', `${JSON.stringify(key)} failed to create a newline: ${JSON.stringify(value)}`); + await page.$eval('textarea', t => t.value = ''); + } +}); +it('should throw on unknown keys', async ({page, server}) => { + let error = await page.keyboard.press('NotARealKey').catch(e => e); + expect(error.message).toBe('Unknown key: "NotARealKey"'); + + error = await page.keyboard.press('ё').catch(e => e); + expect(error && error.message).toBe('Unknown key: "ё"'); + + error = await page.keyboard.press('😊').catch(e => e); + expect(error && error.message).toBe('Unknown key: "😊"'); +}); +it('should type emoji', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.type('textarea', '👹 Tokyo street Japan 🇯🇵'); + expect(await page.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵'); +}); +it('should type emoji into an iframe', async ({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html'); + const frame = page.frames()[1]; + const textarea = await frame.$('textarea'); + await textarea.type('👹 Tokyo street Japan 🇯🇵'); + expect(await frame.$eval('textarea', textarea => textarea.value)).toBe('👹 Tokyo street Japan 🇯🇵'); +}); +it('should handle selectAll', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.type('some text'); + const modifier = MAC ? 'Meta' : 'Control'; + await page.keyboard.down(modifier); + await page.keyboard.press('a'); + await page.keyboard.up(modifier); + await page.keyboard.press('Backspace'); + expect(await page.$eval('textarea', textarea => textarea.value)).toBe(''); +}); +it('should be able to prevent selectAll', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.type('some text'); + await page.$eval('textarea', textarea => { + textarea.addEventListener('keydown', event => { + if (event.key === 'a' && (event.metaKey || event.ctrlKey)) + event.preventDefault(); + }, false); + }); + const modifier = MAC ? 'Meta' : 'Control'; + await page.keyboard.down(modifier); + await page.keyboard.press('a'); + await page.keyboard.up(modifier); + await page.keyboard.press('Backspace'); + expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some tex'); +}); +it.skip(!MAC)('should support MacOS shortcuts', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.type('some text'); + // select one word backwards + await page.keyboard.press('Shift+Control+Alt+KeyB'); + await page.keyboard.press('Backspace'); + expect(await page.$eval('textarea', textarea => textarea.value)).toBe('some '); +}); +it('should press the meta key', async ({page}) => { + const lastEvent = await captureLastKeydown(page); + await page.keyboard.press('Meta'); + const {key, code, metaKey} = await lastEvent.jsonValue(); + if (FFOX && !MAC) + expect(key).toBe('OS'); + else + expect(key).toBe('Meta'); + + if (FFOX) + expect(code).toBe('OSLeft'); + else + expect(code).toBe('MetaLeft'); + + if (FFOX && !MAC) + expect(metaKey).toBe(false); + else + expect(metaKey).toBe(true); + +}); +it('should work after a cross origin navigation', async ({page, server}) => { + await page.goto(server.PREFIX + '/empty.html'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + const lastEvent = await captureLastKeydown(page); + await page.keyboard.press('a'); + expect(await lastEvent.evaluate(l => l.key)).toBe('a'); +}); + +// event.keyIdentifier has been removed from all browsers except WebKit +it.skip(!WEBKIT)('should expose keyIdentifier in webkit', async ({page, server}) => { + const lastEvent = await captureLastKeydown(page); + const keyMap = { + 'ArrowUp': 'Up', + 'ArrowDown': 'Down', + 'ArrowLeft': 'Left', + 'ArrowRight': 'Right', + 'Backspace': 'U+0008', + 'Tab': 'U+0009', + 'Delete': 'U+007F', + 'a': 'U+0041', + 'b': 'U+0042', + 'F12': 'F12', + }; + for (const [key, keyIdentifier] of Object.entries(keyMap)) { + await page.keyboard.press(key); + expect(await lastEvent.evaluate(e => e.keyIdentifier)).toBe(keyIdentifier); + } +}); +it('should scroll with PageDown', async ({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + // A click is required for WebKit to send the event into the body. + await page.click('body'); + await page.keyboard.press('PageDown'); + // We can't wait for the scroll to finish, so just wait for it to start. + await page.waitForFunction(() => scrollY > 0); +}); + +async function captureLastKeydown(page) { + const lastEvent = await page.evaluateHandle(() => { + const lastEvent = { + repeat: false, + location: -1, + code: '', + key: '', + metaKey: false, + keyIdentifier: 'unsupported' + }; + document.addEventListener('keydown', e => { + lastEvent.repeat = e.repeat; + lastEvent.location = e.location; + lastEvent.key = e.key; + lastEvent.code = e.code; + lastEvent.metaKey = e.metaKey; + // keyIdentifier only exists in WebKit, and isn't in TypeScript's lib. + lastEvent.keyIdentifier = 'keyIdentifier' in e && e.keyIdentifier; + }, true); + return lastEvent; + }); + return lastEvent; +} diff --git a/test/logger.jest.js b/test/logger.jest.js deleted file mode 100644 index b8decc288d..0000000000 --- a/test/logger.jest.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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 fs = require('fs'); -const path = require('path'); -const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = testOptions; - -describe('Logger', function() { - it('should log', async({browserType, defaultBrowserOptions}) => { - const log = []; - const browser = await browserType.launch({...defaultBrowserOptions, logger: { - log: (name, severity, message) => log.push({name, severity, message}), - isEnabled: (name, severity) => severity !== 'verbose' - }}); - await browser.newContext(); - await browser.close(); - expect(log.length > 0).toBeTruthy(); - expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy(); - }); - it('should log context-level', async({browserType, defaultBrowserOptions}) => { - const log = []; - const browser = await browserType.launch(defaultBrowserOptions); - const context = await browser.newContext({ - logger: { - log: (name, severity, message) => log.push({name, severity, message}), - isEnabled: (name, severity) => severity !== 'verbose' - } - }); - const page = await context.newPage(); - await page.setContent(''); - await page.click('button'); - await browser.close(); - - expect(log.length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('page.setContent')).length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('page.click')).length > 0).toBeTruthy(); - }); -}); diff --git a/test/logger.spec.js b/test/logger.spec.js new file mode 100644 index 0000000000..cbd4de5ebe --- /dev/null +++ b/test/logger.spec.js @@ -0,0 +1,51 @@ +/** + * 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 fs = require('fs'); +const path = require('path'); +const {FFOX, CHROMIUM, WEBKIT, CHANNEL} = testOptions; + +it('should log', async({browserType, defaultBrowserOptions}) => { + const log = []; + const browser = await browserType.launch({...defaultBrowserOptions, logger: { + log: (name, severity, message) => log.push({name, severity, message}), + isEnabled: (name, severity) => severity !== 'verbose' + }}); + await browser.newContext(); + await browser.close(); + expect(log.length > 0).toBeTruthy(); + expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy(); +}); +it('should log context-level', async({browserType, defaultBrowserOptions}) => { + const log = []; + const browser = await browserType.launch(defaultBrowserOptions); + const context = await browser.newContext({ + logger: { + log: (name, severity, message) => log.push({name, severity, message}), + isEnabled: (name, severity) => severity !== 'verbose' + } + }); + const page = await context.newPage(); + await page.setContent(''); + await page.click('button'); + await browser.close(); + + expect(log.length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('page.setContent')).length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('page.click')).length > 0).toBeTruthy(); +}); diff --git a/test/mouse.jest.js b/test/mouse.jest.js deleted file mode 100644 index e2e7192e89..0000000000 --- a/test/mouse.jest.js +++ /dev/null @@ -1,181 +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 {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions; - -function dimensions() { - const rect = document.querySelector('textarea').getBoundingClientRect(); - return { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height - }; -} - -describe('Mouse', function() { - // Occasionally times out on FFOX on Windows: https://github.com/microsoft/playwright/pull/1911/checks?check_run_id=607149016 - it.fail(FFOX && WIN)('should click the document', async({page, server}) => { - await page.evaluate(() => { - window.clickPromise = new Promise(resolve => { - document.addEventListener('click', event => { - resolve({ - type: event.type, - detail: event.detail, - clientX: event.clientX, - clientY: event.clientY, - isTrusted: event.isTrusted, - button: event.button - }); - }); - }); - }); - await page.mouse.click(50, 60); - const event = await page.evaluate(() => window.clickPromise); - expect(event.type).toBe('click'); - expect(event.detail).toBe(1); - expect(event.clientX).toBe(50); - expect(event.clientY).toBe(60); - expect(event.isTrusted).toBe(true); - expect(event.button).toBe(0); - }); - it('should dblclick the div', async({page, server}) => { - await page.setContent(`
Click me
`); - await page.evaluate(() => { - window.dblclickPromise = new Promise(resolve => { - document.querySelector('div').addEventListener('dblclick', event => { - resolve({ - type: event.type, - detail: event.detail, - clientX: event.clientX, - clientY: event.clientY, - isTrusted: event.isTrusted, - button: event.button, - }); - }); - }); - }); - await page.mouse.dblclick(50, 60); - const event = await page.evaluate(() => window.dblclickPromise); - expect(event.type).toBe('dblclick'); - expect(event.detail).toBe(2); - expect(event.clientX).toBe(50); - expect(event.clientY).toBe(60); - expect(event.isTrusted).toBe(true); - expect(event.button).toBe(0); - }); - it('should select the text with mouse', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; - await page.keyboard.type(text); - // Firefox needs an extra frame here after typing or it will fail to set the scrollTop - await page.evaluate(() => new Promise(requestAnimationFrame)); - await page.evaluate(() => document.querySelector('textarea').scrollTop = 0); - const {x, y} = await page.evaluate(dimensions); - await page.mouse.move(x + 2,y + 2); - await page.mouse.down(); - await page.mouse.move(200,200); - await page.mouse.up(); - expect(await page.evaluate(() => { - const textarea = document.querySelector('textarea'); - return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); - })).toBe(text); - }); - it('should trigger hover state', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.hover('#button-6'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); - await page.hover('#button-2'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2'); - await page.hover('#button-91'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); - }); - it('should trigger hover state with removed window.Node', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.evaluate(() => delete window.Node); - await page.hover('#button-6'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); - }); - it('should set modifier keys on click', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true)); - const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'}; - // In Firefox, the Meta modifier only exists on Mac - if (FFOX && !MAC) - delete modifiers['Meta']; - for (const modifier in modifiers) { - await page.keyboard.down(modifier); - await page.click('#button-3'); - if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) - throw new Error(modifiers[modifier] + ' should be true'); - await page.keyboard.up(modifier); - } - await page.click('#button-3'); - for (const modifier in modifiers) { - if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) - throw new Error(modifiers[modifier] + ' should be false'); - } - }); - it('should tween mouse movement', async({page, server}) => { - // The test becomes flaky on WebKit without next line. - if (WEBKIT) - await page.evaluate(() => new Promise(requestAnimationFrame)); - await page.mouse.move(100, 100); - await page.evaluate(() => { - window.result = []; - document.addEventListener('mousemove', event => { - window.result.push([event.clientX, event.clientY]); - }); - }); - await page.mouse.move(200, 300, {steps: 5}); - expect(await page.evaluate('result')).toEqual([ - [120, 140], - [140, 180], - [160, 220], - [180, 260], - [200, 300] - ]); - }); - it.skip(FFOX)('should work with mobile viewports and cross process navigations', async({browser, server}) => { - // @see https://crbug.com/929806 - const context = await browser.newContext({ viewport: {width: 360, height: 640, isMobile: true} }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html'); - await page.evaluate(() => { - document.addEventListener('click', event => { - window.result = {x: event.clientX, y: event.clientY}; - }); - }); - - await page.mouse.click(30, 40); - - expect(await page.evaluate('result')).toEqual({x: 30, y: 40}); - await context.close(); - }); - xdescribe('Drag and Drop', function() { - it('should work', async({server, page}) => { - await page.goto(server.PREFIX + '/drag-n-drop.html'); - await page.hover('#source'); - await page.mouse.down(); - await page.hover('#target'); - await page.mouse.up(); - expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true, 'could not find source in target'); - }) - }); -}); diff --git a/test/mouse.spec.js b/test/mouse.spec.js new file mode 100644 index 0000000000..cb3325d04e --- /dev/null +++ b/test/mouse.spec.js @@ -0,0 +1,187 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions; + +function dimensions() { + const rect = document.querySelector('textarea').getBoundingClientRect(); + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + }; +} + +it.fail(FFOX && WIN)('should click the document', async({page, server}) => { + // Occasionally times out on FFOX on Windows: https://github.com/microsoft/playwright/pull/1911/checks?check_run_id=607149016 + await page.evaluate(() => { + window.clickPromise = new Promise(resolve => { + document.addEventListener('click', event => { + resolve({ + type: event.type, + detail: event.detail, + clientX: event.clientX, + clientY: event.clientY, + isTrusted: event.isTrusted, + button: event.button + }); + }); + }); + }); + await page.mouse.click(50, 60); + const event = await page.evaluate(() => window.clickPromise); + expect(event.type).toBe('click'); + expect(event.detail).toBe(1); + expect(event.clientX).toBe(50); + expect(event.clientY).toBe(60); + expect(event.isTrusted).toBe(true); + expect(event.button).toBe(0); +}); + +it('should dblclick the div', async({page, server}) => { + await page.setContent(`
Click me
`); + await page.evaluate(() => { + window.dblclickPromise = new Promise(resolve => { + document.querySelector('div').addEventListener('dblclick', event => { + resolve({ + type: event.type, + detail: event.detail, + clientX: event.clientX, + clientY: event.clientY, + isTrusted: event.isTrusted, + button: event.button, + }); + }); + }); + }); + await page.mouse.dblclick(50, 60); + const event = await page.evaluate(() => window.dblclickPromise); + expect(event.type).toBe('dblclick'); + expect(event.detail).toBe(2); + expect(event.clientX).toBe(50); + expect(event.clientY).toBe(60); + expect(event.isTrusted).toBe(true); + expect(event.button).toBe(0); +}); + +it('should select the text with mouse', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; + await page.keyboard.type(text); + // Firefox needs an extra frame here after typing or it will fail to set the scrollTop + await page.evaluate(() => new Promise(requestAnimationFrame)); + await page.evaluate(() => document.querySelector('textarea').scrollTop = 0); + const {x, y} = await page.evaluate(dimensions); + await page.mouse.move(x + 2,y + 2); + await page.mouse.down(); + await page.mouse.move(200,200); + await page.mouse.up(); + expect(await page.evaluate(() => { + const textarea = document.querySelector('textarea'); + return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd); + })).toBe(text); +}); + +it('should trigger hover state', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.hover('#button-6'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); + await page.hover('#button-2'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2'); + await page.hover('#button-91'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); +}); + +it('should trigger hover state with removed window.Node', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.evaluate(() => delete window.Node); + await page.hover('#button-6'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); +}); + +it('should set modifier keys on click', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true)); + const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'}; + // In Firefox, the Meta modifier only exists on Mac + if (FFOX && !MAC) + delete modifiers['Meta']; + for (const modifier in modifiers) { + await page.keyboard.down(modifier); + await page.click('#button-3'); + if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) + throw new Error(modifiers[modifier] + ' should be true'); + await page.keyboard.up(modifier); + } + await page.click('#button-3'); + for (const modifier in modifiers) { + if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) + throw new Error(modifiers[modifier] + ' should be false'); + } +}); + +it('should tween mouse movement', async({page, server}) => { + // The test becomes flaky on WebKit without next line. + if (WEBKIT) + await page.evaluate(() => new Promise(requestAnimationFrame)); + await page.mouse.move(100, 100); + await page.evaluate(() => { + window.result = []; + document.addEventListener('mousemove', event => { + window.result.push([event.clientX, event.clientY]); + }); + }); + await page.mouse.move(200, 300, {steps: 5}); + expect(await page.evaluate('result')).toEqual([ + [120, 140], + [140, 180], + [160, 220], + [180, 260], + [200, 300] + ]); +}); + +it.skip(FFOX)('should work with mobile viewports and cross process navigations', async({browser, server}) => { + // @see https://crbug.com/929806 + const context = await browser.newContext({ viewport: {width: 360, height: 640, isMobile: true} }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html'); + await page.evaluate(() => { + document.addEventListener('click', event => { + window.result = {x: event.clientX, y: event.clientY}; + }); + }); + + await page.mouse.click(30, 40); + + expect(await page.evaluate('result')).toEqual({x: 30, y: 40}); + await context.close(); +}); + +xdescribe('Drag and Drop', function() { + it('should work', async({server, page}) => { + await page.goto(server.PREFIX + '/drag-n-drop.html'); + await page.hover('#source'); + await page.mouse.down(); + await page.hover('#target'); + await page.mouse.up(); + expect(await page.$eval('#target', target => target.contains(document.querySelector('#source')))).toBe(true, 'could not find source in target'); + }) +}); diff --git a/test/page-add-init-script.spec.js b/test/page-add-init-script.spec.js new file mode 100644 index 0000000000..24aa0b143c --- /dev/null +++ b/test/page-add-init-script.spec.js @@ -0,0 +1,99 @@ +/** + * 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 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); +}); diff --git a/test/page-evaluate.spec.js b/test/page-evaluate.spec.js new file mode 100644 index 0000000000..17cd24813a --- /dev/null +++ b/test/page-evaluate.spec.js @@ -0,0 +1,470 @@ +/** + * 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 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: {} }); +}); diff --git a/test/page-popup-event.spec.js b/test/page-popup-event.spec.js new file mode 100644 index 0000000000..b58377865c --- /dev/null +++ b/test/page-popup-event.spec.js @@ -0,0 +1,184 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; + +it('should work', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.__popup = window.open('about:blank')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); +}); +it('should work with window features', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.__popup = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); +}); +it('should emit for immediately closed popups', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const win = window.open('about:blank'); + win.close(); + }), + ]); + expect(popup).toBeTruthy(); + await context.close(); +}); +it('should emit for immediately closed popups 2', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => { + const win = window.open(window.location.href); + win.close(); + }), + ]); + expect(popup).toBeTruthy(); + await context.close(); +}); +it('should be able to capture alert', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const evaluatePromise = page.evaluate(() => { + const win = window.open(''); + win.alert('hello'); + }); + const popup = await page.waitForEvent('popup'); + const dialog = await popup.waitForEvent('dialog'); + expect(dialog.message()).toBe('hello'); + await dialog.dismiss(); + await evaluatePromise; + await context.close(); +}); +it('should work with empty url', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.__popup = window.open('')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); +}); +it('should work with noopener and no url', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.__popup = window.open(undefined, null, 'noopener')), + ]); + // Chromium reports `about:blank#blocked` here. + expect(popup.url().split('#')[0]).toBe('about:blank'); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); +}); +it('should work with noopener and about:blank', async({browser}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); +}); +it('should work with noopener and url', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); +}); +it('should work with clicking target=_blank', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(true); + await context.close(); +}); +it('should work with fake-clicking target=_blank and rel=noopener', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.$eval('a', a => a.click()), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); +}); +it('should work with clicking target=_blank and rel=noopener', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + expect(await page.evaluate(() => !!window.opener)).toBe(false); + expect(await popup.evaluate(() => !!window.opener)).toBe(false); + await context.close(); +}); +it('should not treat navigations as new popups', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('yo'); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.click('a'), + ]); + let badSecondPopup = false; + page.on('popup', () => badSecondPopup = true); + await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + await context.close(); + expect(badSecondPopup).toBe(false); +}); + diff --git a/test/pdf.jest.js b/test/pdf.spec.js similarity index 62% rename from test/pdf.jest.js rename to test/pdf.spec.js index ebd8663579..5258f4cbcc 100644 --- a/test/pdf.jest.js +++ b/test/pdf.spec.js @@ -19,17 +19,13 @@ const path = require('path'); const {FFOX, CHROMIUM, WEBKIT, OUTPUT_DIR, HEADLESS} = testOptions; // Printing to pdf is currently only supported in headless chromium. -describe.skip(!(HEADLESS && CHROMIUM))('Page.pdf', function() { - it('should be able to save file', async({page, server}) => { - const outputFile = path.join(OUTPUT_DIR, 'output.pdf'); - await page.pdf({path: outputFile}); - expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); - fs.unlinkSync(outputFile); - }); +it.skip(!(HEADLESS && CHROMIUM))('should be able to save file', async({page, server}) => { + const outputFile = path.join(OUTPUT_DIR, 'output.pdf'); + await page.pdf({path: outputFile}); + expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); + fs.unlinkSync(outputFile); }); -describe.skip(CHROMIUM)('Page.pdf missing', function() { - it('should be able to save file', async({page, server}) => { - expect(page.pdf).toBe(undefined); - }); +it.skip(CHROMIUM)('should be able to save file', async({page, server}) => { + expect(page.pdf).toBe(undefined); }); diff --git a/test/permissions.jest.js b/test/permissions.jest.js deleted file mode 100644 index a257b0b3c6..0000000000 --- a/test/permissions.jest.js +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2017 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 {FFOX, CHROMIUM, WEBKIT, LINUX, HEADLESS} = testOptions; - -// Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) -describe.skip(WEBKIT)('Permissions', function() { - function getPermission(page, name) { - return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name); - } - - it('should be prompt by default', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - }); - it('should deny permission when not listed', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions([], { origin: server.EMPTY_PAGE }); - expect(await getPermission(page, 'geolocation')).toBe('denied'); - }); - it('should fail when bad permission is given', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - let error = {}; - await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e); - expect(error.message).toContain('Unknown permission: foo'); - }); - it('should grant geolocation permission when listed', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); - expect(await getPermission(page, 'geolocation')).toBe('granted'); - }); - it('should grant notifications permission when listed', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE }); - expect(await getPermission(page, 'notifications')).toBe('granted'); - }); - it('should accumulate when adding', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['geolocation']); - await context.grantPermissions(['notifications']); - expect(await getPermission(page, 'geolocation')).toBe('granted'); - expect(await getPermission(page, 'notifications')).toBe('granted'); - }); - it('should clear permissions', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['geolocation']); - await context.clearPermissions(); - await context.grantPermissions(['notifications']); - expect(await getPermission(page, 'geolocation')).not.toBe('granted'); - expect(await getPermission(page, 'notifications')).toBe('granted'); - }); - it('should grant permission when listed for all domains', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['geolocation']); - expect(await getPermission(page, 'geolocation')).toBe('granted'); - }); - it('should grant permission when creating context', async({server, browser}) => { - const context = await browser.newContext({ permissions: ['geolocation'] }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - expect(await getPermission(page, 'geolocation')).toBe('granted'); - await context.close(); - }); - it('should reset permissions', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); - expect(await getPermission(page, 'geolocation')).toBe('granted'); - await context.clearPermissions(); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - }); - //TODO: flaky - // - Linux: https://github.com/microsoft/playwright/pull/1790/checks?check_run_id=587327883 - // - Win: https://ci.appveyor.com/project/aslushnikov/playwright/builds/32402536 - it.fail(FFOX || (CHROMIUM && !HEADLESS))('should trigger permission onchange', async({page, server, context}) => { - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => { - window['events'] = []; - return navigator.permissions.query({name: 'geolocation'}).then(function(result) { - window['events'].push(result.state); - result.onchange = function() { - window['events'].push(result.state); - }; - }); - }); - expect(await page.evaluate(() => window['events'])).toEqual(['prompt']); - await context.grantPermissions([], { origin: server.EMPTY_PAGE }); - expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']); - await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); - expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']); - await context.clearPermissions(); - expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']); - }); - it('should isolate permissions between browser contexts', async({page, server, context, browser}) => { - await page.goto(server.EMPTY_PAGE); - const otherContext = await browser.newContext(); - const otherPage = await otherContext.newPage(); - await otherPage.goto(server.EMPTY_PAGE); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); - - await context.grantPermissions([], { origin: server.EMPTY_PAGE }); - await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); - expect(await getPermission(page, 'geolocation')).toBe('denied'); - expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); - - await context.clearPermissions(); - expect(await getPermission(page, 'geolocation')).toBe('prompt'); - expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); - await otherContext.close(); - }); - it.fail(FFOX || (CHROMIUM && !HEADLESS))('should support clipboard read', async({page, server, context, browser}) => { - // No such permissions (requires flag) in Firefox - await page.goto(server.EMPTY_PAGE); - expect(await getPermission(page, 'clipboard-read')).toBe('prompt'); - let error; - await page.evaluate(() => navigator.clipboard.readText()).catch(e => error = e); - expect(error.toString()).toContain('denied'); - await context.grantPermissions(['clipboard-read']); - expect(await getPermission(page, 'clipboard-read')).toBe('granted'); - await page.evaluate(() => navigator.clipboard.readText()); - }); -}); diff --git a/test/permissions.spec.js b/test/permissions.spec.js new file mode 100644 index 0000000000..501adf127f --- /dev/null +++ b/test/permissions.spec.js @@ -0,0 +1,146 @@ +/** + * Copyright 2017 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 {FFOX, CHROMIUM, WEBKIT, LINUX, HEADLESS} = testOptions; + +function getPermission(page, name) { + return page.evaluate(name => navigator.permissions.query({name}).then(result => result.state), name); +} + +it.skip(WEBKIT)('should be prompt by default', async({page, server, context}) => { + // Permissions API is not implemented in WebKit (see https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) + await page.goto(server.EMPTY_PAGE); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); +}); + +it.skip(WEBKIT)('should deny permission when not listed', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions([], { origin: server.EMPTY_PAGE }); + expect(await getPermission(page, 'geolocation')).toBe('denied'); +}); + +it.skip(WEBKIT)('should fail when bad permission is given', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + let error = {}; + await context.grantPermissions(['foo'], { origin: server.EMPTY_PAGE }).catch(e => error = e); + expect(error.message).toContain('Unknown permission: foo'); +}); + +it.skip(WEBKIT)('should grant geolocation permission when listed', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); + expect(await getPermission(page, 'geolocation')).toBe('granted'); +}); + +it.skip(WEBKIT)('should grant notifications permission when listed', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['notifications'], { origin: server.EMPTY_PAGE }); + expect(await getPermission(page, 'notifications')).toBe('granted'); +}); + +it.skip(WEBKIT)('should accumulate when adding', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['geolocation']); + await context.grantPermissions(['notifications']); + expect(await getPermission(page, 'geolocation')).toBe('granted'); + expect(await getPermission(page, 'notifications')).toBe('granted'); +}); + +it.skip(WEBKIT)('should clear permissions', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['geolocation']); + await context.clearPermissions(); + await context.grantPermissions(['notifications']); + expect(await getPermission(page, 'geolocation')).not.toBe('granted'); + expect(await getPermission(page, 'notifications')).toBe('granted'); +}); + +it.skip(WEBKIT)('should grant permission when listed for all domains', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['geolocation']); + expect(await getPermission(page, 'geolocation')).toBe('granted'); +}); + +it.skip(WEBKIT)('should grant permission when creating context', async({server, browser}) => { + const context = await browser.newContext({ permissions: ['geolocation'] }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + expect(await getPermission(page, 'geolocation')).toBe('granted'); + await context.close(); +}); + +it.skip(WEBKIT)('should reset permissions', async({page, server, context}) => { + await page.goto(server.EMPTY_PAGE); + await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); + expect(await getPermission(page, 'geolocation')).toBe('granted'); + await context.clearPermissions(); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); +}); + +it.fail(WEBKIT || FFOX || (CHROMIUM && !HEADLESS))('should trigger permission onchange', async({page, server, context}) => { + //TODO: flaky + // - Linux: https://github.com/microsoft/playwright/pull/1790/checks?check_run_id=587327883 + // - Win: https://ci.appveyor.com/project/aslushnikov/playwright/builds/32402536 + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => { + window['events'] = []; + return navigator.permissions.query({name: 'geolocation'}).then(function(result) { + window['events'].push(result.state); + result.onchange = function() { + window['events'].push(result.state); + }; + }); + }); + expect(await page.evaluate(() => window['events'])).toEqual(['prompt']); + await context.grantPermissions([], { origin: server.EMPTY_PAGE }); + expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied']); + await context.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); + expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted']); + await context.clearPermissions(); + expect(await page.evaluate(() => window['events'])).toEqual(['prompt', 'denied', 'granted', 'prompt']); +}); + +it.skip(WEBKIT)('should isolate permissions between browser contexts', async({page, server, context, browser}) => { + await page.goto(server.EMPTY_PAGE); + const otherContext = await browser.newContext(); + const otherPage = await otherContext.newPage(); + await otherPage.goto(server.EMPTY_PAGE); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); + expect(await getPermission(otherPage, 'geolocation')).toBe('prompt'); + + await context.grantPermissions([], { origin: server.EMPTY_PAGE }); + await otherContext.grantPermissions(['geolocation'], { origin: server.EMPTY_PAGE }); + expect(await getPermission(page, 'geolocation')).toBe('denied'); + expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); + + await context.clearPermissions(); + expect(await getPermission(page, 'geolocation')).toBe('prompt'); + expect(await getPermission(otherPage, 'geolocation')).toBe('granted'); + await otherContext.close(); +}); + +it.fail(WEBKIT || FFOX || (CHROMIUM && !HEADLESS))('should support clipboard read', async({page, server, context, browser}) => { + // No such permissions (requires flag) in Firefox + await page.goto(server.EMPTY_PAGE); + expect(await getPermission(page, 'clipboard-read')).toBe('prompt'); + let error; + await page.evaluate(() => navigator.clipboard.readText()).catch(e => error = e); + expect(error.toString()).toContain('denied'); + await context.grantPermissions(['clipboard-read']); + expect(await getPermission(page, 'clipboard-read')).toBe('granted'); + await page.evaluate(() => navigator.clipboard.readText()); +}); diff --git a/test/popup.jest.js b/test/popup.jest.js deleted file mode 100644 index fe0f00aa85..0000000000 --- a/test/popup.jest.js +++ /dev/null @@ -1,417 +0,0 @@ -/** - * 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 {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; - -describe('Link navigation', function() { - it('should inherit user agent from browser context', async function({browser, server}) { - const context = await browser.newContext({ - userAgent: 'hey' - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('link'); - const requestPromise = server.waitForRequest('/popup/popup.html'); - const [popup] = await Promise.all([ - context.waitForEvent('page'), - page.click('a'), - ]); - await popup.waitForLoadState('domcontentloaded'); - const userAgent = await popup.evaluate(() => window.initialUserAgent); - const request = await requestPromise; - await context.close(); - expect(userAgent).toBe('hey'); - expect(request.headers['user-agent']).toBe('hey'); - }); - it('should respect routes from browser context', async function({browser, server}) { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('link'); - let intercepted = false; - await context.route('**/empty.html', route => { - route.continue(); - intercepted = true; - }); - await Promise.all([ - context.waitForEvent('page'), - page.click('a'), - ]); - await context.close(); - expect(intercepted).toBe(true); - }); -}); - -describe('window.open', function() { - it('should inherit user agent from browser context', async function({browser, server}) { - const context = await browser.newContext({ - userAgent: 'hey' - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const requestPromise = server.waitForRequest('/dummy.html'); - const userAgent = await page.evaluate(url => { - const win = window.open(url); - return win.navigator.userAgent; - }, server.PREFIX + '/dummy.html'); - const request = await requestPromise; - await context.close(); - expect(userAgent).toBe('hey'); - expect(request.headers['user-agent']).toBe('hey'); - }); - it('should inherit extra headers from browser context', async function({browser, server}) { - const context = await browser.newContext({ - extraHTTPHeaders: { 'foo': 'bar' }, - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const requestPromise = server.waitForRequest('/dummy.html'); - await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html'); - const request = await requestPromise; - await context.close(); - expect(request.headers['foo']).toBe('bar'); - }); - it('should inherit offline from browser context', async function({browser, server}) { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await context.setOffline(true); - const online = await page.evaluate(url => { - const win = window.open(url); - return win.navigator.onLine; - }, server.PREFIX + '/dummy.html'); - await context.close(); - expect(online).toBe(false); - }); - it('should inherit http credentials from browser context', async function({browser, server}) { - server.setAuth('/title.html', 'user', 'pass'); - const context = await browser.newContext({ - httpCredentials: { username: 'user', password: 'pass' } - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'), - ]); - await popup.waitForLoadState('domcontentloaded'); - expect(await popup.title()).toBe('Woof-Woof'); - await context.close(); - }); - it('should inherit touch support from browser context', async function({browser, server}) { - const context = await browser.newContext({ - viewport: { width: 400, height: 500 }, - hasTouch: true - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const hasTouch = await page.evaluate(() => { - const win = window.open(''); - return 'ontouchstart' in win; - }); - await context.close(); - expect(hasTouch).toBe(true); - }); - it('should inherit viewport size from browser context', async function({browser, server}) { - const context = await browser.newContext({ - viewport: { width: 400, height: 500 } - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const size = await page.evaluate(() => { - const win = window.open('about:blank'); - return { width: win.innerWidth, height: win.innerHeight }; - }); - await context.close(); - expect(size).toEqual({width: 400, height: 500}); - }); - it('should use viewport size from window features', async function({browser, server}) { - const context = await browser.newContext({ - viewport: { width: 700, height: 700 } - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [size, popup] = await Promise.all([ - page.evaluate(() => { - const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0'); - return { width: win.innerWidth, height: win.innerHeight }; - }), - page.waitForEvent('popup'), - ]); - await popup.setViewportSize({ width: 500, height: 400 }); - await popup.waitForLoadState(); - const resized = await popup.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight })); - await context.close(); - expect(size).toEqual({width: 600, height: 300}); - expect(resized).toEqual({width: 500, height: 400}); - }); - it('should respect routes from browser context', async function({browser, server}) { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - let intercepted = false; - await context.route('**/empty.html', route => { - route.continue(); - intercepted = true; - }); - await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE), - ]); - expect(intercepted).toBe(true); - await context.close(); - }); - it('BrowserContext.addInitScript should apply to an in-process popup', async function({browser, server}) { - const context = await browser.newContext(); - await context.addInitScript(() => window.injected = 123); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const injected = await page.evaluate(() => { - const win = window.open('about:blank'); - return win.injected; - }); - await context.close(); - expect(injected).toBe(123); - }); - it('BrowserContext.addInitScript should apply to a cross-process popup', async function({browser, server}) { - const context = await browser.newContext(); - await context.addInitScript(() => window.injected = 123); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/title.html'), - ]); - expect(await popup.evaluate('injected')).toBe(123); - await popup.reload(); - expect(await popup.evaluate('injected')).toBe(123); - await context.close(); - }); - it('should expose function from browser context', async function({browser, server}) { - const context = await browser.newContext(); - const messages = []; - await context.exposeFunction('add', (a, b) => { - messages.push('binding'); - return a + b; - }); - const page = await context.newPage(); - context.on('page', () => messages.push('page')); - await page.goto(server.EMPTY_PAGE); - const added = await page.evaluate(async () => { - const win = window.open('about:blank'); - return win.add(9, 4); - }); - await context.close(); - expect(added).toBe(13); - expect(messages.join('|')).toBe('page|binding'); - }); - it('should not dispatch binding on a closed page', async function({browser, server}) { - const context = await browser.newContext(); - const messages = []; - await context.exposeFunction('add', (a, b) => { - messages.push('binding'); - return a + b; - }); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await Promise.all([ - page.waitForEvent('popup').then(popup => { - if (popup.isClosed()) - messages.push('alreadyclosed'); - else - return popup.waitForEvent('close').then(() => messages.push('close')); - }), - page.evaluate(async () => { - const win = window.open('about:blank'); - win.add(9, 4); - win.close(); - }), - ]); - await context.close(); - if (FFOX) - expect(messages.join('|')).toBe('alreadyclosed'); - else - expect(messages.join('|')).toBe('binding|close'); - }); -}); - -describe('Page.Events.Popup', function() { - it('should work', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.__popup = window.open('about:blank')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should work with window features', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.__popup = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=200,top=0,left=0')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should emit for immediately closed popups', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => { - const win = window.open('about:blank'); - win.close(); - }), - ]); - expect(popup).toBeTruthy(); - await context.close(); - }); - it('should emit for immediately closed popups 2', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => { - const win = window.open(window.location.href); - win.close(); - }), - ]); - expect(popup).toBeTruthy(); - await context.close(); - }); - it('should be able to capture alert', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const evaluatePromise = page.evaluate(() => { - const win = window.open(''); - win.alert('hello'); - }); - const popup = await page.waitForEvent('popup'); - const dialog = await popup.waitForEvent('dialog'); - expect(dialog.message()).toBe('hello'); - await dialog.dismiss(); - await evaluatePromise; - await context.close(); - }); - it('should work with empty url', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.__popup = window.open('')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should work with noopener and no url', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.__popup = window.open(undefined, null, 'noopener')), - ]); - // Chromium reports `about:blank#blocked` here. - expect(popup.url().split('#')[0]).toBe('about:blank'); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with noopener and about:blank', async({browser}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(() => window.__popup = window.open('about:blank', null, 'noopener')), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with noopener and url', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.evaluate(url => window.__popup = window.open(url, null, 'noopener'), server.EMPTY_PAGE), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with clicking target=_blank', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(true); - await context.close(); - }); - it('should work with fake-clicking target=_blank and rel=noopener', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.$eval('a', a => a.click()), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should work with clicking target=_blank and rel=noopener', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - expect(await page.evaluate(() => !!window.opener)).toBe(false); - expect(await popup.evaluate(() => !!window.opener)).toBe(false); - await context.close(); - }); - it('should not treat navigations as new popups', async({browser, server}) => { - const context = await browser.newContext(); - const page = await context.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.setContent('yo'); - const [popup] = await Promise.all([ - page.waitForEvent('popup'), - page.click('a'), - ]); - let badSecondPopup = false; - page.on('popup', () => badSecondPopup = true); - await popup.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - await context.close(); - expect(badSecondPopup).toBe(false); - }); -}); diff --git a/test/popup.spec.js b/test/popup.spec.js new file mode 100644 index 0000000000..90ee69b8ff --- /dev/null +++ b/test/popup.spec.js @@ -0,0 +1,241 @@ +/** + * 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 {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; + + +it('should inherit user agent from browser context', async function({browser, server}) { + const context = await browser.newContext({ + userAgent: 'hey' + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('link'); + const requestPromise = server.waitForRequest('/popup/popup.html'); + const [popup] = await Promise.all([ + context.waitForEvent('page'), + page.click('a'), + ]); + await popup.waitForLoadState('domcontentloaded'); + const userAgent = await popup.evaluate(() => window.initialUserAgent); + const request = await requestPromise; + await context.close(); + expect(userAgent).toBe('hey'); + expect(request.headers['user-agent']).toBe('hey'); +}); + +it('should respect routes from browser context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.setContent('link'); + let intercepted = false; + await context.route('**/empty.html', route => { + route.continue(); + intercepted = true; + }); + await Promise.all([ + context.waitForEvent('page'), + page.click('a'), + ]); + await context.close(); + expect(intercepted).toBe(true); +}); + +it('should inherit extra headers from browser context', async function({browser, server}) { + const context = await browser.newContext({ + extraHTTPHeaders: { 'foo': 'bar' }, + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const requestPromise = server.waitForRequest('/dummy.html'); + await page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/dummy.html'); + const request = await requestPromise; + await context.close(); + expect(request.headers['foo']).toBe('bar'); +}); + +it('should inherit offline from browser context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await context.setOffline(true); + const online = await page.evaluate(url => { + const win = window.open(url); + return win.navigator.onLine; + }, server.PREFIX + '/dummy.html'); + await context.close(); + expect(online).toBe(false); +}); + +it('should inherit http credentials from browser context', async function({browser, server}) { + server.setAuth('/title.html', 'user', 'pass'); + const context = await browser.newContext({ + httpCredentials: { username: 'user', password: 'pass' } + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window._popup = window.open(url), server.PREFIX + '/title.html'), + ]); + await popup.waitForLoadState('domcontentloaded'); + expect(await popup.title()).toBe('Woof-Woof'); + await context.close(); +}); + +it('should inherit touch support from browser context', async function({browser, server}) { + const context = await browser.newContext({ + viewport: { width: 400, height: 500 }, + hasTouch: true + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const hasTouch = await page.evaluate(() => { + const win = window.open(''); + return 'ontouchstart' in win; + }); + await context.close(); + expect(hasTouch).toBe(true); +}); + +it('should inherit viewport size from browser context', async function({browser, server}) { + const context = await browser.newContext({ + viewport: { width: 400, height: 500 } + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const size = await page.evaluate(() => { + const win = window.open('about:blank'); + return { width: win.innerWidth, height: win.innerHeight }; + }); + await context.close(); + expect(size).toEqual({width: 400, height: 500}); +}); + +it('should use viewport size from window features', async function({browser, server}) { + const context = await browser.newContext({ + viewport: { width: 700, height: 700 } + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [size, popup] = await Promise.all([ + page.evaluate(() => { + const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0'); + return { width: win.innerWidth, height: win.innerHeight }; + }), + page.waitForEvent('popup'), + ]); + await popup.setViewportSize({ width: 500, height: 400 }); + await popup.waitForLoadState(); + const resized = await popup.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight })); + await context.close(); + expect(size).toEqual({width: 600, height: 300}); + expect(resized).toEqual({width: 500, height: 400}); +}); + +it('should respect routes from browser context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + let intercepted = false; + await context.route('**/empty.html', route => { + route.continue(); + intercepted = true; + }); + await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.__popup = window.open(url), server.EMPTY_PAGE), + ]); + expect(intercepted).toBe(true); + await context.close(); +}); + +it('BrowserContext.addInitScript should apply to an in-process popup', async function({browser, server}) { + const context = await browser.newContext(); + await context.addInitScript(() => window.injected = 123); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const injected = await page.evaluate(() => { + const win = window.open('about:blank'); + return win.injected; + }); + await context.close(); + expect(injected).toBe(123); +}); + +it('BrowserContext.addInitScript should apply to a cross-process popup', async function({browser, server}) { + const context = await browser.newContext(); + await context.addInitScript(() => window.injected = 123); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + const [popup] = await Promise.all([ + page.waitForEvent('popup'), + page.evaluate(url => window.open(url), server.CROSS_PROCESS_PREFIX + '/title.html'), + ]); + expect(await popup.evaluate('injected')).toBe(123); + await popup.reload(); + expect(await popup.evaluate('injected')).toBe(123); + await context.close(); +}); + +it('should expose function from browser context', async function({browser, server}) { + const context = await browser.newContext(); + const messages = []; + await context.exposeFunction('add', (a, b) => { + messages.push('binding'); + return a + b; + }); + const page = await context.newPage(); + context.on('page', () => messages.push('page')); + await page.goto(server.EMPTY_PAGE); + const added = await page.evaluate(async () => { + const win = window.open('about:blank'); + return win.add(9, 4); + }); + await context.close(); + expect(added).toBe(13); + expect(messages.join('|')).toBe('page|binding'); +}); + +it('should not dispatch binding on a closed page', async function({browser, server}) { + const context = await browser.newContext(); + const messages = []; + await context.exposeFunction('add', (a, b) => { + messages.push('binding'); + return a + b; + }); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await Promise.all([ + page.waitForEvent('popup').then(popup => { + if (popup.isClosed()) + messages.push('alreadyclosed'); + else + return popup.waitForEvent('close').then(() => messages.push('close')); + }), + page.evaluate(async () => { + const win = window.open('about:blank'); + win.add(9, 4); + win.close(); + }), + ]); + await context.close(); + if (FFOX) + expect(messages.join('|')).toBe('alreadyclosed'); + else + expect(messages.join('|')).toBe('binding|close'); +}); diff --git a/test/proxy.jest.js b/test/proxy.jest.js deleted file mode 100644 index 37129f1947..0000000000 --- a/test/proxy.jest.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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 socks = require('socksv5'); -const utils = require('./utils'); -const {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; - -describe('HTTP Proxy', () => { - it('should use proxy', async ({browserType, defaultBrowserOptions, server}) => { - server.setRoute('/target.html', async (req, res) => { - res.end('Served by the proxy'); - }); - const browser = await browserType.launch({ - ...defaultBrowserOptions, - proxy: { server: `localhost:${server.PORT}` } - }); - const page = await browser.newPage(); - await page.goto('http://non-existent.com/target.html'); - expect(await page.title()).toBe('Served by the proxy'); - await browser.close(); - }); - - it('should authenticate', async ({browserType, defaultBrowserOptions, server}) => { - server.setRoute('/target.html', async (req, res) => { - const auth = req.headers['proxy-authorization']; - if (!auth) { - res.writeHead(407, 'Proxy Authentication Required', { - 'Proxy-Authenticate': 'Basic realm="Access to internal site"' - }); - res.end(); - } else { - res.end(`${auth}`); - } - }); - const browser = await browserType.launch({ - ...defaultBrowserOptions, - proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' } - }); - const page = await browser.newPage(); - await page.goto('http://non-existent.com/target.html'); - expect(await page.title()).toBe('Basic ' + Buffer.from('user:secret').toString('base64')); - await browser.close(); - }); - - it('should exclude patterns', async ({browserType, defaultBrowserOptions, server}) => { - server.setRoute('/target.html', async (req, res) => { - res.end('Served by the proxy'); - }); - const browser = await browserType.launch({ - ...defaultBrowserOptions, - proxy: { server: `localhost:${server.PORT}`, bypass: 'non-existent1.com, .non-existent2.com, .zone' } - }); - - const page = await browser.newPage(); - await page.goto('http://non-existent.com/target.html'); - expect(await page.title()).toBe('Served by the proxy'); - - { - const error = await page.goto('http://non-existent1.com/target.html').catch(e => e); - expect(error.message).toBeTruthy(); - } - - { - const error = await page.goto('http://sub.non-existent2.com/target.html').catch(e => e); - expect(error.message).toBeTruthy(); - } - - { - const error = await page.goto('http://foo.zone/target.html').catch(e => e); - expect(error.message).toBeTruthy(); - } - - await browser.close(); - }); -}); - -describe('SOCKS Proxy', () => { - it('should use proxy', async ({ browserType, defaultBrowserOptions, parallelIndex }) => { - const server = socks.createServer((info, accept, deny) => { - if (socket = accept(true)) { - const body = 'Served by the SOCKS proxy'; - socket.end([ - 'HTTP/1.1 200 OK', - 'Connection: close', - 'Content-Type: text/html', - 'Content-Length: ' + Buffer.byteLength(body), - '', - body - ].join('\r\n')); - } - }); - const socksPort = 9107 + parallelIndex * 2; - server.listen(socksPort, 'localhost'); - server.useAuth(socks.auth.None()); - - const browser = await browserType.launch({ - ...defaultBrowserOptions, - proxy: { server: `socks5://localhost:${socksPort}` } - }); - const page = await browser.newPage(); - await page.goto('http://non-existent.com'); - expect(await page.title()).toBe('Served by the SOCKS proxy'); - await browser.close(); - server.close(); - }); -}); diff --git a/test/proxy.spec.js b/test/proxy.spec.js new file mode 100644 index 0000000000..648c3f4cf4 --- /dev/null +++ b/test/proxy.spec.js @@ -0,0 +1,115 @@ +/** + * 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 socks = require('socksv5'); +const utils = require('./utils'); +const {FFOX, CHROMIUM, WEBKIT, MAC} = testOptions; + +it('should use proxy', async ({browserType, defaultBrowserOptions, server}) => { + server.setRoute('/target.html', async (req, res) => { + res.end('Served by the proxy'); + }); + const browser = await browserType.launch({ + ...defaultBrowserOptions, + proxy: { server: `localhost:${server.PORT}` } + }); + const page = await browser.newPage(); + await page.goto('http://non-existent.com/target.html'); + expect(await page.title()).toBe('Served by the proxy'); + await browser.close(); +}); + +it('should authenticate', async ({browserType, defaultBrowserOptions, server}) => { + server.setRoute('/target.html', async (req, res) => { + const auth = req.headers['proxy-authorization']; + if (!auth) { + res.writeHead(407, 'Proxy Authentication Required', { + 'Proxy-Authenticate': 'Basic realm="Access to internal site"' + }); + res.end(); + } else { + res.end(`${auth}`); + } + }); + const browser = await browserType.launch({ + ...defaultBrowserOptions, + proxy: { server: `localhost:${server.PORT}`, username: 'user', password: 'secret' } + }); + const page = await browser.newPage(); + await page.goto('http://non-existent.com/target.html'); + expect(await page.title()).toBe('Basic ' + Buffer.from('user:secret').toString('base64')); + await browser.close(); +}); + +it('should exclude patterns', async ({browserType, defaultBrowserOptions, server}) => { + server.setRoute('/target.html', async (req, res) => { + res.end('Served by the proxy'); + }); + const browser = await browserType.launch({ + ...defaultBrowserOptions, + proxy: { server: `localhost:${server.PORT}`, bypass: 'non-existent1.com, .non-existent2.com, .zone' } + }); + + const page = await browser.newPage(); + await page.goto('http://non-existent.com/target.html'); + expect(await page.title()).toBe('Served by the proxy'); + + { + const error = await page.goto('http://non-existent1.com/target.html').catch(e => e); + expect(error.message).toBeTruthy(); + } + + { + const error = await page.goto('http://sub.non-existent2.com/target.html').catch(e => e); + expect(error.message).toBeTruthy(); + } + + { + const error = await page.goto('http://foo.zone/target.html').catch(e => e); + expect(error.message).toBeTruthy(); + } + + await browser.close(); +}); + +it('should use socks proxy', async ({ browserType, defaultBrowserOptions, parallelIndex }) => { + const server = socks.createServer((info, accept, deny) => { + if (socket = accept(true)) { + const body = 'Served by the SOCKS proxy'; + socket.end([ + 'HTTP/1.1 200 OK', + 'Connection: close', + 'Content-Type: text/html', + 'Content-Length: ' + Buffer.byteLength(body), + '', + body + ].join('\r\n')); + } + }); + const socksPort = 9107 + parallelIndex * 2; + server.listen(socksPort, 'localhost'); + server.useAuth(socks.auth.None()); + + const browser = await browserType.launch({ + ...defaultBrowserOptions, + proxy: { server: `socks5://localhost:${socksPort}` } + }); + const page = await browser.newPage(); + await page.goto('http://non-existent.com'); + expect(await page.title()).toBe('Served by the SOCKS proxy'); + await browser.close(); + server.close(); +}); diff --git a/test/queryselector.jest.js b/test/queryselector.jest.js deleted file mode 100644 index e4b17f3744..0000000000 --- a/test/queryselector.jest.js +++ /dev/null @@ -1,852 +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 path = require('path'); -const utils = require('./utils'); -const {FFOX, CHROMIUM, WEBKIT, CHANNEL, USES_HOOKS} = testOptions; - -describe('Page.$eval', function() { - it('should work with css selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('css=section', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with id selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('id=testAttribute', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with data-test selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('data-test=foo', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with data-testid selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('data-testid=foo', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with data-test-id selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('data-test-id=foo', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with text selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('text="43543"', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with xpath selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('xpath=/html/body/section', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should work with text selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('text=43543', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should auto-detect css selector', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('section', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should auto-detect css selector with attributes', async({page, server}) => { - await page.setContent('
43543
'); - const idAttribute = await page.$eval('section[id="testAttribute"]', e => e.id); - expect(idAttribute).toBe('testAttribute'); - }); - it('should auto-detect nested selectors', async({page, server}) => { - await page.setContent('
43543Hello
'); - const idAttribute = await page.$eval('div[foo=bar] > section >> "Hello" >> div', e => e.id); - expect(idAttribute).toBe('target'); - }); - it('should accept arguments', async({page, server}) => { - await page.setContent('
hello
'); - const text = await page.$eval('section', (e, suffix) => e.textContent + suffix, ' world!'); - expect(text).toBe('hello world!'); - }); - it('should accept ElementHandles as arguments', async({page, server}) => { - await page.setContent('
hello
world
'); - const divHandle = await page.$('div'); - const text = await page.$eval('section', (e, div) => e.textContent + div.textContent, divHandle); - expect(text).toBe('hello world'); - }); - it('should throw error if no element is found', async({page, server}) => { - let error = null; - await page.$eval('section', e => e.id).catch(e => error = e); - expect(error.message).toContain('failed to find element matching selector "section"'); - }); - it('should support >> syntax', async({page, server}) => { - await page.setContent('
hello
'); - const text = await page.$eval('css=section >> css=div', (e, suffix) => e.textContent + suffix, ' world!'); - expect(text).toBe('hello world!'); - }); - it('should support >> syntax with different engines', async({page, server}) => { - await page.setContent('
hello
'); - const text = await page.$eval('xpath=/html/body/section >> css=div >> text="hello"', (e, suffix) => e.textContent + suffix, ' world!'); - expect(text).toBe('hello world!'); - }); - it('should support spaces with >> syntax', async({page, server}) => { - await page.goto(server.PREFIX + '/deep-shadow.html'); - const text = await page.$eval(' css = div >>css=div>>css = span ', e => e.textContent); - expect(text).toBe('Hello from root2'); - }); - it('should not stop at first failure with >> syntax', async({page, server}) => { - await page.setContent('
Next
'); - const html = await page.$eval('button >> "Next"', e => e.outerHTML); - expect(html).toBe(''); - }); - it('should support * capture', async({page, server}) => { - await page.setContent('
a
b
'); - expect(await page.$eval('*css=div >> "b"', e => e.outerHTML)).toBe('
b
'); - expect(await page.$eval('section >> *css=div >> "b"', e => e.outerHTML)).toBe('
b
'); - 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' }]); - }); -}); - -describe('Page.$$eval', function() { - it('should work with css selector', async({page, server}) => { - await page.setContent('
hello
beautiful
world!
'); - const divsCount = await page.$$eval('css=div', divs => divs.length); - expect(divsCount).toBe(3); - }); - it('should work with text selector', async({page, server}) => { - await page.setContent('
hello
beautiful
beautiful
world!
'); - const divsCount = await page.$$eval('text="beautiful"', divs => divs.length); - expect(divsCount).toBe(2); - }); - it('should work with xpath selector', async({page, server}) => { - await page.setContent('
hello
beautiful
world!
'); - const divsCount = await page.$$eval('xpath=/html/body/div', divs => divs.length); - expect(divsCount).toBe(3); - }); - it('should auto-detect css selector', async({page, server}) => { - await page.setContent('
hello
beautiful
world!
'); - const divsCount = await page.$$eval('div', divs => divs.length); - expect(divsCount).toBe(3); - }); - it('should support >> syntax', async({page, server}) => { - await page.setContent('
hello
beautiful
world!
Not this one'); - const spansCount = await page.$$eval('css=div >> css=span', spans => spans.length); - expect(spansCount).toBe(3); - }); - it('should support * capture', async({page, server}) => { - await page.setContent('
a
b
'); - expect(await page.$$eval('*css=div >> "b"', els => els.length)).toBe(1); - expect(await page.$$eval('section >> *css=div >> "b"', els => els.length)).toBe(1); - expect(await page.$$eval('section >> *', els => els.length)).toBe(4); - - await page.setContent('
aa
'); - expect(await page.$$eval('*css=div >> "a"', els => els.length)).toBe(1); - expect(await page.$$eval('section >> *css=div >> "a"', els => els.length)).toBe(1); - - await page.setContent('
a
a
a
'); - expect(await page.$$eval('*css=div >> "a"', els => els.length)).toBe(3); - expect(await page.$$eval('section >> *css=div >> "a"', els => els.length)).toBe(1); - }); - it('should support * capture when multiple paths match', async({page, server}) => { - await page.setContent('
'); - expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2); - await page.setContent('
'); - expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2); - }); - it('should return complex values', async({page, server}) => { - await page.setContent('
hello
beautiful
world!
'); - const texts = await page.$$eval('css=div', divs => divs.map(div => div.textContent)); - expect(texts).toEqual(['hello', 'beautiful', 'world!']); - }); -}); - -describe('Page.$', function() { - it('should query existing element with css selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('css=section'); - expect(element).toBeTruthy(); - }); - it('should query existing element with text selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('text="test"'); - expect(element).toBeTruthy(); - }); - it('should query existing element with xpath selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('xpath=/html/body/section'); - expect(element).toBeTruthy(); - }); - it('should return null for non-existing element', async({page, server}) => { - const element = await page.$('non-existing-element'); - expect(element).toBe(null); - }); - it('should auto-detect xpath selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('//html/body/section'); - expect(element).toBeTruthy(); - }); - it('should auto-detect xpath selector with starting parenthesis', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('(//section)[1]'); - expect(element).toBeTruthy(); - }); - it('should auto-detect xpath selector starting with ..', async({page, server}) => { - await page.setContent('
test
'); - const span = await page.$('"test" >> ../span'); - expect(await span.evaluate(e => e.nodeName)).toBe('SPAN'); - const div = await page.$('"test" >> ..'); - expect(await div.evaluate(e => e.nodeName)).toBe('DIV'); - }); - it('should auto-detect text selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('"test"'); - expect(element).toBeTruthy(); - }); - it('should auto-detect css selector', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('section'); - expect(element).toBeTruthy(); - }); - it('should support >> syntax', async({page, server}) => { - await page.setContent('
test
'); - const element = await page.$('css=section >> css=div'); - expect(element).toBeTruthy(); - }); -}); - -describe('Page.$$', function() { - it('should query existing elements', async({page, server}) => { - await page.setContent('
A

B
'); - 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); - }); -}); - -describe('Page.$$ xpath', function() { - it('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('should return empty array for non-existing element', async({page, server}) => { - const element = await page.$$('//html/body/non-existing-element'); - expect(element).toEqual([]); - }); - it('should return multiple elements', async({page, server}) => { - await page.setContent('
'); - const elements = await page.$$('xpath=/html/body/div'); - expect(elements.length).toBe(2); - }); -}); - -describe('ElementHandle.$', function() { - it('should query existing element', async({page, server}) => { - await page.goto(server.PREFIX + '/playground.html'); - await page.setContent('
A
'); - const html = await page.$('html'); - const second = await html.$('.second'); - const inner = await second.$('.inner'); - const content = await page.evaluate(e => e.textContent, inner); - expect(content).toBe('A'); - }); - - it('should return null for non-existing element', async({page, server}) => { - await page.setContent('
B
'); - const html = await page.$('html'); - const second = await html.$('.third'); - expect(second).toBe(null); - }); - 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); - const span = document.createElement('span'); - span.textContent = 'hello'; - div.appendChild(span); - return div; - }); - expect(await divHandle.$('span')).toBeTruthy(); - expect(await divHandle.$eval('span', e => e.textContent)).toBe('hello'); - - await popup.waitForLoadState('domcontentloaded'); - await page.evaluate(() => { - const div = document.querySelector('div'); - window.__popup.document.body.appendChild(div); - }); - expect(await divHandle.$('span')).toBeTruthy(); - expect(await divHandle.$eval('span', e => e.textContent)).toBe('hello'); - }); -}); - -describe('ElementHandle.$eval', function() { - it('should work', async({page, server}) => { - await page.setContent('
10
'); - const tweet = await page.$('.tweet'); - const content = await tweet.$eval('.like', node => node.innerText); - expect(content).toBe('100'); - }); - - it('should retrieve content from subtree', async({page, server}) => { - const htmlContent = '
not-a-child-div
a-child-div
'; - await page.setContent(htmlContent); - const elementHandle = await page.$('#myId'); - const content = await elementHandle.$eval('.a', node => node.innerText); - expect(content).toBe('a-child-div'); - }); - - it('should throw in case of missing selector', async({page, server}) => { - const htmlContent = '
not-a-child-div
'; - await page.setContent(htmlContent); - const elementHandle = await page.$('#myId'); - const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message); - expect(errorMessage).toContain(`Error: failed to find element matching selector ".a"`); - }); -}); -describe('ElementHandle.$$eval', function() { - it('should work', async({page, server}) => { - await page.setContent('
'); - const tweet = await page.$('.tweet'); - const content = await tweet.$$eval('.like', nodes => nodes.map(n => n.innerText)); - expect(content).toEqual(['100', '10']); - }); - - it('should retrieve content from subtree', async({page, server}) => { - const htmlContent = '
not-a-child-div
a1-child-div
a2-child-div
'; - 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', 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); - }); - -}); - -describe('ElementHandle.$$', function() { - it('should query existing elements', async({page, server}) => { - await page.setContent('
A

B
'); - const html = await page.$('html'); - const elements = await html.$$('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 for non-existing elements', async({page, server}) => { - await page.setContent('A
B'); - const html = await page.$('html'); - const elements = await html.$$('div'); - expect(elements.length).toBe(0); - }); -}); - - -describe('ElementHandle.$$ xpath', function() { - it('should query existing element', async({page, server}) => { - await page.goto(server.PREFIX + '/playground.html'); - await page.setContent('
A
'); - const html = await page.$('html'); - const second = await html.$$(`xpath=./body/div[contains(@class, 'second')]`); - const inner = await second[0].$$(`xpath=./div[contains(@class, 'inner')]`); - const content = await page.evaluate(e => e.textContent, inner[0]); - expect(content).toBe('A'); - }); - - it('should return null for non-existing element', async({page, server}) => { - await page.setContent('
B
'); - const html = await page.$('html'); - const second = await html.$$(`xpath=/div[contains(@class, 'third')]`); - expect(second).toEqual([]); - }); -}); - -describe('text selector', () => { - 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('
x
'); - let error = await page.$(`"`).catch(e => e); - expect(error.message).toContain(WEBKIT ? 'SyntaxError' : 'querySelector'); - error = await page.$(`'`).catch(e => e); - expect(error.message).toContain(WEBKIT ? 'SyntaxError' : 'querySelector'); - - await page.setContent(`
'
"
`); - expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('
"
'); - expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('
\'
'); - - await page.setContent(`
Hi''>>foo=bar
`); - expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`
Hi''>>foo=bar
`); - await page.setContent(`
Hi'">>foo=bar
`); - expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`
Hi'">>foo=bar
`); - - await page.setContent(`
Hi>>
`); - expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(``); - - await page.setContent(`
a
b
a
`); - expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('
a
b
'); - expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('
a
b
'); - expect(await page.$(`text=ab`)).toBe(null); - expect(await page.$$eval(`text=a`, els => els.length)).toBe(2); - expect(await page.$$eval(`text=b`, els => els.length)).toBe(1); - expect(await page.$$eval(`text=ab`, els => els.length)).toBe(0); - - await page.setContent(`
`); - await page.$eval('div', div => { - div.appendChild(document.createTextNode('hello')); - div.appendChild(document.createTextNode('world')); - }); - await page.$eval('span', span => { - span.appendChild(document.createTextNode('hello')); - span.appendChild(document.createTextNode('world')); - }); - expect(await page.$eval(`text=lowo`, e => e.outerHTML)).toBe('
helloworld
'); - expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('
helloworld
helloworld'); - }); - - it('create', async ({playwright, page}) => { - await page.setContent(`
yo
"ya
ye ye
`); - expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo'); - expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('"\\"ya"'); - expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(3)'))).toBe('"ye ye"'); - - await page.setContent(`
yo
yo
ya
hey
`); - expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('hey'); - - await page.setContent(`
yo
ya
`); - expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo'); - - await page.setContent(`
"yo
ya
`); - expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('" \\"yo "'); - }); - - it('should be case sensitive if quotes are specified', async({page}) => { - await page.setContent(`
yo
ya
\nye
`); - expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('
ya
'); - expect(await page.$(`text="yA"`)).toBe(null); - }); - - it('should search for a substring without quotes', async({page}) => { - await page.setContent(`
textwithsubstring
`); - expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('
textwithsubstring
'); - expect(await page.$(`text="with"`)).toBe(null); - }); - - it('should skip head, script and style', async({page}) => { - await page.setContent(` - - title - - - - - - -
title script style
- `); - const head = await page.$('head'); - const title = await page.$('title'); - const script = await page.$('body script'); - const style = await page.$('body style'); - for (const text of ['title', 'script', 'style']) { - expect(await page.$eval(`text=${text}`, e => e.nodeName)).toBe('DIV'); - expect(await page.$$eval(`text=${text}`, els => els.map(e => e.nodeName).join('|'))).toBe('DIV'); - for (const root of [head, title, script, style]) { - expect(await root.$(`text=${text}`)).toBe(null); - expect(await root.$$eval(`text=${text}`, els => els.length)).toBe(0); - } - } - }); - - it('should match input[type=button|submit]', async({page}) => { - await page.setContent(``); - expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe(''); - expect(await page.$eval(`text=world`, e => e.outerHTML)).toBe(''); - }); - - it('should work for open shadow roots', async({page, server}) => { - await page.goto(server.PREFIX + '/deep-shadow.html'); - expect(await page.$eval(`text=root1`, e => e.textContent)).toBe('Hello from root1'); - expect(await page.$eval(`text=root2`, e => e.textContent)).toBe('Hello from root2'); - expect(await page.$eval(`text=root3`, e => e.textContent)).toBe('Hello from root3'); - expect(await page.$eval(`#root1 >> text=from root3`, e => e.textContent)).toBe('Hello from root3'); - expect(await page.$eval(`#target >> text=from root2`, e => e.textContent)).toBe('Hello from root2'); - expect(await page.$(`text:light=root1`)).toBe(null); - expect(await page.$(`text:light=root2`)).toBe(null); - expect(await page.$(`text:light=root3`)).toBe(null); - }); - - it('should prioritize light dom over shadow dom in the same parent', async({page, server}) => { - await page.evaluate(() => { - const div = document.createElement('div'); - document.body.appendChild(div); - - div.attachShadow({ mode: 'open' }); - const shadowSpan = document.createElement('span'); - shadowSpan.textContent = 'Hello from shadow'; - div.shadowRoot.appendChild(shadowSpan); - - const lightSpan = document.createElement('span'); - lightSpan.textContent = 'Hello from light'; - div.appendChild(lightSpan); - }); - expect(await page.$eval(`div >> text=Hello`, e => e.textContent)).toBe('Hello from light'); - }); - - it('should waitForSelector with distributed elements', async({page, server}) => { - const promise = page.waitForSelector(`div >> text=Hello`); - await page.evaluate(() => { - const div = document.createElement('div'); - document.body.appendChild(div); - - div.attachShadow({ mode: 'open' }); - const shadowSpan = document.createElement('span'); - shadowSpan.textContent = 'Hello from shadow'; - div.shadowRoot.appendChild(shadowSpan); - div.shadowRoot.appendChild(document.createElement('slot')); - - const lightSpan = document.createElement('span'); - lightSpan.textContent = 'Hello from light'; - div.appendChild(lightSpan); - }); - const handle = await promise; - expect(await handle.textContent()).toBe('Hello from light'); - }); -}); - -describe('css selector', () => { - 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); - }); -}); - -describe('attribute selector', () => { - 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([]); - }); -}); - -describe('selectors.register', () => { - 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('
'); - expect(await playwright.selectors._createSelector('tag', await page.$('div'))).toBe('DIV'); - expect(await page.$eval('tag=DIV', e => e.nodeName)).toBe('DIV'); - expect(await page.$eval('tag=SPAN', e => e.nodeName)).toBe('SPAN'); - expect(await page.$$eval('tag=DIV', es => es.length)).toBe(2); - - // Selector names are case-sensitive. - const error = await page.$('tAG=DIV').catch(e => e); - expect(error.message).toContain('Unknown engine "tAG" while parsing selector tAG=DIV'); - }); - it('should work with path', async ({playwright, page}) => { - await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); - await page.setContent('
'); - expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION'); - }); - it('should work in main and isolated world', async ({playwright, page}) => { - const createDummySelector = () => ({ - create(root, target) { }, - query(root, selector) { - return window.__answer; - }, - queryAll(root, selector) { - return [document.body, document.documentElement, window.__answer]; - } - }); - await utils.registerEngine(playwright, 'main', createDummySelector); - await utils.registerEngine(playwright, 'isolated', createDummySelector, { contentScript: true }); - 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/queryselector.spec.js b/test/queryselector.spec.js new file mode 100644 index 0000000000..8d872f269b --- /dev/null +++ b/test/queryselector.spec.js @@ -0,0 +1,113 @@ +/** + * 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 with css selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('css=section'); + expect(element).toBeTruthy(); +}); + +it('should query existing element with text selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('text="test"'); + expect(element).toBeTruthy(); +}); + +it('should query existing element with xpath selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('xpath=/html/body/section'); + expect(element).toBeTruthy(); +}); + +it('should return null for non-existing element', async({page, server}) => { + const element = await page.$('non-existing-element'); + expect(element).toBe(null); +}); + +it('should auto-detect xpath selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('//html/body/section'); + expect(element).toBeTruthy(); +}); + +it('should auto-detect xpath selector with starting parenthesis', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('(//section)[1]'); + expect(element).toBeTruthy(); +}); + +it('should auto-detect xpath selector starting with ..', async({page, server}) => { + await page.setContent('
test
'); + const span = await page.$('"test" >> ../span'); + expect(await span.evaluate(e => e.nodeName)).toBe('SPAN'); + const div = await page.$('"test" >> ..'); + expect(await div.evaluate(e => e.nodeName)).toBe('DIV'); +}); + +it('should auto-detect text selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('"test"'); + expect(element).toBeTruthy(); +}); + +it('should auto-detect css selector', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('section'); + expect(element).toBeTruthy(); +}); + +it('should support >> syntax', async({page, server}) => { + await page.setContent('
test
'); + const element = await page.$('css=section >> css=div'); + expect(element).toBeTruthy(); +}); + +it('should query existing elements', async({page, server}) => { + await page.setContent('
A

B
'); + 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('
'); + expect(await playwright.selectors._createSelector('tag', await page.$('div'))).toBe('DIV'); + expect(await page.$eval('tag=DIV', e => e.nodeName)).toBe('DIV'); + expect(await page.$eval('tag=SPAN', e => e.nodeName)).toBe('SPAN'); + expect(await page.$$eval('tag=DIV', es => es.length)).toBe(2); + + // Selector names are case-sensitive. + const error = await page.$('tAG=DIV').catch(e => e); + expect(error.message).toContain('Unknown engine "tAG" while parsing selector tAG=DIV'); +}); + +it('should work with path', async ({playwright, page}) => { + await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); + await page.setContent('
'); + expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION'); +}); + +it('should work in main and isolated world', async ({playwright, page}) => { + const createDummySelector = () => ({ + create(root, target) { }, + query(root, selector) { + return window.__answer; + }, + queryAll(root, selector) { + return [document.body, document.documentElement, window.__answer]; + } + }); + await utils.registerEngine(playwright, 'main', createDummySelector); + await utils.registerEngine(playwright, 'isolated', createDummySelector, { contentScript: true }); + 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('
x
'); + let error = await page.$(`"`).catch(e => e); + expect(error.message).toContain(WEBKIT ? 'SyntaxError' : 'querySelector'); + error = await page.$(`'`).catch(e => e); + expect(error.message).toContain(WEBKIT ? 'SyntaxError' : 'querySelector'); + + await page.setContent(`
'
"
`); + expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('
"
'); + expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('
\'
'); + + await page.setContent(`
Hi''>>foo=bar
`); + expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`
Hi''>>foo=bar
`); + await page.setContent(`
Hi'">>foo=bar
`); + expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`
Hi'">>foo=bar
`); + + await page.setContent(`
Hi>>
`); + expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(``); + + await page.setContent(`
a
b
a
`); + expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('
a
b
'); + expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('
a
b
'); + expect(await page.$(`text=ab`)).toBe(null); + expect(await page.$$eval(`text=a`, els => els.length)).toBe(2); + expect(await page.$$eval(`text=b`, els => els.length)).toBe(1); + expect(await page.$$eval(`text=ab`, els => els.length)).toBe(0); + + await page.setContent(`
`); + await page.$eval('div', div => { + div.appendChild(document.createTextNode('hello')); + div.appendChild(document.createTextNode('world')); + }); + await page.$eval('span', span => { + span.appendChild(document.createTextNode('hello')); + span.appendChild(document.createTextNode('world')); + }); + expect(await page.$eval(`text=lowo`, e => e.outerHTML)).toBe('
helloworld
'); + expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('
helloworld
helloworld'); +}); + +it('create', async ({playwright, page}) => { + await page.setContent(`
yo
"ya
ye ye
`); + expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo'); + expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('"\\"ya"'); + expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(3)'))).toBe('"ye ye"'); + + await page.setContent(`
yo
yo
ya
hey
`); + expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('hey'); + + await page.setContent(`
yo
ya
`); + expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo'); + + await page.setContent(`
"yo
ya
`); + expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('" \\"yo "'); +}); + +it('should be case sensitive if quotes are specified', async({page}) => { + await page.setContent(`
yo
ya
\nye
`); + expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('
ya
'); + expect(await page.$(`text="yA"`)).toBe(null); +}); + +it('should search for a substring without quotes', async({page}) => { + await page.setContent(`
textwithsubstring
`); + expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('
textwithsubstring
'); + expect(await page.$(`text="with"`)).toBe(null); +}); + +it('should skip head, script and style', async({page}) => { + await page.setContent(` + + title + + + + + + +
title script style
+ `); + const head = await page.$('head'); + const title = await page.$('title'); + const script = await page.$('body script'); + const style = await page.$('body style'); + for (const text of ['title', 'script', 'style']) { + expect(await page.$eval(`text=${text}`, e => e.nodeName)).toBe('DIV'); + expect(await page.$$eval(`text=${text}`, els => els.map(e => e.nodeName).join('|'))).toBe('DIV'); + for (const root of [head, title, script, style]) { + expect(await root.$(`text=${text}`)).toBe(null); + expect(await root.$$eval(`text=${text}`, els => els.length)).toBe(0); + } + } +}); + +it('should match input[type=button|submit]', async({page}) => { + await page.setContent(``); + expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe(''); + expect(await page.$eval(`text=world`, e => e.outerHTML)).toBe(''); +}); + +it('should work for open shadow roots', async({page, server}) => { + await page.goto(server.PREFIX + '/deep-shadow.html'); + expect(await page.$eval(`text=root1`, e => e.textContent)).toBe('Hello from root1'); + expect(await page.$eval(`text=root2`, e => e.textContent)).toBe('Hello from root2'); + expect(await page.$eval(`text=root3`, e => e.textContent)).toBe('Hello from root3'); + expect(await page.$eval(`#root1 >> text=from root3`, e => e.textContent)).toBe('Hello from root3'); + expect(await page.$eval(`#target >> text=from root2`, e => e.textContent)).toBe('Hello from root2'); + expect(await page.$(`text:light=root1`)).toBe(null); + expect(await page.$(`text:light=root2`)).toBe(null); + expect(await page.$(`text:light=root3`)).toBe(null); +}); + +it('should prioritize light dom over shadow dom in the same parent', async({page, server}) => { + await page.evaluate(() => { + const div = document.createElement('div'); + document.body.appendChild(div); + + div.attachShadow({ mode: 'open' }); + const shadowSpan = document.createElement('span'); + shadowSpan.textContent = 'Hello from shadow'; + div.shadowRoot.appendChild(shadowSpan); + + const lightSpan = document.createElement('span'); + lightSpan.textContent = 'Hello from light'; + div.appendChild(lightSpan); + }); + expect(await page.$eval(`div >> text=Hello`, e => e.textContent)).toBe('Hello from light'); +}); + +it('should waitForSelector with distributed elements', async({page, server}) => { + const promise = page.waitForSelector(`div >> text=Hello`); + await page.evaluate(() => { + const div = document.createElement('div'); + document.body.appendChild(div); + + div.attachShadow({ mode: 'open' }); + const shadowSpan = document.createElement('span'); + shadowSpan.textContent = 'Hello from shadow'; + div.shadowRoot.appendChild(shadowSpan); + div.shadowRoot.appendChild(document.createElement('slot')); + + const lightSpan = document.createElement('span'); + lightSpan.textContent = 'Hello from light'; + div.appendChild(lightSpan); + }); + const handle = await promise; + expect(await handle.textContent()).toBe('Hello from light'); +});