test: remove describes (2) (#3276)
This commit is contained in:
parent
e481f378f1
commit
bb267356fd
110
test/elementhandle-bounding-box.spec.js
Normal file
110
test/elementhandle-bounding-box.spec.js
Normal file
|
|
@ -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('<div style="display:none">hi</div>');
|
||||
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('<div style="width: 100px; height: 100px">hello</div>');
|
||||
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(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<rect id="theRect" x="30" y="50" width="200" height="300"></rect>
|
||||
</svg>
|
||||
`);
|
||||
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(`
|
||||
<style>
|
||||
i {
|
||||
position: absolute;
|
||||
top: -1000px;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<span><i>woof</i><b>doggo</b></span>
|
||||
`);
|
||||
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));
|
||||
});
|
||||
87
test/elementhandle-click.spec.js
Normal file
87
test/elementhandle-click.spec.js
Normal file
|
|
@ -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 <br> elements with force', async ({ page, server }) => {
|
||||
await page.setContent('hello<br>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');
|
||||
});
|
||||
55
test/elementhandle-content-frame.spec.js
Normal file
55
test/elementhandle-content-frame.spec.js
Normal file
|
|
@ -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);
|
||||
});
|
||||
154
test/elementhandle-convenience.spec.js
Normal file
154
test/elementhandle-convenience.spec.js
Normal file
|
|
@ -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@<div id="outer" name="value">…</div>');
|
||||
expect(String(inner)).toBe('JSHandle@<div id="inner">Text,↵more text</div>');
|
||||
expect(String(text)).toBe('JSHandle@#text=Text,↵more text');
|
||||
expect(String(check)).toBe('JSHandle@<input checked id="check" foo="bar"" type="checkbox"/>');
|
||||
});
|
||||
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('<div id="inner">Text,\nmore text</div>');
|
||||
expect(await page.innerHTML('#outer')).toBe('<div id="inner">Text,\nmore text</div>');
|
||||
});
|
||||
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(`<svg>text</svg>`);
|
||||
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(`<div>Hello</div>`);
|
||||
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(`<div>Hello</div>`);
|
||||
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(`<div>Hello<span>world</span></div>`);
|
||||
const tc = await page.innerHTML('innerHTML=div');
|
||||
expect(tc).toBe('Hello<span>world</span>');
|
||||
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(`<div foo=hello></div>`);
|
||||
const tc = await page.getAttribute('getAttribute=div', 'foo');
|
||||
expect(tc).toBe('hello');
|
||||
expect(await page.evaluate(() => document.querySelector('div').getAttribute('foo'))).toBe('modified');
|
||||
});
|
||||
66
test/elementhandle-eval-on-selector.spec.js
Normal file
66
test/elementhandle-eval-on-selector.spec.js
Normal file
|
|
@ -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('<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>');
|
||||
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 = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></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 = '<div class="a">not-a-child-div</div><div id="myId"></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('<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>');
|
||||
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 = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></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 = '<div class="a">not-a-child-div</div><div id="myId"></div>';
|
||||
await page.setContent(htmlContent);
|
||||
const elementHandle = await page.$('#myId');
|
||||
const nodesLength = await elementHandle.$$eval('.a', nodes => nodes.length);
|
||||
expect(nodesLength).toBe(0);
|
||||
});
|
||||
79
test/elementhandle-misc.spec.js
Normal file
79
test/elementhandle-misc.spec.js
Normal file
|
|
@ -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(`<input id='checkbox' type='checkbox'></input>`);
|
||||
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(`<input id='checkbox' type='checkbox' checked></input>`);
|
||||
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);
|
||||
});
|
||||
88
test/elementhandle-owner-frame.spec.js
Normal file
88
test/elementhandle-owner-frame.spec.js
Normal file
|
|
@ -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());
|
||||
});
|
||||
55
test/elementhandle-press.spec.js
Normal file
55
test/elementhandle-press.spec.js
Normal file
|
|
@ -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(`<input type='text' />`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='text' value='hello' /><div tabIndex=2>text</div>`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='number' value=2 />`);
|
||||
await page.press('input', '1');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('12');
|
||||
});
|
||||
97
test/elementhandle-query-selector.spec.js
Normal file
97
test/elementhandle-query-selector.spec.js
Normal file
|
|
@ -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('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
|
||||
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('<html><body><div>A</div><br/><div>B</div></body></html>');
|
||||
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('<html><body><span>A</span><br/><span>B</span></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
|
||||
const html = await page.$('html');
|
||||
const second = await html.$$(`xpath=/div[contains(@class, 'third')]`);
|
||||
expect(second).toEqual([]);
|
||||
});
|
||||
96
test/elementhandle-scroll-into-view.spec.js
Normal file
96
test/elementhandle-scroll-into-view.spec.js
Normal file
|
|
@ -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('<div>Hello</div>');
|
||||
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('<div style="display:none">Hello</div>');
|
||||
await testWaiting(page, div => div.style.display = 'block');
|
||||
});
|
||||
it('should wait for display:contents to become visible', async ({ page, server }) => {
|
||||
await page.setContent('<div style="display:contents">Hello</div>');
|
||||
await testWaiting(page, div => div.style.display = 'block');
|
||||
});
|
||||
it('should wait for visibility:hidden to become visible', async ({ page, server }) => {
|
||||
await page.setContent('<div style="visibility:hidden">Hello</div>');
|
||||
await testWaiting(page, div => div.style.visibility = 'visible');
|
||||
});
|
||||
it('should wait for zero-sized element to become visible', async ({ page, server }) => {
|
||||
await page.setContent('<div style="height:0">Hello</div>');
|
||||
await testWaiting(page, div => div.style.height = '100px');
|
||||
});
|
||||
it('should wait for nested display:none to become visible', async ({ page, server }) => {
|
||||
await page.setContent('<span style="display:none"><div>Hello</div></span>');
|
||||
await testWaiting(page, div => div.parentElement.style.display = 'block');
|
||||
});
|
||||
it('should wait for element to stop moving', async ({ page, server }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
@keyframes move {
|
||||
from { margin-left: 0; }
|
||||
to { margin-left: 200px; }
|
||||
}
|
||||
div.animated {
|
||||
animation: 2s linear 0s infinite alternate move;
|
||||
}
|
||||
</style>
|
||||
<div class=animated>moving</div>
|
||||
`);
|
||||
await testWaiting(page, div => div.classList.remove('animated'));
|
||||
});
|
||||
|
||||
it('should timeout waiting for visible', async ({ page, server }) => {
|
||||
await page.setContent('<div style="display:none">Hello</div>');
|
||||
const div = await page.$('div');
|
||||
const error = await div.scrollIntoViewIfNeeded({ timeout: 3000 }).catch(e => e);
|
||||
expect(error.message).toContain('element is not visible');
|
||||
});
|
||||
69
test/elementhandle-select-text.spec.js
Normal file
69
test/elementhandle-select-text.spec.js
Normal file
|
|
@ -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;
|
||||
});
|
||||
55
test/elementhandle-type.spec.js
Normal file
55
test/elementhandle-type.spec.js
Normal file
|
|
@ -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(`<input type='text' />`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='text' value='hello' /><div tabIndex=2>text</div>`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='number' value=2 />`);
|
||||
await page.type('input', '13');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('132');
|
||||
});
|
||||
|
|
@ -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('<div style="display:none">hi</div>');
|
||||
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('<div style="width: 100px; height: 100px">hello</div>');
|
||||
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(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<rect id="theRect" x="30" y="50" width="200" height="300"></rect>
|
||||
</svg>
|
||||
`);
|
||||
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(`
|
||||
<style>
|
||||
i {
|
||||
position: absolute;
|
||||
top: -1000px;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
<span><i>woof</i><b>doggo</b></span>
|
||||
`);
|
||||
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 <br> elements with force', async({page, server}) => {
|
||||
await page.setContent('hello<br>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('<div>Hello</div>');
|
||||
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('<div style="display:none">Hello</div>');
|
||||
await testWaiting(page, div => div.style.display = 'block');
|
||||
});
|
||||
it('should wait for display:contents to become visible', async({page, server}) => {
|
||||
await page.setContent('<div style="display:contents">Hello</div>');
|
||||
await testWaiting(page, div => div.style.display = 'block');
|
||||
});
|
||||
it('should wait for visibility:hidden to become visible', async({page, server}) => {
|
||||
await page.setContent('<div style="visibility:hidden">Hello</div>');
|
||||
await testWaiting(page, div => div.style.visibility = 'visible');
|
||||
});
|
||||
it('should wait for zero-sized element to become visible', async({page, server}) => {
|
||||
await page.setContent('<div style="height:0">Hello</div>');
|
||||
await testWaiting(page, div => div.style.height = '100px');
|
||||
});
|
||||
it('should wait for nested display:none to become visible', async({page, server}) => {
|
||||
await page.setContent('<span style="display:none"><div>Hello</div></span>');
|
||||
await testWaiting(page, div => div.parentElement.style.display = 'block');
|
||||
});
|
||||
it('should wait for element to stop moving', async({page, server}) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
@keyframes move {
|
||||
from { margin-left: 0; }
|
||||
to { margin-left: 200px; }
|
||||
}
|
||||
div.animated {
|
||||
animation: 2s linear 0s infinite alternate move;
|
||||
}
|
||||
</style>
|
||||
<div class=animated>moving</div>
|
||||
`);
|
||||
await testWaiting(page, div => div.classList.remove('animated'));
|
||||
});
|
||||
|
||||
it('should timeout waiting for visible', async({page, server}) => {
|
||||
await page.setContent('<div style="display:none">Hello</div>');
|
||||
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@<div id="outer" name="value">…</div>');
|
||||
expect(String(inner)).toBe('JSHandle@<div id="inner">Text,↵more text</div>');
|
||||
expect(String(text)).toBe('JSHandle@#text=Text,↵more text');
|
||||
expect(String(check)).toBe('JSHandle@<input checked id="check" foo="bar"" type="checkbox"/>');
|
||||
});
|
||||
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('<div id="inner">Text,\nmore text</div>');
|
||||
expect(await page.innerHTML('#outer')).toBe('<div id="inner">Text,\nmore text</div>');
|
||||
});
|
||||
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(`<svg>text</svg>`);
|
||||
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(`<div>Hello</div>`);
|
||||
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(`<div>Hello</div>`);
|
||||
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(`<div>Hello<span>world</span></div>`);
|
||||
const tc = await page.innerHTML('innerHTML=div');
|
||||
expect(tc).toBe('Hello<span>world</span>');
|
||||
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(`<div foo=hello></div>`);
|
||||
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(`<input id='checkbox' type='checkbox'></input>`);
|
||||
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(`<input id='checkbox' type='checkbox' checked></input>`);
|
||||
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(`<input type='text' />`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='text' value='hello' /><div tabIndex=2>text</div>`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='number' value=2 />`);
|
||||
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(`<input type='text' />`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='text' value='hello' /><div tabIndex=2>text</div>`);
|
||||
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(`<input type='text' value='hello' />`);
|
||||
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(`<input type='number' value=2 />`);
|
||||
await page.press('input', '1');
|
||||
expect(await page.$eval('input', input => input.value)).toBe('12');
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
};
|
||||
71
test/eval-on-selector-all.spec.js
Normal file
71
test/eval-on-selector-all.spec.js
Normal file
|
|
@ -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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
const divsCount = await page.$$eval('div', divs => divs.length);
|
||||
expect(divsCount).toBe(3);
|
||||
});
|
||||
it('should support >> syntax', async({page, server}) => {
|
||||
await page.setContent('<div><span>hello</span></div><div>beautiful</div><div><span>wo</span><span>rld!</span></div><span>Not this one</span>');
|
||||
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('<section><div><span>a</span></div></section><section><div><span>b</span></div></section>');
|
||||
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('<section><div><span>a</span><span>a</span></div></section>');
|
||||
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('<div><span>a</span></div><div><span>a</span></div><section><div><span>a</span></div></section>');
|
||||
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('<div><div><span></span></div></div><div></div>');
|
||||
expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2);
|
||||
await page.setContent('<div><div><span></span></div><span></span><span></span></div><div></div>');
|
||||
expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2);
|
||||
});
|
||||
it('should return complex values', async({page, server}) => {
|
||||
await page.setContent('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
const texts = await page.$$eval('css=div', divs => divs.map(div => div.textContent));
|
||||
expect(texts).toEqual(['hello', 'beautiful', 'world!']);
|
||||
});
|
||||
204
test/eval-on-selector.spec.js
Normal file
204
test/eval-on-selector.spec.js
Normal file
|
|
@ -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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section data-test=foo id="testAttribute">43543</section>');
|
||||
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('<section data-testid=foo id="testAttribute">43543</section>');
|
||||
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('<section data-test-id=foo id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<div foo=bar><section>43543<span>Hello<div id=target></div></span></section></div>');
|
||||
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('<section>hello</section>');
|
||||
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('<section>hello</section><div> world</div>');
|
||||
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('<section><div>hello</div></section>');
|
||||
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('<section><div><span>hello</span></div></section>');
|
||||
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('<div><span>Next</span><button>Previous</button><button>Next</button></div>');
|
||||
const html = await page.$eval('button >> "Next"', e => e.outerHTML);
|
||||
expect(html).toBe('<button>Next</button>');
|
||||
});
|
||||
|
||||
it('should support * capture', async({page, server}) => {
|
||||
await page.setContent('<section><div><span>a</span></div></section><section><div><span>b</span></div></section>');
|
||||
expect(await page.$eval('*css=div >> "b"', e => e.outerHTML)).toBe('<div><span>b</span></div>');
|
||||
expect(await page.$eval('section >> *css=div >> "b"', e => e.outerHTML)).toBe('<div><span>b</span></div>');
|
||||
expect(await page.$eval('css=div >> *text="b"', e => e.outerHTML)).toBe('<span>b</span>');
|
||||
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('<div><input placeholder="Select date"></div>');
|
||||
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('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`input[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`input[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=input[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=input[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`div >> [placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`div >> [placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
});
|
||||
|
||||
it('should work with quotes in css attributes', async({page, server}) => {
|
||||
await page.setContent('<div><input placeholder="Select"date"></div>');
|
||||
expect(await page.$(`[placeholder="Select\\"date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select"date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select " date"></div>');
|
||||
expect(await page.$(`[placeholder="Select \\" date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select " date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select'date"></div>');
|
||||
expect(await page.$(`[placeholder="Select'date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select\\'date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select ' date"></div>');
|
||||
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('<div><input placeholder="Select date"></div>');
|
||||
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('<div><input placeholder="Select"date"></div>');
|
||||
await inputPromise;
|
||||
});
|
||||
|
||||
it('should return complex values', async({page, server}) => {
|
||||
await page.setContent('<section id="testAttribute">43543</section>');
|
||||
const idAttribute = await page.$eval('css=section', e => [{ id: e.id }]);
|
||||
expect(idAttribute).toEqual([{ id: 'testAttribute' }]);
|
||||
});
|
||||
|
|
@ -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('<section>42</section>');
|
||||
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('<section>39</section>');
|
||||
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('<div>Hi, I\'m frame</div>');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
@ -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(`<div id=d1 tabIndex=0></div>`);
|
||||
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(`<div id=d1 tabIndex=0></div>`);
|
||||
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(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
|
||||
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(`<input id="i1"><input id="i2">`);
|
||||
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(`<input value="1"><input value="2"><input value="3">`);
|
||||
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(`
|
||||
<input id="input-1">
|
||||
<button id="button">buttton</button>
|
||||
<a href id="link">link</a>
|
||||
<input id="input-2">
|
||||
`);
|
||||
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');
|
||||
});
|
||||
});
|
||||
101
test/focus.spec.js
Normal file
101
test/focus.spec.js
Normal file
|
|
@ -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(`<div id=d1 tabIndex=0></div>`);
|
||||
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(`<div id=d1 tabIndex=0></div>`);
|
||||
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(`<div id=d1 tabIndex=0>DIV1</div><div id=d2 tabIndex=0>DIV2</div>`);
|
||||
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(`<input id="i1"><input id="i2">`);
|
||||
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(`<input value="1"><input value="2"><input value="3">`);
|
||||
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(`
|
||||
<input id="input-1">
|
||||
<button id="button">buttton</button>
|
||||
<a href id="link">link</a>
|
||||
<input id="input-2">
|
||||
`);
|
||||
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');
|
||||
});
|
||||
173
test/frame-evaluate.spec.js
Normal file
173
test/frame-evaluate.spec.js
Normal file
|
|
@ -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('<div>Hi, I\'m frame</div>');
|
||||
});
|
||||
|
||||
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(`
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'none';">
|
||||
<iframe src='javascript:""'></iframe>
|
||||
`, { 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('<div>hello</div>');
|
||||
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();
|
||||
});
|
||||
49
test/frame-frame-element.spec.js
Normal file
49
test/frame-frame-element.spec.js
Normal file
|
|
@ -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.');
|
||||
});
|
||||
179
test/frame-hierarcy.spec.js
Normal file
179
test/frame-hierarcy.spec.js
Normal file
|
|
@ -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:<PORT>/frames/nested-frames.html',
|
||||
' http://localhost:<PORT>/frames/frame.html (aframe)',
|
||||
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
|
||||
' http://localhost:<PORT>/frames/frame.html (dos)',
|
||||
' http://localhost:<PORT>/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(`<!DOCTYPE html><html><head><title>login</title></head><body style="background-color: red;"><p>dangerous login page</p></body></html>`);
|
||||
});
|
||||
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(`<iframe src="${server.CROSS_PROCESS_PREFIX}/x-frame-options-deny.html"></iframe>`);
|
||||
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)
|
||||
});
|
||||
|
|
@ -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(`
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'none';">
|
||||
<iframe src='javascript:""'></iframe>
|
||||
`, { 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('<div>hello</div>');
|
||||
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:<PORT>/frames/nested-frames.html',
|
||||
' http://localhost:<PORT>/frames/frame.html (aframe)',
|
||||
' http://localhost:<PORT>/frames/two-frames.html (2frames)',
|
||||
' http://localhost:<PORT>/frames/frame.html (dos)',
|
||||
' http://localhost:<PORT>/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(`<!DOCTYPE html><html><head><title>login</title></head><body style="background-color: red;"><p>dangerous login page</p></body></html>`);
|
||||
});
|
||||
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(`<iframe src="${server.CROSS_PROCESS_PREFIX}/x-frame-options-deny.html"></iframe>`);
|
||||
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)
|
||||
});
|
||||
});
|
||||
|
|
@ -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 });
|
||||
});
|
||||
});
|
||||
148
test/geolocation.spec.js
Normal file
148
test/geolocation.spec.js
Normal file
|
|
@ -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 });
|
||||
});
|
||||
|
|
@ -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(`<button>Hello</button><a target=_blank href="${server.EMPTY_PAGE}">empty.html</a>`);
|
||||
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();
|
||||
});
|
||||
});
|
||||
162
test/headful.spec.js
Normal file
162
test/headful.spec.js
Normal file
|
|
@ -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(`<button>Hello</button><a target=_blank href="${server.EMPTY_PAGE}">empty.html</a>`);
|
||||
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();
|
||||
});
|
||||
|
|
@ -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(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
89
test/ignorehttpserrors.spec.js
Normal file
89
test/ignorehttpserrors.spec.js
Normal file
|
|
@ -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(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
|
||||
});
|
||||
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();
|
||||
});
|
||||
|
|
@ -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('<textarea></textarea>');
|
||||
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;
|
||||
}
|
||||
408
test/keyboard.spec.js
Normal file
408
test/keyboard.spec.js
Normal file
|
|
@ -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('<textarea></textarea>');
|
||||
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;
|
||||
}
|
||||
|
|
@ -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('<button>Button</button>');
|
||||
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();
|
||||
});
|
||||
});
|
||||
51
test/logger.spec.js
Normal file
51
test/logger.spec.js
Normal file
|
|
@ -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('<button>Button</button>');
|
||||
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();
|
||||
});
|
||||
|
|
@ -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(`<div style='width: 100px; height: 100px;'>Click me</div>`);
|
||||
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');
|
||||
})
|
||||
});
|
||||
});
|
||||
187
test/mouse.spec.js
Normal file
187
test/mouse.spec.js
Normal file
|
|
@ -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(`<div style='width: 100px; height: 100px;'>Click me</div>`);
|
||||
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');
|
||||
})
|
||||
});
|
||||
99
test/page-add-init-script.spec.js
Normal file
99
test/page-add-init-script.spec.js
Normal file
|
|
@ -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);
|
||||
});
|
||||
470
test/page-evaluate.spec.js
Normal file
470
test/page-evaluate.spec.js
Normal file
|
|
@ -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('<section>42</section>');
|
||||
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('<section>39</section>');
|
||||
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: {} });
|
||||
});
|
||||
184
test/page-popup-event.spec.js
Normal file
184
test/page-popup-event.spec.js
Normal file
|
|
@ -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('<a target=_blank rel="opener" href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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());
|
||||
});
|
||||
});
|
||||
146
test/permissions.spec.js
Normal file
146
test/permissions.spec.js
Normal file
|
|
@ -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());
|
||||
});
|
||||
|
|
@ -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('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
|
||||
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('<a target=_blank rel=noopener href="empty.html">link</a>');
|
||||
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('<a target=_blank rel="opener" href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
|
||||
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);
|
||||
});
|
||||
});
|
||||
241
test/popup.spec.js
Normal file
241
test/popup.spec.js
Normal file
|
|
@ -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('<a target=_blank rel=noopener href="/popup/popup.html">link</a>');
|
||||
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('<a target=_blank rel=noopener href="empty.html">link</a>');
|
||||
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');
|
||||
});
|
||||
|
|
@ -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('<html><title>Served by the proxy</title></html>');
|
||||
});
|
||||
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(`<html><title>${auth}</title></html>`);
|
||||
}
|
||||
});
|
||||
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('<html><title>Served by the proxy</title></html>');
|
||||
});
|
||||
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 = '<html><title>Served by the SOCKS proxy</title></html>';
|
||||
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();
|
||||
});
|
||||
});
|
||||
115
test/proxy.spec.js
Normal file
115
test/proxy.spec.js
Normal file
|
|
@ -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('<html><title>Served by the proxy</title></html>');
|
||||
});
|
||||
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(`<html><title>${auth}</title></html>`);
|
||||
}
|
||||
});
|
||||
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('<html><title>Served by the proxy</title></html>');
|
||||
});
|
||||
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 = '<html><title>Served by the SOCKS proxy</title></html>';
|
||||
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();
|
||||
});
|
||||
|
|
@ -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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section data-test=foo id="testAttribute">43543</section>');
|
||||
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('<section data-testid=foo id="testAttribute">43543</section>');
|
||||
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('<section data-test-id=foo id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<section id="testAttribute">43543</section>');
|
||||
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('<div foo=bar><section>43543<span>Hello<div id=target></div></span></section></div>');
|
||||
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('<section>hello</section>');
|
||||
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('<section>hello</section><div> world</div>');
|
||||
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('<section><div>hello</div></section>');
|
||||
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('<section><div><span>hello</span></div></section>');
|
||||
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('<div><span>Next</span><button>Previous</button><button>Next</button></div>');
|
||||
const html = await page.$eval('button >> "Next"', e => e.outerHTML);
|
||||
expect(html).toBe('<button>Next</button>');
|
||||
});
|
||||
it('should support * capture', async({page, server}) => {
|
||||
await page.setContent('<section><div><span>a</span></div></section><section><div><span>b</span></div></section>');
|
||||
expect(await page.$eval('*css=div >> "b"', e => e.outerHTML)).toBe('<div><span>b</span></div>');
|
||||
expect(await page.$eval('section >> *css=div >> "b"', e => e.outerHTML)).toBe('<div><span>b</span></div>');
|
||||
expect(await page.$eval('css=div >> *text="b"', e => e.outerHTML)).toBe('<span>b</span>');
|
||||
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('<div><input placeholder="Select date"></div>');
|
||||
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('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`input[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`input[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=input[placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`css=input[placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`div >> [placeholder="Select date"]`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
expect(await page.$eval(`div >> [placeholder='Select date']`, e => e.outerHTML)).toBe('<input placeholder="Select date">');
|
||||
});
|
||||
it('should work with quotes in css attributes', async({page, server}) => {
|
||||
await page.setContent('<div><input placeholder="Select"date"></div>');
|
||||
expect(await page.$(`[placeholder="Select\\"date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select"date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select " date"></div>');
|
||||
expect(await page.$(`[placeholder="Select \\" date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select " date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select'date"></div>');
|
||||
expect(await page.$(`[placeholder="Select'date"]`)).toBeTruthy();
|
||||
expect(await page.$(`[placeholder='Select\\'date']`)).toBeTruthy();
|
||||
await page.setContent('<div><input placeholder="Select ' date"></div>');
|
||||
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('<div><input placeholder="Select date"></div>');
|
||||
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('<div><input placeholder="Select"date"></div>');
|
||||
await inputPromise;
|
||||
});
|
||||
it('should return complex values', async({page, server}) => {
|
||||
await page.setContent('<section id="testAttribute">43543</section>');
|
||||
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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
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('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
const divsCount = await page.$$eval('div', divs => divs.length);
|
||||
expect(divsCount).toBe(3);
|
||||
});
|
||||
it('should support >> syntax', async({page, server}) => {
|
||||
await page.setContent('<div><span>hello</span></div><div>beautiful</div><div><span>wo</span><span>rld!</span></div><span>Not this one</span>');
|
||||
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('<section><div><span>a</span></div></section><section><div><span>b</span></div></section>');
|
||||
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('<section><div><span>a</span><span>a</span></div></section>');
|
||||
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('<div><span>a</span></div><div><span>a</span></div><section><div><span>a</span></div></section>');
|
||||
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('<div><div><span></span></div></div><div></div>');
|
||||
expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2);
|
||||
await page.setContent('<div><div><span></span></div><span></span><span></span></div><div></div>');
|
||||
expect(await page.$$eval('*css=div >> span', els => els.length)).toBe(2);
|
||||
});
|
||||
it('should return complex values', async({page, server}) => {
|
||||
await page.setContent('<div>hello</div><div>beautiful</div><div>world!</div>');
|
||||
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('<section>test</section>');
|
||||
const element = await page.$('css=section');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should query existing element with text selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('text="test"');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should query existing element with xpath selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
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('<section>test</section>');
|
||||
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('<section>test</section>');
|
||||
const element = await page.$('(//section)[1]');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should auto-detect xpath selector starting with ..', async({page, server}) => {
|
||||
await page.setContent('<div><section>test</section><span></span></div>');
|
||||
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('<section>test</section>');
|
||||
const element = await page.$('"test"');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should auto-detect css selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('section');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
it('should support >> syntax', async({page, server}) => {
|
||||
await page.setContent('<section><div>test</div></section>');
|
||||
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('<div>A</div><br/><div>B</div>');
|
||||
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('<section>test</section>');
|
||||
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('<div></div><div></div>');
|
||||
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('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
|
||||
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('<html><body><div class="tweet"><div class="like">100</div><div class="retweets">10</div></div></body></html>');
|
||||
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 = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a-child-div</div></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 = '<div class="a">not-a-child-div</div><div id="myId"></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('<html><body><div class="tweet"><div class="like">100</div><div class="like">10</div></div></body></html>');
|
||||
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 = '<div class="a">not-a-child-div</div><div id="myId"><div class="a">a1-child-div</div><div class="a">a2-child-div</div></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 = '<div class="a">not-a-child-div</div><div id="myId"></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('<html><body><div>A</div><br/><div>B</div></body></html>');
|
||||
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('<html><body><span>A</span><br/><span>B</span></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">A</div></div></body></html>');
|
||||
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('<html><body><div class="second"><div class="inner">B</div></div></body></html>');
|
||||
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(`<div>yo</div><div>ya</div><div>\nye </div>`);
|
||||
expect(await page.$eval(`text=ya`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=/^[ay]+$/`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=/Ya/i`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=ye`, e => e.outerHTML)).toBe('<div>\nye </div>');
|
||||
|
||||
await page.setContent(`<div> ye </div><div>ye</div>`);
|
||||
expect(await page.$eval(`text="ye"`, e => e.outerHTML)).toBe('<div>ye</div>');
|
||||
|
||||
await page.setContent(`<div>yo</div><div>"ya</div><div> hello world! </div>`);
|
||||
expect(await page.$eval(`text="\\"ya"`, e => e.outerHTML)).toBe('<div>"ya</div>');
|
||||
expect(await page.$eval(`text=/hello/`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||
expect(await page.$eval(`text=/^\\s*heLLo/i`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||
|
||||
await page.setContent(`<div>yo<div>ya</div>hey<div>hey</div></div>`);
|
||||
expect(await page.$eval(`text=hey`, e => e.outerHTML)).toBe('<div>yo<div>ya</div>hey<div>hey</div></div>');
|
||||
expect(await page.$eval(`text="yo">>text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text='yo'>> text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text="yo" >>text='ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text='yo' >> text='ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`'yo'>>"ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`"yo" >> 'ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
|
||||
await page.setContent(`<div>yo<span id="s1"></span></div><div>yo<span id="s2"></span><span id="s3"></span></div>`);
|
||||
expect(await page.$$eval(`text=yo`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s1"></span></div>\n<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
|
||||
await page.setContent(`<div>'</div><div>"</div><div>\\</div><div>x</div>`);
|
||||
expect(await page.$eval(`text='\\''`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`text='"'`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text="\\""`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text="'"`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`text="\\x"`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`text='\\x'`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`text='\\\\'`, e => e.outerHTML)).toBe('<div>\\</div>');
|
||||
expect(await page.$eval(`text="\\\\"`, e => e.outerHTML)).toBe('<div>\\</div>');
|
||||
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`"x"`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`'x'`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
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(`<div> ' </div><div> " </div>`);
|
||||
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div> " </div>');
|
||||
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div> \' </div>');
|
||||
|
||||
await page.setContent(`<div>Hi''>>foo=bar</div>`);
|
||||
expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi''>>foo=bar</div>`);
|
||||
await page.setContent(`<div>Hi'">>foo=bar</div>`);
|
||||
expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi'">>foo=bar</div>`);
|
||||
|
||||
await page.setContent(`<div>Hi>><span></span></div>`);
|
||||
expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
|
||||
await page.setContent(`<div>a<br>b</div><div>a</div>`);
|
||||
expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
|
||||
expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
|
||||
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(`<div></div><span></span>`);
|
||||
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('<div>helloworld</div>');
|
||||
expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('<div>helloworld</div><span>helloworld</span>');
|
||||
});
|
||||
|
||||
it('create', async ({playwright, page}) => {
|
||||
await page.setContent(`<div>yo</div><div>"ya</div><div>ye ye</div>`);
|
||||
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(`<div>yo</div><div>yo<div>ya</div>hey</div>`);
|
||||
expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('hey');
|
||||
|
||||
await page.setContent(`<div> yo <div></div>ya</div>`);
|
||||
expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo');
|
||||
|
||||
await page.setContent(`<div> "yo <div></div>ya</div>`);
|
||||
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(`<div>yo</div><div>ya</div><div>\nye </div>`);
|
||||
expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$(`text="yA"`)).toBe(null);
|
||||
});
|
||||
|
||||
it('should search for a substring without quotes', async({page}) => {
|
||||
await page.setContent(`<div>textwithsubstring</div>`);
|
||||
expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('<div>textwithsubstring</div>');
|
||||
expect(await page.$(`text="with"`)).toBe(null);
|
||||
});
|
||||
|
||||
it('should skip head, script and style', async({page}) => {
|
||||
await page.setContent(`
|
||||
<head>
|
||||
<title>title</title>
|
||||
<script>var script</script>
|
||||
<style>.style {}</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>var script</script>
|
||||
<style>.style {}</style>
|
||||
<div>title script style</div>
|
||||
</body>`);
|
||||
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(`<input type="submit" value="hello"><input type="button" value="world">`);
|
||||
expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe('<input type="submit" value="hello">');
|
||||
expect(await page.$eval(`text=world`, e => e.outerHTML)).toBe('<input type="button" value="world">');
|
||||
});
|
||||
|
||||
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(`<div foo="bar" bar="baz"><span></span></div>`);
|
||||
expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
});
|
||||
|
||||
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(`<section><span><div><x></x><y></y></div></span></section>`);
|
||||
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(`<span></span><div attr="hello,world!"></div>`);
|
||||
expect(await page.$eval(`css=div[attr="hello,world!"]`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=[attr="hello,world!"]`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=div[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
});
|
||||
|
||||
it('should work with attribute selectors', async({page}) => {
|
||||
await page.setContent(`<div attr="hello world" attr2="hello-''>>foo=bar[]" attr3="] span"><span></span></div>`);
|
||||
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('<div><span></span></div><div></div>');
|
||||
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('<section></section>');
|
||||
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('<div><span><section></section></span></div>');
|
||||
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');
|
||||
});
|
||||
});
|
||||
113
test/queryselector.spec.js
Normal file
113
test/queryselector.spec.js
Normal file
|
|
@ -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('<section>test</section>');
|
||||
const element = await page.$('css=section');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should query existing element with text selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('text="test"');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should query existing element with xpath selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
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('<section>test</section>');
|
||||
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('<section>test</section>');
|
||||
const element = await page.$('(//section)[1]');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should auto-detect xpath selector starting with ..', async({page, server}) => {
|
||||
await page.setContent('<div><section>test</section><span></span></div>');
|
||||
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('<section>test</section>');
|
||||
const element = await page.$('"test"');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should auto-detect css selector', async({page, server}) => {
|
||||
await page.setContent('<section>test</section>');
|
||||
const element = await page.$('section');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should support >> syntax', async({page, server}) => {
|
||||
await page.setContent('<section><div>test</div></section>');
|
||||
const element = await page.$('css=section >> css=div');
|
||||
expect(element).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should query existing elements', async({page, server}) => {
|
||||
await page.setContent('<div>A</div><br/><div>B</div>');
|
||||
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('<section>test</section>');
|
||||
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('<div></div><div></div>');
|
||||
const elements = await page.$$('xpath=/html/body/div');
|
||||
expect(elements.length).toBe(2);
|
||||
});
|
||||
124
test/selectors-css.spec.js
Normal file
124
test/selectors-css.spec.js
Normal file
|
|
@ -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(`<div foo="bar" bar="baz"><span></span></div>`);
|
||||
expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] > span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"]> span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
expect(await page.$eval(`div[foo="bar"][bar="baz"] >span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
});
|
||||
|
||||
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(`<section><span><div><x></x><y></y></div></span></section>`);
|
||||
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(`<span></span><div attr="hello,world!"></div>`);
|
||||
expect(await page.$eval(`css=div[attr="hello,world!"]`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=[attr="hello,world!"]`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=div[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=[attr='hello,world!']`, e => e.outerHTML)).toBe('<div attr="hello,world!"></div>');
|
||||
expect(await page.$eval(`css=div[attr="hello,world!"],span`, e => e.outerHTML)).toBe('<span></span>');
|
||||
});
|
||||
|
||||
it('should work with attribute selectors', async({page}) => {
|
||||
await page.setContent(`<div attr="hello world" attr2="hello-''>>foo=bar[]" attr3="] span"><span></span></div>`);
|
||||
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);
|
||||
});
|
||||
30
test/selectors-misc.spec.js
Normal file
30
test/selectors-misc.spec.js
Normal file
|
|
@ -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([]);
|
||||
});
|
||||
112
test/selectors-register.spec.js
Normal file
112
test/selectors-register.spec.js
Normal file
|
|
@ -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('<div><span></span></div><div></div>');
|
||||
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('<section></section>');
|
||||
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('<div><span><section></section></span></div>');
|
||||
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');
|
||||
});
|
||||
208
test/selectors-text.spec.js
Normal file
208
test/selectors-text.spec.js
Normal file
|
|
@ -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(`<div>yo</div><div>ya</div><div>\nye </div>`);
|
||||
expect(await page.$eval(`text=ya`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=/^[ay]+$/`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=/Ya/i`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text=ye`, e => e.outerHTML)).toBe('<div>\nye </div>');
|
||||
|
||||
await page.setContent(`<div> ye </div><div>ye</div>`);
|
||||
expect(await page.$eval(`text="ye"`, e => e.outerHTML)).toBe('<div>ye</div>');
|
||||
|
||||
await page.setContent(`<div>yo</div><div>"ya</div><div> hello world! </div>`);
|
||||
expect(await page.$eval(`text="\\"ya"`, e => e.outerHTML)).toBe('<div>"ya</div>');
|
||||
expect(await page.$eval(`text=/hello/`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||
expect(await page.$eval(`text=/^\\s*heLLo/i`, e => e.outerHTML)).toBe('<div> hello world! </div>');
|
||||
|
||||
await page.setContent(`<div>yo<div>ya</div>hey<div>hey</div></div>`);
|
||||
expect(await page.$eval(`text=hey`, e => e.outerHTML)).toBe('<div>yo<div>ya</div>hey<div>hey</div></div>');
|
||||
expect(await page.$eval(`text="yo">>text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text='yo'>> text="ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text="yo" >>text='ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`text='yo' >> text='ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`'yo'>>"ya"`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$eval(`"yo" >> 'ya'`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
|
||||
await page.setContent(`<div>yo<span id="s1"></span></div><div>yo<span id="s2"></span><span id="s3"></span></div>`);
|
||||
expect(await page.$$eval(`text=yo`, es => es.map(e => e.outerHTML).join('\n'))).toBe('<div>yo<span id="s1"></span></div>\n<div>yo<span id="s2"></span><span id="s3"></span></div>');
|
||||
|
||||
await page.setContent(`<div>'</div><div>"</div><div>\\</div><div>x</div>`);
|
||||
expect(await page.$eval(`text='\\''`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`text='"'`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text="\\""`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text="'"`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`text="\\x"`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`text='\\x'`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`text='\\\\'`, e => e.outerHTML)).toBe('<div>\\</div>');
|
||||
expect(await page.$eval(`text="\\\\"`, e => e.outerHTML)).toBe('<div>\\</div>');
|
||||
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div>"</div>');
|
||||
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div>\'</div>');
|
||||
expect(await page.$eval(`"x"`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
expect(await page.$eval(`'x'`, e => e.outerHTML)).toBe('<div>x</div>');
|
||||
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(`<div> ' </div><div> " </div>`);
|
||||
expect(await page.$eval(`text="`, e => e.outerHTML)).toBe('<div> " </div>');
|
||||
expect(await page.$eval(`text='`, e => e.outerHTML)).toBe('<div> \' </div>');
|
||||
|
||||
await page.setContent(`<div>Hi''>>foo=bar</div>`);
|
||||
expect(await page.$eval(`text="Hi''>>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi''>>foo=bar</div>`);
|
||||
await page.setContent(`<div>Hi'">>foo=bar</div>`);
|
||||
expect(await page.$eval(`text="Hi'\\">>foo=bar"`, e => e.outerHTML)).toBe(`<div>Hi'">>foo=bar</div>`);
|
||||
|
||||
await page.setContent(`<div>Hi>><span></span></div>`);
|
||||
expect(await page.$eval(`text="Hi>>">>span`, e => e.outerHTML)).toBe(`<span></span>`);
|
||||
|
||||
await page.setContent(`<div>a<br>b</div><div>a</div>`);
|
||||
expect(await page.$eval(`text=a`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
|
||||
expect(await page.$eval(`text=b`, e => e.outerHTML)).toBe('<div>a<br>b</div>');
|
||||
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(`<div></div><span></span>`);
|
||||
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('<div>helloworld</div>');
|
||||
expect(await page.$$eval(`text=lowo`, els => els.map(e => e.outerHTML).join(''))).toBe('<div>helloworld</div><span>helloworld</span>');
|
||||
});
|
||||
|
||||
it('create', async ({playwright, page}) => {
|
||||
await page.setContent(`<div>yo</div><div>"ya</div><div>ye ye</div>`);
|
||||
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(`<div>yo</div><div>yo<div>ya</div>hey</div>`);
|
||||
expect(await playwright.selectors._createSelector('text', await page.$('div:nth-child(2)'))).toBe('hey');
|
||||
|
||||
await page.setContent(`<div> yo <div></div>ya</div>`);
|
||||
expect(await playwright.selectors._createSelector('text', await page.$('div'))).toBe('yo');
|
||||
|
||||
await page.setContent(`<div> "yo <div></div>ya</div>`);
|
||||
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(`<div>yo</div><div>ya</div><div>\nye </div>`);
|
||||
expect(await page.$eval(`text=yA`, e => e.outerHTML)).toBe('<div>ya</div>');
|
||||
expect(await page.$(`text="yA"`)).toBe(null);
|
||||
});
|
||||
|
||||
it('should search for a substring without quotes', async({page}) => {
|
||||
await page.setContent(`<div>textwithsubstring</div>`);
|
||||
expect(await page.$eval(`text=with`, e => e.outerHTML)).toBe('<div>textwithsubstring</div>');
|
||||
expect(await page.$(`text="with"`)).toBe(null);
|
||||
});
|
||||
|
||||
it('should skip head, script and style', async({page}) => {
|
||||
await page.setContent(`
|
||||
<head>
|
||||
<title>title</title>
|
||||
<script>var script</script>
|
||||
<style>.style {}</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>var script</script>
|
||||
<style>.style {}</style>
|
||||
<div>title script style</div>
|
||||
</body>`);
|
||||
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(`<input type="submit" value="hello"><input type="button" value="world">`);
|
||||
expect(await page.$eval(`text=hello`, e => e.outerHTML)).toBe('<input type="submit" value="hello">');
|
||||
expect(await page.$eval(`text=world`, e => e.outerHTML)).toBe('<input type="button" value="world">');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
Loading…
Reference in a new issue