diff --git a/test/elementhandle-screenshot.spec.js b/test/elementhandle-screenshot.spec.js
new file mode 100644
index 0000000000..ca454605a1
--- /dev/null
+++ b/test/elementhandle-screenshot.spec.js
@@ -0,0 +1,348 @@
+/**
+ * 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, USES_HOOKS, HEADLESS} = testOptions;
+const {PNG} = require('pngjs');
+
+// Firefox headful produces a different image.
+const ffheadful = FFOX && !HEADLESS;
+
+it.skip(ffheadful)('should work', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(50, 100));
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
+});
+it.skip(ffheadful)('should take into account padding and border', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.setContent(`
+
oooo
+
+
+ `);
+ const elementHandle = await page.$('div#d');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
+});
+it.skip(ffheadful)('should capture full element when larger than viewport in parallel', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+
+ await page.setContent(`
+ oooo
+
+
+
+
+ `);
+ const elementHandles = await page.$$('div.to-screenshot');
+ const promises = elementHandles.map(handle => handle.screenshot());
+ const screenshots = await Promise.all(promises);
+ expect(screenshots[2]).toBeGolden('screenshot-element-larger-than-viewport.png');
+
+ await utils.verifyViewport(page, 500, 500);
+});
+it.skip(ffheadful)('should capture full element when larger than viewport', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+
+ await page.setContent(`
+ oooo
+
+
+
+
+ `);
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png');
+
+ await utils.verifyViewport(page, 500, 500);
+});
+it.skip(ffheadful)('should scroll element into view', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.setContent(`
+ oooo
+
+
+
+ `);
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
+});
+it.skip(ffheadful)('should scroll 15000px into view', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.setContent(`
+ oooo
+
+
+
+ `);
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
+});
+it.skip(ffheadful)('should work with a rotated element', async({page}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.setContent(`
`);
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-rotate.png');
+});
+it.skip(ffheadful)('should fail to screenshot a detached element', async({page, server}) => {
+ await page.setContent('remove this ');
+ const elementHandle = await page.$('h1');
+ await page.evaluate(element => element.remove(), elementHandle);
+ const screenshotError = await elementHandle.screenshot().catch(error => error);
+ expect(screenshotError.message).toContain('Element is not attached to the DOM');
+});
+it.skip(ffheadful)('should timeout waiting for visible', async({page, server}) => {
+ await page.setContent('
');
+ const div = await page.$('div');
+ const error = await div.screenshot({ timeout: 3000 }).catch(e => e);
+ expect(error.message).toContain('elementHandle.screenshot: Timeout 3000ms exceeded');
+ expect(error.message).toContain('element is not visible');
+});
+it.skip(ffheadful)('should wait for visible', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(50, 100));
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ await elementHandle.evaluate(e => e.style.visibility = 'hidden');
+ let done = false;
+ const promise = elementHandle.screenshot().then(buffer => {
+ done = true;
+ return buffer;
+ });
+ for (let i = 0; i < 10; i++)
+ await page.evaluate(() => new Promise(f => requestAnimationFrame(f)));
+ expect(done).toBe(false);
+ await elementHandle.evaluate(e => e.style.visibility = 'visible');
+ const screenshot = await promise;
+ expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
+});
+it.skip(ffheadful)('should work for an element with fractional dimensions', async({page}) => {
+ await page.setContent('
');
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-fractional.png');
+});
+it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
+ const context = await browser.newContext({viewport: { width: 320, height: 480, isMobile: true }});
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(50, 100));
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-mobile.png');
+ await context.close();
+});
+it.skip(FFOX)('should work with device scale factor', async({browser, server}) => {
+ const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(50, 100));
+ const elementHandle = await page.$('.box:nth-of-type(3)');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-mobile-dsf.png');
+ await context.close();
+});
+it.skip(ffheadful)('should work for an element with an offset', async({page}) => {
+ await page.setContent('
');
+ const elementHandle = await page.$('div');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
+});
+it.skip(ffheadful)('should take screenshots when default viewport is null', async({server, browser}) => {
+ const context = await browser.newContext({ viewport: null });
+ const page = await context.newPage();
+ await page.setContent(`
`);
+ const windowSize = await page.evaluate(() => ({ width: window.innerWidth * window.devicePixelRatio, height: window.innerHeight * window.devicePixelRatio }));
+ const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeInstanceOf(Buffer);
+ const decoded = PNG.sync.read(screenshot);
+ expect(decoded.width).toBe(windowSize.width);
+ expect(decoded.height).toBe(windowSize.height);
+
+ const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+ expect(sizeBefore.width).toBe(sizeAfter.width);
+ expect(sizeBefore.height).toBe(sizeAfter.height);
+ await context.close();
+});
+it.skip(ffheadful)('should take fullPage screenshots when default viewport is null', async({server, browser}) => {
+ const context = await browser.newContext({ viewport: null });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+ const screenshot = await page.screenshot({
+ fullPage: true
+ });
+ expect(screenshot).toBeInstanceOf(Buffer);
+
+ const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+ expect(sizeBefore.width).toBe(sizeAfter.width);
+ expect(sizeBefore.height).toBe(sizeAfter.height);
+ await context.close();
+});
+it.skip(ffheadful)('should restore default viewport after fullPage screenshot', async({ browser }) => {
+ const context = await browser.newContext({ viewport: { width: 456, height: 789 } });
+ const page = await context.newPage();
+ await utils.verifyViewport(page, 456, 789);
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(screenshot).toBeInstanceOf(Buffer);
+ await utils.verifyViewport(page, 456, 789);
+ await context.close();
+});
+it.skip(ffheadful || USES_HOOKS)('should restore viewport after page screenshot and exception', async({ browser, server }) => {
+ const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ const __testHookBeforeScreenshot = () => { throw new Error('oh my') };
+ const error = await page.screenshot({ fullPage: true, __testHookBeforeScreenshot }).catch(e => e);
+ expect(error.message).toContain('oh my');
+ await utils.verifyViewport(page, 350, 360);
+ await context.close();
+});
+it.skip(ffheadful || USES_HOOKS)('should restore viewport after page screenshot and timeout', async({ browser, server }) => {
+ const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000));
+ const error = await page.screenshot({ fullPage: true, __testHookAfterScreenshot, timeout: 3000 }).catch(e => e);
+ expect(error.message).toContain('page.screenshot: Timeout 3000ms exceeded');
+ await utils.verifyViewport(page, 350, 360);
+ await page.setViewportSize({ width: 400, height: 400 });
+ await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport.
+ await utils.verifyViewport(page, 400, 400);
+ await context.close();
+});
+it.skip(ffheadful)('should take element screenshot when default viewport is null and restore back', async({server, browser}) => {
+ const context = await browser.newContext({viewport: null});
+ const page = await context.newPage({ viewport: null });
+ await page.setContent(`
+ oooo
+
+
+
+
+ `);
+ const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+ const elementHandle = await page.$('div.to-screenshot');
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeInstanceOf(Buffer);
+ const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
+ expect(sizeBefore.width).toBe(sizeAfter.width);
+ expect(sizeBefore.height).toBe(sizeAfter.height);
+ await context.close();
+});
+it.skip(ffheadful || USES_HOOKS)('should restore viewport after element screenshot and exception', async({server, browser}) => {
+ const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
+ const page = await context.newPage();
+ await page.setContent(`
`);
+ const elementHandle = await page.$('div');
+ const __testHookBeforeScreenshot = () => { throw new Error('oh my') };
+ const error = await elementHandle.screenshot({ __testHookBeforeScreenshot }).catch(e => e);
+ expect(error.message).toContain('oh my');
+ await utils.verifyViewport(page, 350, 360);
+ await context.close();
+});
+it.skip(ffheadful)('should wait for element to stop moving', 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(3)');
+ await elementHandle.evaluate(e => {
+ e.classList.add('animation');
+ return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)));
+ });
+ const screenshot = await elementHandle.screenshot();
+ expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
+});
+it.skip(ffheadful)('should take screenshot of disabled button', async({page}) => {
+ await page.setViewportSize({ width: 500, height: 500 });
+ await page.setContent(`Click me `);
+ const button = await page.$('button');
+ const screenshot = await button.screenshot();
+ expect(screenshot).toBeInstanceOf(Buffer);
+});
diff --git a/test/jshandle-as-element.spec.js b/test/jshandle-as-element.spec.js
new file mode 100644
index 0000000000..5ec08ccd2c
--- /dev/null
+++ b/test/jshandle-as-element.spec.js
@@ -0,0 +1,46 @@
+/**
+ * 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} = testOptions;
+
+it('should work', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => document.body);
+ const element = aHandle.asElement();
+ expect(element).toBeTruthy();
+});
+
+it('should return null for non-elements', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => 2);
+ const element = aHandle.asElement();
+ expect(element).toBeFalsy();
+});
+
+it('should return ElementHandle for TextNodes', async({page, server}) => {
+ await page.setContent('ee!
');
+ const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
+ const element = aHandle.asElement();
+ expect(element).toBeTruthy();
+ expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)).toBeTruthy();
+});
+
+it('should work with nullified Node', async({page, server}) => {
+ await page.setContent('');
+ await page.evaluate('delete Node');
+ const handle = await page.evaluateHandle(() => document.querySelector('section'));
+ const element = handle.asElement();
+ expect(element).not.toBe(null);
+});
diff --git a/test/jshandle-json-value.spec.js b/test/jshandle-json-value.spec.js
new file mode 100644
index 0000000000..8c43d462b3
--- /dev/null
+++ b/test/jshandle-json-value.spec.js
@@ -0,0 +1,37 @@
+/**
+ * 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} = testOptions;
+
+it('should work', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => ({foo: 'bar'}));
+ const json = await aHandle.jsonValue();
+ expect(json).toEqual({foo: 'bar'});
+});
+
+it('should work with dates', async({page, server}) => {
+ const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
+ const date = await dateHandle.jsonValue();
+ expect(date.toJSON()).toBe('2017-09-26T00:00:00.000Z');
+});
+
+it('should throw for circular objects', async({page, server}) => {
+ const windowHandle = await page.evaluateHandle('window');
+ let error = null;
+ await windowHandle.jsonValue().catch(e => error = e);
+ expect(error.message).toContain('Argument is a circular structure');
+});
diff --git a/test/jshandle-properties.spec.js b/test/jshandle-properties.spec.js
new file mode 100644
index 0000000000..e5efb285c0
--- /dev/null
+++ b/test/jshandle-properties.spec.js
@@ -0,0 +1,94 @@
+/**
+ * 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} = testOptions;
+
+it('should work', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => ({
+ one: 1,
+ two: 2,
+ three: 3
+ }));
+ const twoHandle = await aHandle.getProperty('two');
+ expect(await twoHandle.jsonValue()).toEqual(2);
+});
+
+it('should work with undefined, null, and empty', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => ({
+ undefined: undefined,
+ null: null,
+ }));
+ const undefinedHandle = await aHandle.getProperty('undefined');
+ expect(String(await undefinedHandle.jsonValue())).toEqual('undefined');
+ const nullHandle = await aHandle.getProperty('null');
+ expect(await nullHandle.jsonValue()).toEqual(null);
+ const emptyhandle = await aHandle.getProperty('empty');
+ expect(String(await emptyhandle.jsonValue())).toEqual('undefined');
+});
+
+it('should work with unserializable values', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => ({
+ infinity: Infinity,
+ nInfinity: -Infinity,
+ nan: NaN,
+ nzero: -0
+ }));
+ const infinityHandle = await aHandle.getProperty('infinity');
+ expect(await infinityHandle.jsonValue()).toEqual(Infinity);
+ const nInfinityHandle = await aHandle.getProperty('nInfinity');
+ expect(await nInfinityHandle.jsonValue()).toEqual(-Infinity);
+ const nanHandle = await aHandle.getProperty('nan');
+ expect(String(await nanHandle.jsonValue())).toEqual('NaN');
+ const nzeroHandle = await aHandle.getProperty('nzero');
+ expect(await nzeroHandle.jsonValue()).toEqual(-0);
+});
+
+it('getProperties should work', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => ({
+ foo: 'bar'
+ }));
+ const properties = await aHandle.getProperties();
+ const foo = properties.get('foo');
+ expect(foo).toBeTruthy();
+ expect(await foo.jsonValue()).toBe('bar');
+});
+
+it('getProperties should return empty map for non-objects', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => 123);
+ const properties = await aHandle.getProperties();
+ expect(properties.size).toBe(0);
+});
+
+it('getProperties should return even non-own properties', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => {
+ class A {
+ constructor() {
+ this.a = '1';
+ }
+ }
+ class B extends A {
+ constructor() {
+ super();
+ this.b = '2';
+ }
+ }
+ return new B();
+ });
+ const properties = await aHandle.getProperties();
+ expect(await properties.get('a').jsonValue()).toBe('1');
+ expect(await properties.get('b').jsonValue()).toBe('2');
+});
diff --git a/test/jshandle-to-string.spec.js b/test/jshandle-to-string.spec.js
new file mode 100644
index 0000000000..0f42cbc06f
--- /dev/null
+++ b/test/jshandle-to-string.spec.js
@@ -0,0 +1,59 @@
+/**
+ * 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} = testOptions;
+
+it('should work for primitives', async({page, server}) => {
+ const numberHandle = await page.evaluateHandle(() => 2);
+ expect(numberHandle.toString()).toBe('JSHandle@2');
+ const stringHandle = await page.evaluateHandle(() => 'a');
+ expect(stringHandle.toString()).toBe('JSHandle@a');
+});
+
+it('should work for complicated objects', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => window);
+ expect(aHandle.toString()).toBe('JSHandle@object');
+});
+
+it('should work for promises', async({page, server}) => {
+ // wrap the promise in an object, otherwise we will await.
+ const wrapperHandle = await page.evaluateHandle(() => ({b: Promise.resolve(123)}));
+ const bHandle = await wrapperHandle.getProperty('b');
+ expect(bHandle.toString()).toBe('JSHandle@promise');
+});
+
+it('should work with different subtypes', async({page, server}) => {
+ expect((await page.evaluateHandle('(function(){})')).toString()).toBe('JSHandle@function');
+ expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle@12');
+ expect((await page.evaluateHandle('true')).toString()).toBe('JSHandle@true');
+ expect((await page.evaluateHandle('undefined')).toString()).toBe('JSHandle@undefined');
+ expect((await page.evaluateHandle('"foo"')).toString()).toBe('JSHandle@foo');
+ expect((await page.evaluateHandle('Symbol()')).toString()).toBe('JSHandle@symbol');
+ expect((await page.evaluateHandle('new Map()')).toString()).toBe('JSHandle@map');
+ expect((await page.evaluateHandle('new Set()')).toString()).toBe('JSHandle@set');
+ expect((await page.evaluateHandle('[]')).toString()).toBe('JSHandle@array');
+ expect((await page.evaluateHandle('null')).toString()).toBe('JSHandle@null');
+ expect((await page.evaluateHandle('/foo/')).toString()).toBe('JSHandle@regexp');
+ expect((await page.evaluateHandle('document.body')).toString()).toBe('JSHandle@node');
+ expect((await page.evaluateHandle('new Date()')).toString()).toBe('JSHandle@date');
+ expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe('JSHandle@weakmap');
+ expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe('JSHandle@weakset');
+ expect((await page.evaluateHandle('new Error()')).toString()).toBe('JSHandle@error');
+ // TODO(yurys): change subtype from array to typedarray in WebKit.
+ expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe(WEBKIT ? 'JSHandle@array' : 'JSHandle@typedarray');
+ expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe('JSHandle@proxy');
+});
diff --git a/test/jshandle.jest.js b/test/jshandle.jest.js
deleted file mode 100644
index ca714432c7..0000000000
--- a/test/jshandle.jest.js
+++ /dev/null
@@ -1,279 +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} = testOptions;
-
-describe('Page.evaluateHandle', function() {
- it('should work', async({page, server}) => {
- const windowHandle = await page.evaluateHandle(() => window);
- expect(windowHandle).toBeTruthy();
- });
- it('should accept object handle as an argument', async({page, server}) => {
- const navigatorHandle = await page.evaluateHandle(() => navigator);
- const text = await page.evaluate(e => e.userAgent, navigatorHandle);
- expect(text).toContain('Mozilla');
- });
- it('should accept object handle to primitive types', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => 5);
- const isFive = await page.evaluate(e => Object.is(e, 5), aHandle);
- expect(isFive).toBeTruthy();
- });
- it('should accept nested handle', async({page, server}) => {
- const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
- const result = await page.evaluate(({ foo }) => {
- return foo;
- }, { foo });
- expect(result).toEqual({ x: 1, y: 'foo' });
- });
- it('should accept nested window handle', async({page, server}) => {
- const foo = await page.evaluateHandle(() => window);
- const result = await page.evaluate(({ foo }) => {
- return foo === window;
- }, { foo });
- expect(result).toBe(true);
- });
- it('should accept multiple nested handles', async({page, server}) => {
- const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
- const bar = await page.evaluateHandle(() => 5);
- const baz = await page.evaluateHandle(() => (['baz']));
- const result = await page.evaluate(x => {
- return JSON.stringify(x);
- }, { a1: { foo }, a2: { bar, arr: [{ baz }] } });
- expect(JSON.parse(result)).toEqual({
- a1: { foo: { x: 1, y: 'foo' } },
- a2: { bar: 5, arr: [{ baz: ['baz'] }] }
- });
- });
- it('should throw for circular objects', async({page, server}) => {
- const a = { x: 1 };
- a.y = a;
- const error = await page.evaluate(x => x, a).catch(e => e);
- expect(error.message).toContain('Argument is a circular structure');
- });
- it('should accept same handle multiple times', async({page, server}) => {
- const foo = await page.evaluateHandle(() => 1);
- expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
- });
- it('should accept same nested object multiple times', async({page, server}) => {
- const foo = { x: 1 };
- expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: { x: 1 }, bar: [{ x : 1 }], baz: { foo: { x : 1 } } });
- });
- it('should accept object handle to unserializable value', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => Infinity);
- expect(await page.evaluate(e => Object.is(e, Infinity), aHandle)).toBe(true);
- });
- it('should pass configurable args', async({page, server}) => {
- const result = await page.evaluate(arg => {
- if (arg.foo !== 42)
- throw new Error('Not a 42');
- arg.foo = 17;
- if (arg.foo !== 17)
- throw new Error('Not 17');
- delete arg.foo;
- if (arg.foo === 17)
- throw new Error('Still 17');
- return arg;
- }, { foo: 42 });
- expect(result).toEqual({});
- });
- it('should work with primitives', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => {
- window.FOO = 123;
- return window;
- });
- expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
- });
-});
-
-describe('JSHandle.evaluate', function() {
- it('should work with function', async({page, server}) => {
- const windowHandle = await page.evaluateHandle(() => {
- window.foo = [1, 2];
- return window;
- });
- expect(await windowHandle.evaluate(w => w.foo)).toEqual([1, 2]);
- });
- it('should work with expression', async({page, server}) => {
- const windowHandle = await page.evaluateHandle(() => {
- window.foo = [1, 2];
- return window;
- });
- expect(await windowHandle.evaluate('window.foo')).toEqual([1, 2]);
- });
-});
-
-describe('JSHandle.getProperty', function() {
- it('should work', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => ({
- one: 1,
- two: 2,
- three: 3
- }));
- const twoHandle = await aHandle.getProperty('two');
- expect(await twoHandle.jsonValue()).toEqual(2);
- });
- it('should work with undefined, null, and empty', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => ({
- undefined: undefined,
- null: null,
- }));
- const undefinedHandle = await aHandle.getProperty('undefined');
- expect(String(await undefinedHandle.jsonValue())).toEqual('undefined');
- const nullHandle = await aHandle.getProperty('null');
- expect(await nullHandle.jsonValue()).toEqual(null);
- const emptyhandle = await aHandle.getProperty('empty');
- expect(String(await emptyhandle.jsonValue())).toEqual('undefined');
- });
- it('should work with unserializable values', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => ({
- infinity: Infinity,
- nInfinity: -Infinity,
- nan: NaN,
- nzero: -0
- }));
- const infinityHandle = await aHandle.getProperty('infinity');
- expect(await infinityHandle.jsonValue()).toEqual(Infinity);
- const nInfinityHandle = await aHandle.getProperty('nInfinity');
- expect(await nInfinityHandle.jsonValue()).toEqual(-Infinity);
- const nanHandle = await aHandle.getProperty('nan');
- expect(String(await nanHandle.jsonValue())).toEqual('NaN');
- const nzeroHandle = await aHandle.getProperty('nzero');
- expect(await nzeroHandle.jsonValue()).toEqual(-0);
- });
-});
-
-describe('JSHandle.jsonValue', function() {
- it('should work', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => ({foo: 'bar'}));
- const json = await aHandle.jsonValue();
- expect(json).toEqual({foo: 'bar'});
- });
- it('should work with dates', async({page, server}) => {
- const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z'));
- const date = await dateHandle.jsonValue();
- expect(date.toJSON()).toBe('2017-09-26T00:00:00.000Z');
- });
- it('should throw for circular objects', async({page, server}) => {
- const windowHandle = await page.evaluateHandle('window');
- let error = null;
- await windowHandle.jsonValue().catch(e => error = e);
- expect(error.message).toContain('Argument is a circular structure');
- });
-});
-
-describe('JSHandle.getProperties', function() {
- it('should work', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => ({
- foo: 'bar'
- }));
- const properties = await aHandle.getProperties();
- const foo = properties.get('foo');
- expect(foo).toBeTruthy();
- expect(await foo.jsonValue()).toBe('bar');
- });
- it('should return empty map for non-objects', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => 123);
- const properties = await aHandle.getProperties();
- expect(properties.size).toBe(0);
- });
- it('should return even non-own properties', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => {
- class A {
- constructor() {
- this.a = '1';
- }
- }
- class B extends A {
- constructor() {
- super();
- this.b = '2';
- }
- }
- return new B();
- });
- const properties = await aHandle.getProperties();
- expect(await properties.get('a').jsonValue()).toBe('1');
- expect(await properties.get('b').jsonValue()).toBe('2');
- });
-});
-
-describe('JSHandle.asElement', function() {
- it('should work', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => document.body);
- const element = aHandle.asElement();
- expect(element).toBeTruthy();
- });
- it('should return null for non-elements', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => 2);
- const element = aHandle.asElement();
- expect(element).toBeFalsy();
- });
- it('should return ElementHandle for TextNodes', async({page, server}) => {
- await page.setContent('ee!
');
- const aHandle = await page.evaluateHandle(() => document.querySelector('div').firstChild);
- const element = aHandle.asElement();
- expect(element).toBeTruthy();
- expect(await page.evaluate(e => e.nodeType === HTMLElement.TEXT_NODE, element)).toBeTruthy();
- });
- it('should work with nullified Node', async({page, server}) => {
- await page.setContent('');
- await page.evaluate('delete Node');
- const handle = await page.evaluateHandle(() => document.querySelector('section'));
- const element = handle.asElement();
- expect(element).not.toBe(null);
- });
-});
-
-describe('JSHandle.toString', function() {
- it('should work for primitives', async({page, server}) => {
- const numberHandle = await page.evaluateHandle(() => 2);
- expect(numberHandle.toString()).toBe('JSHandle@2');
- const stringHandle = await page.evaluateHandle(() => 'a');
- expect(stringHandle.toString()).toBe('JSHandle@a');
- });
- it('should work for complicated objects', async({page, server}) => {
- const aHandle = await page.evaluateHandle(() => window);
- expect(aHandle.toString()).toBe('JSHandle@object');
- });
- it('should work for promises', async({page, server}) => {
- // wrap the promise in an object, otherwise we will await.
- const wrapperHandle = await page.evaluateHandle(() => ({b: Promise.resolve(123)}));
- const bHandle = await wrapperHandle.getProperty('b');
- expect(bHandle.toString()).toBe('JSHandle@promise');
- });
- it('should work with different subtypes', async({page, server}) => {
- expect((await page.evaluateHandle('(function(){})')).toString()).toBe('JSHandle@function');
- expect((await page.evaluateHandle('12')).toString()).toBe('JSHandle@12');
- expect((await page.evaluateHandle('true')).toString()).toBe('JSHandle@true');
- expect((await page.evaluateHandle('undefined')).toString()).toBe('JSHandle@undefined');
- expect((await page.evaluateHandle('"foo"')).toString()).toBe('JSHandle@foo');
- expect((await page.evaluateHandle('Symbol()')).toString()).toBe('JSHandle@symbol');
- expect((await page.evaluateHandle('new Map()')).toString()).toBe('JSHandle@map');
- expect((await page.evaluateHandle('new Set()')).toString()).toBe('JSHandle@set');
- expect((await page.evaluateHandle('[]')).toString()).toBe('JSHandle@array');
- expect((await page.evaluateHandle('null')).toString()).toBe('JSHandle@null');
- expect((await page.evaluateHandle('/foo/')).toString()).toBe('JSHandle@regexp');
- expect((await page.evaluateHandle('document.body')).toString()).toBe('JSHandle@node');
- expect((await page.evaluateHandle('new Date()')).toString()).toBe('JSHandle@date');
- expect((await page.evaluateHandle('new WeakMap()')).toString()).toBe('JSHandle@weakmap');
- expect((await page.evaluateHandle('new WeakSet()')).toString()).toBe('JSHandle@weakset');
- expect((await page.evaluateHandle('new Error()')).toString()).toBe('JSHandle@error');
- // TODO(yurys): change subtype from array to typedarray in WebKit.
- expect((await page.evaluateHandle('new Int32Array()')).toString()).toBe(WEBKIT ? 'JSHandle@array' : 'JSHandle@typedarray');
- expect((await page.evaluateHandle('new Proxy({}, {})')).toString()).toBe('JSHandle@proxy');
- });
-});
diff --git a/test/multiclient.jest.js b/test/multiclient.jest.js
deleted file mode 100644
index 5002bb2b9b..0000000000
--- a/test/multiclient.jest.js
+++ /dev/null
@@ -1,109 +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('BrowserContext', function() {
- it('should work across sessions', async ({browserType, defaultBrowserOptions}) => {
- const browserServer = await browserType.launchServer(defaultBrowserOptions);
- const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- expect(browser1.contexts().length).toBe(0);
- await browser1.newContext();
- expect(browser1.contexts().length).toBe(1);
-
- const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- expect(browser2.contexts().length).toBe(0);
- await browser2.newContext();
- expect(browser2.contexts().length).toBe(1);
-
- expect(browser1.contexts().length).toBe(1);
-
- await browser1.close();
- await browser2.close();
-
- await browserServer._checkLeaks();
- await browserServer.close();
- });
-});
-
-describe('Browser.Events.disconnected', function() {
- it.slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async ({browserType, defaultBrowserOptions}) => {
- const browserServer = await browserType.launchServer(defaultBrowserOptions);
- const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- const wsEndpoint = browserServer.wsEndpoint();
- const remoteBrowser1 = await browserType.connect({ wsEndpoint });
- const remoteBrowser2 = await browserType.connect({ wsEndpoint });
-
- let disconnectedOriginal = 0;
- let disconnectedRemote1 = 0;
- let disconnectedRemote2 = 0;
- originalBrowser.on('disconnected', () => ++disconnectedOriginal);
- remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
- remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
-
- await Promise.all([
- new Promise(f => remoteBrowser2.on('disconnected', f)),
- remoteBrowser2.close(),
- ]);
-
- expect(disconnectedOriginal).toBe(0);
- expect(disconnectedRemote1).toBe(0);
- expect(disconnectedRemote2).toBe(1);
-
- await Promise.all([
- new Promise(f => remoteBrowser1.on('disconnected', f)),
- new Promise(f => originalBrowser.on('disconnected', f)),
- browserServer.close(),
- ]);
-
- expect(disconnectedOriginal).toBe(1);
- expect(disconnectedRemote1).toBe(1);
- expect(disconnectedRemote2).toBe(1);
- });
-});
-
-describe('browserType.connect', function() {
- it('should be able to connect multiple times to the same browser', async({browserType, defaultBrowserOptions}) => {
- const browserServer = await browserType.launchServer(defaultBrowserOptions);
- const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- const page1 = await browser1.newPage();
- expect(await page1.evaluate(() => 7 * 8)).toBe(56);
- browser1.close();
-
- const page2 = await browser2.newPage();
- expect(await page2.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
- await browser2.close();
- await browserServer._checkLeaks();
- await browserServer.close();
- });
- it('should not be able to close remote browser', async({browserType, defaultBrowserOptions}) => {
- const browserServer = await browserType.launchServer(defaultBrowserOptions);
- {
- const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- await remote.newContext();
- await remote.close();
- }
- {
- const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
- await remote.newContext();
- await remote.close();
- }
- await browserServer._checkLeaks();
- await browserServer.close();
- });
-});
diff --git a/test/multiclient.spec.js b/test/multiclient.spec.js
new file mode 100644
index 0000000000..d30e32479e
--- /dev/null
+++ b/test/multiclient.spec.js
@@ -0,0 +1,104 @@
+/**
+ * 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 across sessions', async ({browserType, defaultBrowserOptions}) => {
+ const browserServer = await browserType.launchServer(defaultBrowserOptions);
+ const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ expect(browser1.contexts().length).toBe(0);
+ await browser1.newContext();
+ expect(browser1.contexts().length).toBe(1);
+
+ const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ expect(browser2.contexts().length).toBe(0);
+ await browser2.newContext();
+ expect(browser2.contexts().length).toBe(1);
+
+ expect(browser1.contexts().length).toBe(1);
+
+ await browser1.close();
+ await browser2.close();
+
+ await browserServer._checkLeaks();
+ await browserServer.close();
+});
+
+it.slow()('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async ({browserType, defaultBrowserOptions}) => {
+ const browserServer = await browserType.launchServer(defaultBrowserOptions);
+ const originalBrowser = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ const wsEndpoint = browserServer.wsEndpoint();
+ const remoteBrowser1 = await browserType.connect({ wsEndpoint });
+ const remoteBrowser2 = await browserType.connect({ wsEndpoint });
+
+ let disconnectedOriginal = 0;
+ let disconnectedRemote1 = 0;
+ let disconnectedRemote2 = 0;
+ originalBrowser.on('disconnected', () => ++disconnectedOriginal);
+ remoteBrowser1.on('disconnected', () => ++disconnectedRemote1);
+ remoteBrowser2.on('disconnected', () => ++disconnectedRemote2);
+
+ await Promise.all([
+ new Promise(f => remoteBrowser2.on('disconnected', f)),
+ remoteBrowser2.close(),
+ ]);
+
+ expect(disconnectedOriginal).toBe(0);
+ expect(disconnectedRemote1).toBe(0);
+ expect(disconnectedRemote2).toBe(1);
+
+ await Promise.all([
+ new Promise(f => remoteBrowser1.on('disconnected', f)),
+ new Promise(f => originalBrowser.on('disconnected', f)),
+ browserServer.close(),
+ ]);
+
+ expect(disconnectedOriginal).toBe(1);
+ expect(disconnectedRemote1).toBe(1);
+ expect(disconnectedRemote2).toBe(1);
+});
+
+it('should be able to connect multiple times to the same browser', async({browserType, defaultBrowserOptions}) => {
+ const browserServer = await browserType.launchServer(defaultBrowserOptions);
+ const browser1 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ const browser2 = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ const page1 = await browser1.newPage();
+ expect(await page1.evaluate(() => 7 * 8)).toBe(56);
+ browser1.close();
+
+ const page2 = await browser2.newPage();
+ expect(await page2.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
+ await browser2.close();
+ await browserServer._checkLeaks();
+ await browserServer.close();
+});
+
+it('should not be able to close remote browser', async({browserType, defaultBrowserOptions}) => {
+ const browserServer = await browserType.launchServer(defaultBrowserOptions);
+ {
+ const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ await remote.newContext();
+ await remote.close();
+ }
+ {
+ const remote = await browserType.connect({ wsEndpoint: browserServer.wsEndpoint() });
+ await remote.newContext();
+ await remote.close();
+ }
+ await browserServer._checkLeaks();
+ await browserServer.close();
+});
diff --git a/test/network-request.spec.js b/test/network-request.spec.js
new file mode 100644
index 0000000000..be28a030f8
--- /dev/null
+++ b/test/network-request.spec.js
@@ -0,0 +1,192 @@
+/**
+ * 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 fs = require('fs');
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
+
+it('should work for main frame navigation request', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.mainFrame());
+});
+
+it('should work for subframe navigation request', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.frames()[1]);
+});
+
+it('should work for fetch requests', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ let requests = [];
+ page.on('request', request => requests.push(request));
+ await page.evaluate(() => fetch('/digits/1.png'));
+ requests = requests.filter(request => !request.url().includes('favicon'));
+ expect(requests.length).toBe(1);
+ expect(requests[0].frame()).toBe(page.mainFrame());
+});
+
+it('should return headers', async({page, server}) => {
+ const response = await page.goto(server.EMPTY_PAGE);
+ if (CHROMIUM)
+ expect(response.request().headers()['user-agent']).toContain('Chrome');
+ else if (FFOX)
+ expect(response.request().headers()['user-agent']).toContain('Firefox');
+ else if (WEBKIT)
+ expect(response.request().headers()['user-agent']).toContain('WebKit');
+});
+
+it.fail(CHROMIUM||WEBKIT)('should get the same headers as the server', async({page, server}) => {
+ await page.goto(server.PREFIX + '/empty.html');
+ let serverRequest;
+ await server.setRoute('/something', (request, response) => {
+ serverRequest = request;
+ response.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
+ response.end('done');
+ });
+ const requestPromise = page.waitForEvent('request');
+ const text = await page.evaluate(async url => {
+ const data = await fetch(url);
+ return data.text();
+ }, server.CROSS_PROCESS_PREFIX + '/something');
+ const request = await requestPromise;
+ expect(text).toBe('done');
+ expect(request.headers()).toEqual(serverRequest.headers);
+});
+
+it('should return postData', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ page.on('request', r => request = r);
+ await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({foo: 'bar'})}));
+ expect(request).toBeTruthy();
+ expect(request.postData()).toBe('{"foo":"bar"}');
+});
+
+it('should work with binary post data', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ page.on('request', r => request = r);
+ await page.evaluate(async () => {
+ await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) })
+ });
+ expect(request).toBeTruthy();
+ const buffer = request.postDataBuffer();
+ expect(buffer.length).toBe(256);
+ for (let i = 0; i < 256; ++i)
+ expect(buffer[i]).toBe(i);
+});
+
+it('should work with binary post data and interception', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ await page.route('/post', route => route.continue());
+ page.on('request', r => request = r);
+ await page.evaluate(async () => {
+ await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) })
+ });
+ expect(request).toBeTruthy();
+ const buffer = request.postDataBuffer();
+ expect(buffer.length).toBe(256);
+ for (let i = 0; i < 256; ++i)
+ expect(buffer[i]).toBe(i);
+});
+
+it('should be |undefined| when there is no post data', async({page, server}) => {
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.request().postData()).toBe(null);
+});
+
+it('should parse the json post data', async ({ page, server }) => {
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ page.on('request', r => request = r);
+ await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({ foo: 'bar' }) }));
+ expect(request).toBeTruthy();
+ expect(request.postDataJSON()).toEqual({ "foo": "bar" });
+});
+
+it('should parse the data if content-type is application/x-www-form-urlencoded', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ server.setRoute('/post', (req, res) => res.end());
+ let request = null;
+ page.on('request', r => request = r);
+ await page.setContent(``);
+ await page.click('input[type=submit]');
+ expect(request).toBeTruthy();
+ expect(request.postDataJSON()).toEqual({'foo':'bar','baz':'123'});
+})
+
+it('should be |undefined| when there is no post data', async ({ page, server }) => {
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.request().postDataJSON()).toBe(null);
+});
+
+it('should return event source', async ({page, server}) => {
+ const SSE_MESSAGE = {foo: 'bar'};
+ // 1. Setup server-sent events on server that immediately sends a message to the client.
+ server.setRoute('/sse', (req, res) => {
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Connection': 'keep-alive',
+ 'Cache-Control': 'no-cache',
+ });
+ res.write(`data: ${JSON.stringify(SSE_MESSAGE)}\n\n`);
+ });
+ // 2. Subscribe to page request events.
+ await page.goto(server.EMPTY_PAGE);
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ // 3. Connect to EventSource in browser and return first message.
+ expect(await page.evaluate(() => {
+ const eventSource = new EventSource('/sse');
+ return new Promise(resolve => {
+ eventSource.onmessage = e => resolve(JSON.parse(e.data));
+ });
+ })).toEqual(SSE_MESSAGE);
+ expect(requests[0].resourceType()).toBe('eventsource');
+});
+
+it('should return navigation bit', async({page, server}) => {
+ const requests = new Map();
+ page.on('request', request => requests.set(request.url().split('/').pop(), request));
+ server.setRedirect('/rrredirect', '/frames/one-frame.html');
+ await page.goto(server.PREFIX + '/rrredirect');
+ expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
+ expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
+ expect(requests.get('script.js').isNavigationRequest()).toBe(false);
+ expect(requests.get('style.css').isNavigationRequest()).toBe(false);
+});
+
+it('should return navigation bit when navigating to image', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.PREFIX + '/pptr.png');
+ expect(requests[0].isNavigationRequest()).toBe(true);
+});
diff --git a/test/network-response.spec.js b/test/network-response.spec.js
new file mode 100644
index 0000000000..4286ee5ba5
--- /dev/null
+++ b/test/network-response.spec.js
@@ -0,0 +1,118 @@
+/**
+ * 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 fs = require('fs');
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
+
+it('should work', async({page, server}) => {
+ server.setRoute('/empty.html', (req, res) => {
+ res.setHeader('foo', 'bar');
+ res.end();
+ });
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(response.headers()['foo']).toBe('bar');
+});
+
+
+it('should return text', async({page, server}) => {
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ expect(await response.text()).toBe('{"foo": "bar"}\n');
+});
+
+it('should return uncompressed text', async({page, server}) => {
+ server.enableGzip('/simple.json');
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ expect(response.headers()['content-encoding']).toBe('gzip');
+ expect(await response.text()).toBe('{"foo": "bar"}\n');
+});
+
+it('should throw when requesting body of redirected response', async({page, server}) => {
+ server.setRedirect('/foo.html', '/empty.html');
+ const response = await page.goto(server.PREFIX + '/foo.html');
+ const redirectedFrom = response.request().redirectedFrom();
+ expect(redirectedFrom).toBeTruthy();
+ const redirected = await redirectedFrom.response();
+ expect(redirected.status()).toBe(302);
+ let error = null;
+ await redirected.text().catch(e => error = e);
+ expect(error.message).toContain('Response body is unavailable for redirect responses');
+});
+
+it('should wait until response completes', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ // Setup server to trap request.
+ let serverResponse = null;
+ server.setRoute('/get', (req, res) => {
+ serverResponse = res;
+ // In Firefox, |fetch| will be hanging until it receives |Content-Type| header
+ // from server.
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+ res.write('hello ');
+ });
+ // Setup page to trap response.
+ let requestFinished = false;
+ page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
+ // send request and wait for server response
+ const [pageResponse] = await Promise.all([
+ page.waitForEvent('response'),
+ page.evaluate(() => fetch('./get', { method: 'GET'})),
+ server.waitForRequest('/get'),
+ ]);
+
+ expect(serverResponse).toBeTruthy();
+ expect(pageResponse).toBeTruthy();
+ expect(pageResponse.status()).toBe(200);
+ expect(requestFinished).toBe(false);
+
+ const responseText = pageResponse.text();
+ // Write part of the response and wait for it to be flushed.
+ await new Promise(x => serverResponse.write('wor', x));
+ // Finish response.
+ await new Promise(x => serverResponse.end('ld!', x));
+ expect(await responseText).toBe('hello world!');
+});
+
+it('should return json', async({page, server}) => {
+ const response = await page.goto(server.PREFIX + '/simple.json');
+ expect(await response.json()).toEqual({foo: 'bar'});
+});
+
+it('should return body', async({page, server}) => {
+ const response = await page.goto(server.PREFIX + '/pptr.png');
+ const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
+ const responseBuffer = await response.body();
+ expect(responseBuffer.equals(imageBuffer)).toBe(true);
+});
+
+it('should return body with compression', async({page, server}) => {
+ server.enableGzip('/pptr.png');
+ const response = await page.goto(server.PREFIX + '/pptr.png');
+ const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
+ const responseBuffer = await response.body();
+ expect(responseBuffer.equals(imageBuffer)).toBe(true);
+});
+
+it('should return status text', async({page, server}) => {
+ server.setRoute('/cool', (req, res) => {
+ res.writeHead(200, 'cool!');
+ res.end();
+ });
+ const response = await page.goto(server.PREFIX + '/cool');
+ expect(response.statusText()).toBe('cool!');
+});
diff --git a/test/network.jest.js b/test/network.jest.js
deleted file mode 100644
index 990ff8c77a..0000000000
--- a/test/network.jest.js
+++ /dev/null
@@ -1,488 +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 fs = require('fs');
-const path = require('path');
-const utils = require('./utils');
-const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
-
-describe('Page.Events.Request', function() {
- it('should fire for navigation requests', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.EMPTY_PAGE);
- expect(requests.length).toBe(1);
- });
- it('should fire for iframes', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- expect(requests.length).toBe(2);
- });
- it('should fire for fetches', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.EMPTY_PAGE);
- await page.evaluate(() => fetch('/empty.html'));
- expect(requests.length).toBe(2);
- });
- it('should report requests and responses handled by service worker', async({page, server}) => {
- await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html');
- await page.evaluate(() => window.activationPromise);
- const [swResponse, request] = await Promise.all([
- page.evaluate(() => fetchDummy('foo')),
- page.waitForEvent('request'),
- ]);
- expect(swResponse).toBe('responseFromServiceWorker:foo');
- expect(request.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
- const response = await request.response();
- expect(response.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
- expect(await response.text()).toBe('responseFromServiceWorker:foo');
- });
-});
-
-describe('Request.frame', function() {
- it('should work for main frame navigation request', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.EMPTY_PAGE);
- expect(requests.length).toBe(1);
- expect(requests[0].frame()).toBe(page.mainFrame());
- });
- it('should work for subframe navigation request', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const requests = [];
- page.on('request', request => requests.push(request));
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- expect(requests.length).toBe(1);
- expect(requests[0].frame()).toBe(page.frames()[1]);
- });
- it('should work for fetch requests', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- let requests = [];
- page.on('request', request => requests.push(request));
- await page.evaluate(() => fetch('/digits/1.png'));
- requests = requests.filter(request => !request.url().includes('favicon'));
- expect(requests.length).toBe(1);
- expect(requests[0].frame()).toBe(page.mainFrame());
- });
-});
-
-describe('Request.headers', function() {
- it('should work', async({page, server}) => {
- const response = await page.goto(server.EMPTY_PAGE);
- if (CHROMIUM)
- expect(response.request().headers()['user-agent']).toContain('Chrome');
- else if (FFOX)
- expect(response.request().headers()['user-agent']).toContain('Firefox');
- else if (WEBKIT)
- expect(response.request().headers()['user-agent']).toContain('WebKit');
- });
- it.fail(CHROMIUM||WEBKIT)('should get the same headers as the server', async({page, server}) => {
- await page.goto(server.PREFIX + '/empty.html');
- let serverRequest;
- await server.setRoute('/something', (request, response) => {
- serverRequest = request;
- response.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
- response.end('done');
- });
- const requestPromise = page.waitForEvent('request');
- const text = await page.evaluate(async url => {
- const data = await fetch(url);
- return data.text();
- }, server.CROSS_PROCESS_PREFIX + '/something');
- const request = await requestPromise;
- expect(text).toBe('done');
- expect(request.headers()).toEqual(serverRequest.headers);
- });
-});
-
-describe('Response.headers', function() {
- it('should work', async({page, server}) => {
- server.setRoute('/empty.html', (req, res) => {
- res.setHeader('foo', 'bar');
- res.end();
- });
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.headers()['foo']).toBe('bar');
- });
-});
-
-describe('Request.postData', function() {
- it('should work', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/post', (req, res) => res.end());
- let request = null;
- page.on('request', r => request = r);
- await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({foo: 'bar'})}));
- expect(request).toBeTruthy();
- expect(request.postData()).toBe('{"foo":"bar"}');
- });
- it('should work with binary', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/post', (req, res) => res.end());
- let request = null;
- page.on('request', r => request = r);
- await page.evaluate(async () => {
- await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) })
- });
- expect(request).toBeTruthy();
- const buffer = request.postDataBuffer();
- expect(buffer.length).toBe(256);
- for (let i = 0; i < 256; ++i)
- expect(buffer[i]).toBe(i);
- });
- it('should work with binary and interception', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/post', (req, res) => res.end());
- let request = null;
- await page.route('/post', route => route.continue());
- page.on('request', r => request = r);
- await page.evaluate(async () => {
- await fetch('./post', { method: 'POST', body: new Uint8Array(Array.from(Array(256).keys())) })
- });
- expect(request).toBeTruthy();
- const buffer = request.postDataBuffer();
- expect(buffer.length).toBe(256);
- for (let i = 0; i < 256; ++i)
- expect(buffer[i]).toBe(i);
- });
- it('should be |undefined| when there is no post data', async({page, server}) => {
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.request().postData()).toBe(null);
- });
- it('should parse the JSON payload', async ({ page, server }) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/post', (req, res) => res.end());
- let request = null;
- page.on('request', r => request = r);
- await page.evaluate(() => fetch('./post', { method: 'POST', body: JSON.stringify({ foo: 'bar' }) }));
- expect(request).toBeTruthy();
- expect(request.postDataJSON()).toEqual({ "foo": "bar" });
- });
- it('should parse the data if content-type is application/x-www-form-urlencoded', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- server.setRoute('/post', (req, res) => res.end());
- let request = null;
- page.on('request', r => request = r);
- await page.setContent(``);
- await page.click('input[type=submit]');
- expect(request).toBeTruthy();
- expect(request.postDataJSON()).toEqual({'foo':'bar','baz':'123'});
- })
- it('should be |undefined| when there is no post data', async ({ page, server }) => {
- const response = await page.goto(server.EMPTY_PAGE);
- expect(response.request().postDataJSON()).toBe(null);
- });
-});
-
-describe('Response.text', function() {
- it('should work', async({page, server}) => {
- const response = await page.goto(server.PREFIX + '/simple.json');
- expect(await response.text()).toBe('{"foo": "bar"}\n');
- });
- it('should return uncompressed text', async({page, server}) => {
- server.enableGzip('/simple.json');
- const response = await page.goto(server.PREFIX + '/simple.json');
- expect(response.headers()['content-encoding']).toBe('gzip');
- expect(await response.text()).toBe('{"foo": "bar"}\n');
- });
- it('should throw when requesting body of redirected response', async({page, server}) => {
- server.setRedirect('/foo.html', '/empty.html');
- const response = await page.goto(server.PREFIX + '/foo.html');
- const redirectedFrom = response.request().redirectedFrom();
- expect(redirectedFrom).toBeTruthy();
- const redirected = await redirectedFrom.response();
- expect(redirected.status()).toBe(302);
- let error = null;
- await redirected.text().catch(e => error = e);
- expect(error.message).toContain('Response body is unavailable for redirect responses');
- });
- it('should wait until response completes', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- // Setup server to trap request.
- let serverResponse = null;
- server.setRoute('/get', (req, res) => {
- serverResponse = res;
- // In Firefox, |fetch| will be hanging until it receives |Content-Type| header
- // from server.
- res.setHeader('Content-Type', 'text/plain; charset=utf-8');
- res.write('hello ');
- });
- // Setup page to trap response.
- let requestFinished = false;
- page.on('requestfinished', r => requestFinished = requestFinished || r.url().includes('/get'));
- // send request and wait for server response
- const [pageResponse] = await Promise.all([
- page.waitForEvent('response'),
- page.evaluate(() => fetch('./get', { method: 'GET'})),
- server.waitForRequest('/get'),
- ]);
-
- expect(serverResponse).toBeTruthy();
- expect(pageResponse).toBeTruthy();
- expect(pageResponse.status()).toBe(200);
- expect(requestFinished).toBe(false);
-
- const responseText = pageResponse.text();
- // Write part of the response and wait for it to be flushed.
- await new Promise(x => serverResponse.write('wor', x));
- // Finish response.
- await new Promise(x => serverResponse.end('ld!', x));
- expect(await responseText).toBe('hello world!');
- });
-});
-
-describe('Response.json', function() {
- it('should work', async({page, server}) => {
- const response = await page.goto(server.PREFIX + '/simple.json');
- expect(await response.json()).toEqual({foo: 'bar'});
- });
-});
-
-describe('Response.body', function() {
- it('should work', async({page, server}) => {
- const response = await page.goto(server.PREFIX + '/pptr.png');
- const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
- const responseBuffer = await response.body();
- expect(responseBuffer.equals(imageBuffer)).toBe(true);
- });
- it('should work with compression', async({page, server}) => {
- server.enableGzip('/pptr.png');
- const response = await page.goto(server.PREFIX + '/pptr.png');
- const imageBuffer = fs.readFileSync(path.join(__dirname, 'assets', 'pptr.png'));
- const responseBuffer = await response.body();
- expect(responseBuffer.equals(imageBuffer)).toBe(true);
- });
-});
-
-describe('Response.statusText', function() {
- it('should work', async({page, server}) => {
- server.setRoute('/cool', (req, res) => {
- res.writeHead(200, 'cool!');
- res.end();
- });
- const response = await page.goto(server.PREFIX + '/cool');
- expect(response.statusText()).toBe('cool!');
- });
-});
-
-describe('Request.resourceType', function() {
- it('should return event source', async ({page, server}) => {
- const SSE_MESSAGE = {foo: 'bar'};
- // 1. Setup server-sent events on server that immediately sends a message to the client.
- server.setRoute('/sse', (req, res) => {
- res.writeHead(200, {
- 'Content-Type': 'text/event-stream',
- 'Connection': 'keep-alive',
- 'Cache-Control': 'no-cache',
- });
- res.write(`data: ${JSON.stringify(SSE_MESSAGE)}\n\n`);
- });
- // 2. Subscribe to page request events.
- await page.goto(server.EMPTY_PAGE);
- const requests = [];
- page.on('request', request => requests.push(request));
- // 3. Connect to EventSource in browser and return first message.
- expect(await page.evaluate(() => {
- const eventSource = new EventSource('/sse');
- return new Promise(resolve => {
- eventSource.onmessage = e => resolve(JSON.parse(e.data));
- });
- })).toEqual(SSE_MESSAGE);
- expect(requests[0].resourceType()).toBe('eventsource');
- });
-});
-
-describe('Network Events', function() {
- it('Page.Events.Request', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.EMPTY_PAGE);
- expect(requests.length).toBe(1);
- expect(requests[0].url()).toBe(server.EMPTY_PAGE);
- expect(requests[0].resourceType()).toBe('document');
- expect(requests[0].method()).toBe('GET');
- expect(await requests[0].response()).toBeTruthy();
- expect(requests[0].frame() === page.mainFrame()).toBe(true);
- expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
- });
- it('Page.Events.Response', async({page, server}) => {
- const responses = [];
- page.on('response', response => responses.push(response));
- await page.goto(server.EMPTY_PAGE);
- expect(responses.length).toBe(1);
- expect(responses[0].url()).toBe(server.EMPTY_PAGE);
- expect(responses[0].status()).toBe(200);
- expect(responses[0].ok()).toBe(true);
- expect(responses[0].request()).toBeTruthy();
- });
-
- it('Page.Events.RequestFailed', async({page, server}) => {
- server.setRoute('/one-style.css', (req, res) => {
- res.setHeader('Content-Type', 'text/css');
- res.socket.destroy();
- });
- const failedRequests = [];
- page.on('requestfailed', request => failedRequests.push(request));
- await page.goto(server.PREFIX + '/one-style.html');
- expect(failedRequests.length).toBe(1);
- expect(failedRequests[0].url()).toContain('one-style.css');
- expect(await failedRequests[0].response()).toBe(null);
- expect(failedRequests[0].resourceType()).toBe('stylesheet');
- if (CHROMIUM) {
- expect(failedRequests[0].failure().errorText).toBe('net::ERR_EMPTY_RESPONSE');
- } else if (WEBKIT) {
- if (MAC)
- expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.');
- else if (WIN)
- expect(failedRequests[0].failure().errorText).toBe('Server returned nothing (no headers, no data)');
- else
- expect(failedRequests[0].failure().errorText).toBe('Message Corrupt');
- } else {
- expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_NET_RESET');
- }
- expect(failedRequests[0].frame()).toBeTruthy();
- });
- it('Page.Events.RequestFinished', async({page, server}) => {
- const [response] = await Promise.all([
- page.goto(server.EMPTY_PAGE),
- page.waitForEvent('requestfinished')
- ]);
- const request = response.request();
- expect(request.url()).toBe(server.EMPTY_PAGE);
- expect(await request.response()).toBeTruthy();
- expect(request.frame() === page.mainFrame()).toBe(true);
- expect(request.frame().url()).toBe(server.EMPTY_PAGE);
- expect(request.failure()).toBe(null);
- });
- it('should fire events in proper order', async({page, server}) => {
- const events = [];
- page.on('request', request => events.push('request'));
- page.on('response', response => events.push('response'));
- const response = await page.goto(server.EMPTY_PAGE);
- expect(await response.finished()).toBe(null);
- events.push('requestfinished')
- expect(events).toEqual(['request', 'response', 'requestfinished']);
- });
- it('should support redirects', async({page, server}) => {
- const events = [];
- page.on('request', request => events.push(`${request.method()} ${request.url()}`));
- page.on('response', response => events.push(`${response.status()} ${response.url()}`));
- page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
- page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
- server.setRedirect('/foo.html', '/empty.html');
- const FOO_URL = server.PREFIX + '/foo.html';
- const response = await page.goto(FOO_URL);
- await response.finished();
- expect(events).toEqual([
- `GET ${FOO_URL}`,
- `302 ${FOO_URL}`,
- `DONE ${FOO_URL}`,
- `GET ${server.EMPTY_PAGE}`,
- `200 ${server.EMPTY_PAGE}`,
- `DONE ${server.EMPTY_PAGE}`
- ]);
- const redirectedFrom = response.request().redirectedFrom();
- expect(redirectedFrom.url()).toContain('/foo.html');
- expect(redirectedFrom.redirectedFrom()).toBe(null);
- expect(redirectedFrom.redirectedTo()).toBe(response.request());
- });
-});
-
-describe('Request.isNavigationRequest', () => {
- it('should work', async({page, server}) => {
- const requests = new Map();
- page.on('request', request => requests.set(request.url().split('/').pop(), request));
- server.setRedirect('/rrredirect', '/frames/one-frame.html');
- await page.goto(server.PREFIX + '/rrredirect');
- expect(requests.get('rrredirect').isNavigationRequest()).toBe(true);
- expect(requests.get('one-frame.html').isNavigationRequest()).toBe(true);
- expect(requests.get('frame.html').isNavigationRequest()).toBe(true);
- expect(requests.get('script.js').isNavigationRequest()).toBe(false);
- expect(requests.get('style.css').isNavigationRequest()).toBe(false);
- });
- it('should work when navigating to image', async({page, server}) => {
- const requests = [];
- page.on('request', request => requests.push(request));
- await page.goto(server.PREFIX + '/pptr.png');
- expect(requests[0].isNavigationRequest()).toBe(true);
- });
-});
-
-describe('Page.setExtraHTTPHeaders', function() {
- it('should work', async({page, server}) => {
- await page.setExtraHTTPHeaders({
- foo: 'bar'
- });
- const [request] = await Promise.all([
- server.waitForRequest('/empty.html'),
- page.goto(server.EMPTY_PAGE),
- ]);
- expect(request.headers['foo']).toBe('bar');
- });
- it('should work with redirects', async({page, server}) => {
- server.setRedirect('/foo.html', '/empty.html');
- await page.setExtraHTTPHeaders({
- foo: 'bar'
- });
- const [request] = await Promise.all([
- server.waitForRequest('/empty.html'),
- page.goto(server.PREFIX + '/foo.html'),
- ]);
- expect(request.headers['foo']).toBe('bar');
- });
- it('should work with extra headers from browser context', async({browser, server}) => {
- const context = await browser.newContext();
- await context.setExtraHTTPHeaders({
- 'foo': 'bar',
- });
- const page = await context.newPage();
- const [request] = await Promise.all([
- server.waitForRequest('/empty.html'),
- page.goto(server.EMPTY_PAGE),
- ]);
- await context.close();
- expect(request.headers['foo']).toBe('bar');
- });
- it('should override extra headers from browser context', async({browser, server}) => {
- const context = await browser.newContext({
- extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
- });
- const page = await context.newPage();
- await page.setExtraHTTPHeaders({
- 'Foo': 'Bar'
- });
- const [request] = await Promise.all([
- server.waitForRequest('/empty.html'),
- page.goto(server.EMPTY_PAGE),
- ]);
- await context.close();
- expect(request.headers['foo']).toBe('Bar');
- expect(request.headers['bar']).toBe('foO');
- });
- it('should throw for non-string header values', async({page, server}) => {
- let error = null;
- try {
- await page.setExtraHTTPHeaders({ 'foo': 1 });
- } catch (e) {
- error = e;
- }
- expect(error.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
- });
-});
diff --git a/test/page-evaluate-handle.spec.js b/test/page-evaluate-handle.spec.js
new file mode 100644
index 0000000000..3a35fa8665
--- /dev/null
+++ b/test/page-evaluate-handle.spec.js
@@ -0,0 +1,109 @@
+/**
+ * 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} = testOptions;
+
+it('should work', async({page, server}) => {
+ const windowHandle = await page.evaluateHandle(() => window);
+ expect(windowHandle).toBeTruthy();
+});
+
+it('should accept object handle as an argument', async({page, server}) => {
+ const navigatorHandle = await page.evaluateHandle(() => navigator);
+ const text = await page.evaluate(e => e.userAgent, navigatorHandle);
+ expect(text).toContain('Mozilla');
+});
+
+it('should accept object handle to primitive types', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => 5);
+ const isFive = await page.evaluate(e => Object.is(e, 5), aHandle);
+ expect(isFive).toBeTruthy();
+});
+
+it('should accept nested handle', async({page, server}) => {
+ const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
+ const result = await page.evaluate(({ foo }) => {
+ return foo;
+ }, { foo });
+ expect(result).toEqual({ x: 1, y: 'foo' });
+});
+
+it('should accept nested window handle', async({page, server}) => {
+ const foo = await page.evaluateHandle(() => window);
+ const result = await page.evaluate(({ foo }) => {
+ return foo === window;
+ }, { foo });
+ expect(result).toBe(true);
+});
+
+it('should accept multiple nested handles', async({page, server}) => {
+ const foo = await page.evaluateHandle(() => ({ x: 1, y: 'foo' }));
+ const bar = await page.evaluateHandle(() => 5);
+ const baz = await page.evaluateHandle(() => (['baz']));
+ const result = await page.evaluate(x => {
+ return JSON.stringify(x);
+ }, { a1: { foo }, a2: { bar, arr: [{ baz }] } });
+ expect(JSON.parse(result)).toEqual({
+ a1: { foo: { x: 1, y: 'foo' } },
+ a2: { bar: 5, arr: [{ baz: ['baz'] }] }
+ });
+});
+
+it('should throw for circular objects', async({page, server}) => {
+ const a = { x: 1 };
+ a.y = a;
+ const error = await page.evaluate(x => x, a).catch(e => e);
+ expect(error.message).toContain('Argument is a circular structure');
+});
+
+it('should accept same handle multiple times', async({page, server}) => {
+ const foo = await page.evaluateHandle(() => 1);
+ expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: 1, bar: [1], baz: { foo: 1 } });
+});
+
+it('should accept same nested object multiple times', async({page, server}) => {
+ const foo = { x: 1 };
+ expect(await page.evaluate(x => x, { foo, bar: [foo], baz: { foo }})).toEqual({ foo: { x: 1 }, bar: [{ x : 1 }], baz: { foo: { x : 1 } } });
+});
+
+it('should accept object handle to unserializable value', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => Infinity);
+ expect(await page.evaluate(e => Object.is(e, Infinity), aHandle)).toBe(true);
+});
+
+it('should pass configurable args', async({page, server}) => {
+ const result = await page.evaluate(arg => {
+ if (arg.foo !== 42)
+ throw new Error('Not a 42');
+ arg.foo = 17;
+ if (arg.foo !== 17)
+ throw new Error('Not 17');
+ delete arg.foo;
+ if (arg.foo === 17)
+ throw new Error('Still 17');
+ return arg;
+ }, { foo: 42 });
+ expect(result).toEqual({});
+});
+
+it('should work with primitives', async({page, server}) => {
+ const aHandle = await page.evaluateHandle(() => {
+ window.FOO = 123;
+ return window;
+ });
+ expect(await page.evaluate(e => e.FOO, aHandle)).toBe(123);
+});
diff --git a/test/page-event-network.spec.js b/test/page-event-network.spec.js
new file mode 100644
index 0000000000..920689c7ab
--- /dev/null
+++ b/test/page-event-network.spec.js
@@ -0,0 +1,119 @@
+/**
+ * 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 fs = require('fs');
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
+
+it('Page.Events.Request', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+ expect(requests[0].url()).toBe(server.EMPTY_PAGE);
+ expect(requests[0].resourceType()).toBe('document');
+ expect(requests[0].method()).toBe('GET');
+ expect(await requests[0].response()).toBeTruthy();
+ expect(requests[0].frame() === page.mainFrame()).toBe(true);
+ expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE);
+});
+
+it('Page.Events.Response', async({page, server}) => {
+ const responses = [];
+ page.on('response', response => responses.push(response));
+ await page.goto(server.EMPTY_PAGE);
+ expect(responses.length).toBe(1);
+ expect(responses[0].url()).toBe(server.EMPTY_PAGE);
+ expect(responses[0].status()).toBe(200);
+ expect(responses[0].ok()).toBe(true);
+ expect(responses[0].request()).toBeTruthy();
+});
+
+it('Page.Events.RequestFailed', async({page, server}) => {
+ server.setRoute('/one-style.css', (req, res) => {
+ res.setHeader('Content-Type', 'text/css');
+ res.socket.destroy();
+ });
+ const failedRequests = [];
+ page.on('requestfailed', request => failedRequests.push(request));
+ await page.goto(server.PREFIX + '/one-style.html');
+ expect(failedRequests.length).toBe(1);
+ expect(failedRequests[0].url()).toContain('one-style.css');
+ expect(await failedRequests[0].response()).toBe(null);
+ expect(failedRequests[0].resourceType()).toBe('stylesheet');
+ if (CHROMIUM) {
+ expect(failedRequests[0].failure().errorText).toBe('net::ERR_EMPTY_RESPONSE');
+ } else if (WEBKIT) {
+ if (MAC)
+ expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.');
+ else if (WIN)
+ expect(failedRequests[0].failure().errorText).toBe('Server returned nothing (no headers, no data)');
+ else
+ expect(failedRequests[0].failure().errorText).toBe('Message Corrupt');
+ } else {
+ expect(failedRequests[0].failure().errorText).toBe('NS_ERROR_NET_RESET');
+ }
+ expect(failedRequests[0].frame()).toBeTruthy();
+});
+
+it('Page.Events.RequestFinished', async({page, server}) => {
+ const [response] = await Promise.all([
+ page.goto(server.EMPTY_PAGE),
+ page.waitForEvent('requestfinished')
+ ]);
+ const request = response.request();
+ expect(request.url()).toBe(server.EMPTY_PAGE);
+ expect(await request.response()).toBeTruthy();
+ expect(request.frame() === page.mainFrame()).toBe(true);
+ expect(request.frame().url()).toBe(server.EMPTY_PAGE);
+ expect(request.failure()).toBe(null);
+});
+
+it('should fire events in proper order', async({page, server}) => {
+ const events = [];
+ page.on('request', request => events.push('request'));
+ page.on('response', response => events.push('response'));
+ const response = await page.goto(server.EMPTY_PAGE);
+ expect(await response.finished()).toBe(null);
+ events.push('requestfinished')
+ expect(events).toEqual(['request', 'response', 'requestfinished']);
+});
+
+it('should support redirects', async({page, server}) => {
+ const events = [];
+ page.on('request', request => events.push(`${request.method()} ${request.url()}`));
+ page.on('response', response => events.push(`${response.status()} ${response.url()}`));
+ page.on('requestfinished', request => events.push(`DONE ${request.url()}`));
+ page.on('requestfailed', request => events.push(`FAIL ${request.url()}`));
+ server.setRedirect('/foo.html', '/empty.html');
+ const FOO_URL = server.PREFIX + '/foo.html';
+ const response = await page.goto(FOO_URL);
+ await response.finished();
+ expect(events).toEqual([
+ `GET ${FOO_URL}`,
+ `302 ${FOO_URL}`,
+ `DONE ${FOO_URL}`,
+ `GET ${server.EMPTY_PAGE}`,
+ `200 ${server.EMPTY_PAGE}`,
+ `DONE ${server.EMPTY_PAGE}`
+ ]);
+ const redirectedFrom = response.request().redirectedFrom();
+ expect(redirectedFrom.url()).toContain('/foo.html');
+ expect(redirectedFrom.redirectedFrom()).toBe(null);
+ expect(redirectedFrom.redirectedTo()).toBe(response.request());
+});
diff --git a/test/page-popup-event.spec.js b/test/page-event-popup.spec.js
similarity index 100%
rename from test/page-popup-event.spec.js
rename to test/page-event-popup.spec.js
diff --git a/test/page-event-request.spec.js b/test/page-event-request.spec.js
new file mode 100644
index 0000000000..3d3252a728
--- /dev/null
+++ b/test/page-event-request.spec.js
@@ -0,0 +1,58 @@
+/**
+ * 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 fs = require('fs');
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
+
+it('should fire for navigation requests', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ expect(requests.length).toBe(1);
+});
+
+it('should fire for iframes', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ expect(requests.length).toBe(2);
+});
+
+it('should fire for fetches', async({page, server}) => {
+ const requests = [];
+ page.on('request', request => requests.push(request));
+ await page.goto(server.EMPTY_PAGE);
+ await page.evaluate(() => fetch('/empty.html'));
+ expect(requests.length).toBe(2);
+});
+
+it('should report requests and responses handled by service worker', async({page, server}) => {
+ await page.goto(server.PREFIX + '/serviceworkers/fetchdummy/sw.html');
+ await page.evaluate(() => window.activationPromise);
+ const [swResponse, request] = await Promise.all([
+ page.evaluate(() => fetchDummy('foo')),
+ page.waitForEvent('request'),
+ ]);
+ expect(swResponse).toBe('responseFromServiceWorker:foo');
+ expect(request.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
+ const response = await request.response();
+ expect(response.url()).toBe(server.PREFIX + '/serviceworkers/fetchdummy/foo');
+ expect(await response.text()).toBe('responseFromServiceWorker:foo');
+});
diff --git a/test/page-screenshot.spec.js b/test/page-screenshot.spec.js
new file mode 100644
index 0000000000..ed6805059a
--- /dev/null
+++ b/test/page-screenshot.spec.js
@@ -0,0 +1,249 @@
+/**
+ * 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, USES_HOOKS, HEADLESS} = testOptions;
+const {PNG} = require('pngjs');
+
+// Firefox headful produces a different image.
+const ffheadful = FFOX && !HEADLESS;
+
+it.skip(ffheadful)('should work', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-sanity.png');
+});
+
+it.skip(ffheadful)('should clip rect', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 50,
+ y: 100,
+ width: 150,
+ height: 100
+ }
+ });
+ expect(screenshot).toBeGolden('screenshot-clip-rect.png');
+});
+
+it.skip(ffheadful)('should clip rect with fullPage', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ await page.evaluate(() => window.scrollBy(150, 200));
+ const screenshot = await page.screenshot({
+ fullPage: true,
+ clip: {
+ x: 50,
+ y: 100,
+ width: 150,
+ height: 100,
+ },
+ });
+ expect(screenshot).toBeGolden('screenshot-clip-rect.png');
+});
+
+it.skip(ffheadful)('should clip elements to the viewport', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 50,
+ y: 450,
+ width: 1000,
+ height: 100
+ }
+ });
+ expect(screenshot).toBeGolden('screenshot-offscreen-clip.png');
+});
+
+it.skip(ffheadful)('should throw on clip outside the viewport', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshotError = await page.screenshot({
+ clip: {
+ x: 50,
+ y: 650,
+ width: 100,
+ height: 100
+ }
+ }).catch(error => error);
+ expect(screenshotError.message).toContain('Clipped area is either empty or outside the resulting image');
+});
+
+it.skip(ffheadful)('should run in parallel', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const promises = [];
+ for (let i = 0; i < 3; ++i) {
+ promises.push(page.screenshot({
+ clip: {
+ x: 50 * i,
+ y: 0,
+ width: 50,
+ height: 50
+ }
+ }));
+ }
+ const screenshots = await Promise.all(promises);
+ expect(screenshots[1]).toBeGolden('grid-cell-1.png');
+});
+
+it.skip(ffheadful)('should take fullPage screenshots', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({
+ fullPage: true
+ });
+ expect(screenshot).toBeGolden('screenshot-grid-fullpage.png');
+});
+
+it.skip(ffheadful)('should restore viewport after fullPage screenshot', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(screenshot).toBeInstanceOf(Buffer);
+ await utils.verifyViewport(page, 500, 500);
+});
+
+it.skip(ffheadful)('should run in parallel in multiple pages', async({page, server, context}) => {
+ const N = 5;
+ const pages = await Promise.all(Array(N).fill(0).map(async() => {
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ return page;
+ }));
+ const promises = [];
+ for (let i = 0; i < N; ++i)
+ promises.push(pages[i].screenshot({ clip: { x: 50 * (i % 2), y: 0, width: 50, height: 50 } }));
+ const screenshots = await Promise.all(promises);
+ for (let i = 0; i < N; ++i)
+ expect(screenshots[i]).toBeGolden(`grid-cell-${i % 2}.png`);
+ await Promise.all(pages.map(page => page.close()));
+});
+
+it.fail(FFOX)('should allow transparency', async({page}) => {
+ await page.setViewportSize({ width: 50, height: 150 });
+ await page.setContent(`
+
+
+
+
+ `);
+ const screenshot = await page.screenshot({omitBackground: true});
+ expect(screenshot).toBeGolden('transparent.png');
+});
+
+it.skip(ffheadful)('should render white background on jpeg file', async({page, server}) => {
+ await page.setViewportSize({ width: 100, height: 100 });
+ await page.goto(server.EMPTY_PAGE);
+ const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'});
+ expect(screenshot).toBeGolden('white.jpg');
+});
+
+it.skip(ffheadful)('should work with odd clip size on Retina displays', async({page}) => {
+ const screenshot = await page.screenshot({
+ clip: {
+ x: 0,
+ y: 0,
+ width: 11,
+ height: 11,
+ }
+ });
+ expect(screenshot).toBeGolden('screenshot-clip-odd-size.png');
+});
+
+it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
+ const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/overflow.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-mobile.png');
+ await context.close();
+});
+
+it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server}) => {
+ const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/overflow.html');
+ const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } });
+ expect(screenshot).toBeGolden('screenshot-mobile-clip.png');
+ await context.close();
+});
+
+it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server}) => {
+ const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/overflow-large.html');
+ const screenshot = await page.screenshot({ fullPage: true });
+ expect(screenshot).toBeGolden('screenshot-mobile-fullpage.png');
+ await context.close();
+});
+
+it.skip(ffheadful)('should work for canvas', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/screenshots/canvas.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-canvas.png');
+});
+
+it.skip(ffheadful)('should work for translateZ', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/screenshots/translateZ.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-translateZ.png');
+});
+
+it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server}) => {
+ await page.setViewportSize({width: 640, height: 480});
+ await page.goto(server.PREFIX + '/screenshots/webgl.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-webgl.png');
+});
+
+it.skip(ffheadful)('should work while navigating', async({page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/redirectloop1.html');
+ for (let i = 0; i < 10; i++) {
+ const screenshot = await page.screenshot({ fullPage: true }).catch(e => {
+ if (e.message.includes('Cannot take a screenshot while page is navigating'))
+ return Buffer.from('');
+ throw e;
+ });
+ expect(screenshot).toBeInstanceOf(Buffer);
+ }
+});
+
+it.skip(ffheadful)('should work with device scale factor', async({browser, server}) => {
+ const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
+ const page = await context.newPage();
+ await page.goto(server.PREFIX + '/grid.html');
+ const screenshot = await page.screenshot();
+ expect(screenshot).toBeGolden('screenshot-device-scale-factor.png');
+ await context.close();
+});
+
+it.skip(ffheadful)('should work with iframe in shadow', async({browser, page, server}) => {
+ await page.setViewportSize({width: 500, height: 500});
+ await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html');
+ expect(await page.screenshot()).toBeGolden('screenshot-iframe.png');
+});
diff --git a/test/page-set-extra-http-headers.spec.js b/test/page-set-extra-http-headers.spec.js
new file mode 100644
index 0000000000..dc1f0f0602
--- /dev/null
+++ b/test/page-set-extra-http-headers.spec.js
@@ -0,0 +1,86 @@
+/**
+ * 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 fs = require('fs');
+const path = require('path');
+const utils = require('./utils');
+const {FFOX, CHROMIUM, WEBKIT, MAC, WIN} = testOptions;
+
+
+it('should work', async({page, server}) => {
+ await page.setExtraHTTPHeaders({
+ foo: 'bar'
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ expect(request.headers['foo']).toBe('bar');
+});
+
+it('should work with redirects', async({page, server}) => {
+ server.setRedirect('/foo.html', '/empty.html');
+ await page.setExtraHTTPHeaders({
+ foo: 'bar'
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.PREFIX + '/foo.html'),
+ ]);
+ expect(request.headers['foo']).toBe('bar');
+});
+
+it('should work with extra headers from browser context', async({browser, server}) => {
+ const context = await browser.newContext();
+ await context.setExtraHTTPHeaders({
+ 'foo': 'bar',
+ });
+ const page = await context.newPage();
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ await context.close();
+ expect(request.headers['foo']).toBe('bar');
+});
+
+it('should override extra headers from browser context', async({browser, server}) => {
+ const context = await browser.newContext({
+ extraHTTPHeaders: { 'fOo': 'bAr', 'baR': 'foO' },
+ });
+ const page = await context.newPage();
+ await page.setExtraHTTPHeaders({
+ 'Foo': 'Bar'
+ });
+ const [request] = await Promise.all([
+ server.waitForRequest('/empty.html'),
+ page.goto(server.EMPTY_PAGE),
+ ]);
+ await context.close();
+ expect(request.headers['foo']).toBe('Bar');
+ expect(request.headers['bar']).toBe('foO');
+});
+
+it('should throw for non-string header values', async({page, server}) => {
+ let error = null;
+ try {
+ await page.setExtraHTTPHeaders({ 'foo': 1 });
+ } catch (e) {
+ error = e;
+ }
+ expect(error.message).toContain('Expected value of header "foo" to be String, but "number" is found.');
+});
diff --git a/test/screenshot.jest.js b/test/screenshot.jest.js
deleted file mode 100644
index 9ec7be161e..0000000000
--- a/test/screenshot.jest.js
+++ /dev/null
@@ -1,558 +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, USES_HOOKS, HEADLESS} = testOptions;
-const {PNG} = require('pngjs');
-
-// Firefox headful produces a different image.
-const ffheadful = FFOX && !HEADLESS;
-
-describe.skip(ffheadful)('Page.screenshot', function() {
- it('should work', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-sanity.png');
- });
- it('should clip rect', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot({
- clip: {
- x: 50,
- y: 100,
- width: 150,
- height: 100
- }
- });
- expect(screenshot).toBeGolden('screenshot-clip-rect.png');
- });
- it('should clip rect with fullPage', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- await page.evaluate(() => window.scrollBy(150, 200));
- const screenshot = await page.screenshot({
- fullPage: true,
- clip: {
- x: 50,
- y: 100,
- width: 150,
- height: 100,
- },
- });
- expect(screenshot).toBeGolden('screenshot-clip-rect.png');
- });
- it('should clip elements to the viewport', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot({
- clip: {
- x: 50,
- y: 450,
- width: 1000,
- height: 100
- }
- });
- expect(screenshot).toBeGolden('screenshot-offscreen-clip.png');
- });
- it('should throw on clip outside the viewport', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshotError = await page.screenshot({
- clip: {
- x: 50,
- y: 650,
- width: 100,
- height: 100
- }
- }).catch(error => error);
- expect(screenshotError.message).toContain('Clipped area is either empty or outside the resulting image');
- });
- it('should run in parallel', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const promises = [];
- for (let i = 0; i < 3; ++i) {
- promises.push(page.screenshot({
- clip: {
- x: 50 * i,
- y: 0,
- width: 50,
- height: 50
- }
- }));
- }
- const screenshots = await Promise.all(promises);
- expect(screenshots[1]).toBeGolden('grid-cell-1.png');
- });
- it('should take fullPage screenshots', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot({
- fullPage: true
- });
- expect(screenshot).toBeGolden('screenshot-grid-fullpage.png');
- });
- it('should restore viewport after fullPage screenshot', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot({ fullPage: true });
- expect(screenshot).toBeInstanceOf(Buffer);
- await utils.verifyViewport(page, 500, 500);
- });
- it('should run in parallel in multiple pages', async({page, server, context}) => {
- const N = 5;
- const pages = await Promise.all(Array(N).fill(0).map(async() => {
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- return page;
- }));
- const promises = [];
- for (let i = 0; i < N; ++i)
- promises.push(pages[i].screenshot({ clip: { x: 50 * (i % 2), y: 0, width: 50, height: 50 } }));
- const screenshots = await Promise.all(promises);
- for (let i = 0; i < N; ++i)
- expect(screenshots[i]).toBeGolden(`grid-cell-${i % 2}.png`);
- await Promise.all(pages.map(page => page.close()));
- });
- it.fail(FFOX)('should allow transparency', async({page}) => {
- await page.setViewportSize({ width: 50, height: 150 });
- await page.setContent(`
-
-
-
-
- `);
- const screenshot = await page.screenshot({omitBackground: true});
- expect(screenshot).toBeGolden('transparent.png');
- });
- it('should render white background on jpeg file', async({page, server}) => {
- await page.setViewportSize({ width: 100, height: 100 });
- await page.goto(server.EMPTY_PAGE);
- const screenshot = await page.screenshot({omitBackground: true, type: 'jpeg'});
- expect(screenshot).toBeGolden('white.jpg');
- });
- it('should work with odd clip size on Retina displays', async({page}) => {
- const screenshot = await page.screenshot({
- clip: {
- x: 0,
- y: 0,
- width: 11,
- height: 11,
- }
- });
- expect(screenshot).toBeGolden('screenshot-clip-odd-size.png');
- });
- it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
- const context = await browser.newContext({ viewport: { width: 320, height: 480 }, isMobile: true });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/overflow.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-mobile.png');
- await context.close();
- });
- it.skip(FFOX)('should work with a mobile viewport and clip', async({browser, server}) => {
- const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/overflow.html');
- const screenshot = await page.screenshot({ clip: { x: 10, y: 10, width: 100, height: 150 } });
- expect(screenshot).toBeGolden('screenshot-mobile-clip.png');
- await context.close();
- });
- it.skip(FFOX)('should work with a mobile viewport and fullPage', async({browser, server}) => {
- const context = await browser.newContext({viewport: { width: 320, height: 480 }, isMobile: true});
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/overflow-large.html');
- const screenshot = await page.screenshot({ fullPage: true });
- expect(screenshot).toBeGolden('screenshot-mobile-fullpage.png');
- await context.close();
- });
- it('should work for canvas', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/screenshots/canvas.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-canvas.png');
- });
- it('should work for translateZ', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/screenshots/translateZ.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-translateZ.png');
- });
- it.fail(FFOX || WEBKIT)('should work for webgl', async({page, server}) => {
- await page.setViewportSize({width: 640, height: 480});
- await page.goto(server.PREFIX + '/screenshots/webgl.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-webgl.png');
- });
- it('should work while navigating', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/redirectloop1.html');
- for (let i = 0; i < 10; i++) {
- const screenshot = await page.screenshot({ fullPage: true }).catch(e => {
- if (e.message.includes('Cannot take a screenshot while page is navigating'))
- return Buffer.from('');
- throw e;
- });
- expect(screenshot).toBeInstanceOf(Buffer);
- }
- });
- it('should work with device scale factor', async({browser, server}) => {
- const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- const screenshot = await page.screenshot();
- expect(screenshot).toBeGolden('screenshot-device-scale-factor.png');
- await context.close();
- });
- it('should work with iframe in shadow', async({browser, page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid-iframe-in-shadow.html');
- expect(await page.screenshot()).toBeGolden('screenshot-iframe.png');
- });
-});
-
-describe.skip(ffheadful)('ElementHandle.screenshot', function() {
- it('should work', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- await page.evaluate(() => window.scrollBy(50, 100));
- const elementHandle = await page.$('.box:nth-of-type(3)');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
- });
- it('should take into account padding and border', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.setContent(`
- oooo
-
-
- `);
- const elementHandle = await page.$('div#d');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-padding-border.png');
- });
- it('should capture full element when larger than viewport in parallel', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
-
- await page.setContent(`
- oooo
-
-
-
-
- `);
- const elementHandles = await page.$$('div.to-screenshot');
- const promises = elementHandles.map(handle => handle.screenshot());
- const screenshots = await Promise.all(promises);
- expect(screenshots[2]).toBeGolden('screenshot-element-larger-than-viewport.png');
-
- await utils.verifyViewport(page, 500, 500);
- });
- it('should capture full element when larger than viewport', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
-
- await page.setContent(`
- oooo
-
-
-
-
- `);
- const elementHandle = await page.$('div.to-screenshot');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png');
-
- await utils.verifyViewport(page, 500, 500);
- });
- it('should scroll element into view', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.setContent(`
- oooo
-
-
-
- `);
- const elementHandle = await page.$('div.to-screenshot');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
- });
- it('should scroll 15000px into view', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.setContent(`
- oooo
-
-
-
- `);
- const elementHandle = await page.$('div.to-screenshot');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-scrolled-into-view.png');
- });
- it('should work with a rotated element', async({page}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.setContent(`
`);
- const elementHandle = await page.$('div');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-rotate.png');
- });
- it('should fail to screenshot a detached element', async({page, server}) => {
- await page.setContent('remove this ');
- const elementHandle = await page.$('h1');
- await page.evaluate(element => element.remove(), elementHandle);
- const screenshotError = await elementHandle.screenshot().catch(error => error);
- expect(screenshotError.message).toContain('Element is not attached to the DOM');
- });
- it('should timeout waiting for visible', async({page, server}) => {
- await page.setContent('
');
- const div = await page.$('div');
- const error = await div.screenshot({ timeout: 3000 }).catch(e => e);
- expect(error.message).toContain('elementHandle.screenshot: Timeout 3000ms exceeded');
- expect(error.message).toContain('element is not visible');
- });
- it('should wait for visible', async({page, server}) => {
- await page.setViewportSize({width: 500, height: 500});
- await page.goto(server.PREFIX + '/grid.html');
- await page.evaluate(() => window.scrollBy(50, 100));
- const elementHandle = await page.$('.box:nth-of-type(3)');
- await elementHandle.evaluate(e => e.style.visibility = 'hidden');
- let done = false;
- const promise = elementHandle.screenshot().then(buffer => {
- done = true;
- return buffer;
- });
- for (let i = 0; i < 10; i++)
- await page.evaluate(() => new Promise(f => requestAnimationFrame(f)));
- expect(done).toBe(false);
- await elementHandle.evaluate(e => e.style.visibility = 'visible');
- const screenshot = await promise;
- expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
- });
- it('should work for an element with fractional dimensions', async({page}) => {
- await page.setContent('
');
- const elementHandle = await page.$('div');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-fractional.png');
- });
- it.skip(FFOX)('should work with a mobile viewport', async({browser, server}) => {
- const context = await browser.newContext({viewport: { width: 320, height: 480, isMobile: true }});
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- await page.evaluate(() => window.scrollBy(50, 100));
- const elementHandle = await page.$('.box:nth-of-type(3)');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-mobile.png');
- await context.close();
- });
- it.skip(FFOX)('should work with device scale factor', async({browser, server}) => {
- const context = await browser.newContext({ viewport: { width: 320, height: 480 }, deviceScaleFactor: 2 });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- await page.evaluate(() => window.scrollBy(50, 100));
- const elementHandle = await page.$('.box:nth-of-type(3)');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-mobile-dsf.png');
- await context.close();
- });
- it('should work for an element with an offset', async({page}) => {
- await page.setContent('
');
- const elementHandle = await page.$('div');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-fractional-offset.png');
- });
- it('should take screenshots when default viewport is null', async({server, browser}) => {
- const context = await browser.newContext({ viewport: null });
- const page = await context.newPage();
- await page.setContent(`
`);
- const windowSize = await page.evaluate(() => ({ width: window.innerWidth * window.devicePixelRatio, height: window.innerHeight * window.devicePixelRatio }));
- const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
-
- const screenshot = await page.screenshot();
- expect(screenshot).toBeInstanceOf(Buffer);
- const decoded = PNG.sync.read(screenshot);
- expect(decoded.width).toBe(windowSize.width);
- expect(decoded.height).toBe(windowSize.height);
-
- const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
- expect(sizeBefore.width).toBe(sizeAfter.width);
- expect(sizeBefore.height).toBe(sizeAfter.height);
- await context.close();
- });
- it('should take fullPage screenshots when default viewport is null', async({server, browser}) => {
- const context = await browser.newContext({ viewport: null });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
- const screenshot = await page.screenshot({
- fullPage: true
- });
- expect(screenshot).toBeInstanceOf(Buffer);
-
- const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
- expect(sizeBefore.width).toBe(sizeAfter.width);
- expect(sizeBefore.height).toBe(sizeAfter.height);
- await context.close();
- });
- it('should restore default viewport after fullPage screenshot', async({ browser }) => {
- const context = await browser.newContext({ viewport: { width: 456, height: 789 } });
- const page = await context.newPage();
- await utils.verifyViewport(page, 456, 789);
- const screenshot = await page.screenshot({ fullPage: true });
- expect(screenshot).toBeInstanceOf(Buffer);
- await utils.verifyViewport(page, 456, 789);
- await context.close();
- });
- it.skip(USES_HOOKS)('should restore viewport after page screenshot and exception', async({ browser, server }) => {
- const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- const __testHookBeforeScreenshot = () => { throw new Error('oh my') };
- const error = await page.screenshot({ fullPage: true, __testHookBeforeScreenshot }).catch(e => e);
- expect(error.message).toContain('oh my');
- await utils.verifyViewport(page, 350, 360);
- await context.close();
- });
- it.skip(USES_HOOKS)('should restore viewport after page screenshot and timeout', async({ browser, server }) => {
- const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
- const page = await context.newPage();
- await page.goto(server.PREFIX + '/grid.html');
- const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000));
- const error = await page.screenshot({ fullPage: true, __testHookAfterScreenshot, timeout: 3000 }).catch(e => e);
- expect(error.message).toContain('page.screenshot: Timeout 3000ms exceeded');
- await utils.verifyViewport(page, 350, 360);
- await page.setViewportSize({ width: 400, height: 400 });
- await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport.
- await utils.verifyViewport(page, 400, 400);
- await context.close();
- });
- it('should take element screenshot when default viewport is null and restore back', async({server, browser}) => {
- const context = await browser.newContext({viewport: null});
- const page = await context.newPage({ viewport: null });
- await page.setContent(`
- oooo
-
-
-
-
- `);
- const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
- const elementHandle = await page.$('div.to-screenshot');
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeInstanceOf(Buffer);
- const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
- expect(sizeBefore.width).toBe(sizeAfter.width);
- expect(sizeBefore.height).toBe(sizeAfter.height);
- await context.close();
- });
- it.skip(USES_HOOKS)('should restore viewport after element screenshot and exception', async({server, browser}) => {
- const context = await browser.newContext({ viewport: { width: 350, height: 360 } });
- const page = await context.newPage();
- await page.setContent(`
`);
- const elementHandle = await page.$('div');
- const __testHookBeforeScreenshot = () => { throw new Error('oh my') };
- const error = await elementHandle.screenshot({ __testHookBeforeScreenshot }).catch(e => e);
- expect(error.message).toContain('oh my');
- await utils.verifyViewport(page, 350, 360);
- await context.close();
- });
- it('should wait for element to stop moving', 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(3)');
- await elementHandle.evaluate(e => {
- e.classList.add('animation');
- return new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)));
- });
- const screenshot = await elementHandle.screenshot();
- expect(screenshot).toBeGolden('screenshot-element-bounding-box.png');
- });
- it('should take screenshot of disabled button', async({page}) => {
- await page.setViewportSize({ width: 500, height: 500 });
- await page.setContent(`Click me `);
- const button = await page.$('button');
- const screenshot = await button.screenshot();
- expect(screenshot).toBeInstanceOf(Buffer);
- });
-});
diff --git a/test/wait-for-function.spec.js b/test/wait-for-function.spec.js
new file mode 100644
index 0000000000..ae6e838d3f
--- /dev/null
+++ b/test/wait-for-function.spec.js
@@ -0,0 +1,210 @@
+/**
+ * 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, CHANNEL} = testOptions;
+
+it('should timeout', async({page, server}) => {
+ const startTime = Date.now();
+ const timeout = 42;
+ await page.waitForTimeout(timeout);
+ expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
+});
+
+it('should accept a string', async({page, server}) => {
+ const watchdog = page.waitForFunction('window.__FOO === 1');
+ await page.evaluate(() => window.__FOO = 1);
+ await watchdog;
+});
+
+it('should work when resolved right before execution context disposal', async({page, server}) => {
+ await page.addInitScript(() => window.__RELOADED = true);
+ await page.waitForFunction(() => {
+ if (!window.__RELOADED)
+ window.location.reload();
+ return true;
+ });
+});
+
+it('should poll on interval', async({page, server}) => {
+ const polling = 100;
+ const timeDelta = await page.waitForFunction(() => {
+ if (!window.__startTime) {
+ window.__startTime = Date.now();
+ return false;
+ }
+ return Date.now() - window.__startTime;
+ }, {}, {polling});
+ expect(await timeDelta.jsonValue()).not.toBeLessThan(polling);
+});
+
+it('should avoid side effects after timeout', async({page, server}) => {
+ let counter = 0;
+ page.on('console', () => ++counter);
+
+ const error = await page.waitForFunction(() => {
+ window.counter = (window.counter || 0) + 1;
+ console.log(window.counter);
+ }, {}, { polling: 1, timeout: 1000 }).catch(e => e);
+
+ const savedCounter = counter;
+ await page.waitForTimeout(2000); // Give it some time to produce more logs.
+
+ expect(error.message).toContain('page.waitForFunction: Timeout 1000ms exceeded');
+ expect(counter).toBe(savedCounter);
+});
+
+it('should throw on polling:mutation', async({page, server}) => {
+ const error = await page.waitForFunction(() => true, {}, {polling: 'mutation'}).catch(e => e);
+ expect(error.message).toContain('Unknown polling option: mutation');
+});
+
+it('should poll on raf', async({page, server}) => {
+ const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'});
+ await page.evaluate(() => window.__FOO = 'hit');
+ await watchdog;
+});
+
+it('should fail with predicate throwing on first call', async({page, server}) => {
+ const error = await page.waitForFunction(() => { throw new Error('oh my'); }).catch(e => e);
+ expect(error.message).toContain('oh my');
+});
+
+it('should fail with predicate throwing sometimes', async({page, server}) => {
+ const error = await page.waitForFunction(() => {
+ window.counter = (window.counter || 0) + 1;
+ if (window.counter === 3)
+ throw new Error('Bad counter!');
+ return window.counter === 5 ? 'result' : false;
+ }).catch(e => e);
+ expect(error.message).toContain('Bad counter!');
+});
+
+it('should fail with ReferenceError on wrong page', async({page, server}) => {
+ const error = await page.waitForFunction(() => globalVar === 123).catch(e => e);
+ expect(error.message).toContain('globalVar');
+});
+
+it('should work with strict CSP policy', async({page, server}) => {
+ server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
+ await page.goto(server.EMPTY_PAGE);
+ let error = null;
+ await Promise.all([
+ page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}).catch(e => error = e),
+ page.evaluate(() => window.__FOO = 'hit')
+ ]);
+ expect(error).toBe(null);
+});
+
+it('should throw on bad polling value', async({page, server}) => {
+ let error = null;
+ try {
+ await page.waitForFunction(() => !!document.body, {}, {polling: 'unknown'});
+ } catch (e) {
+ error = e;
+ }
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('polling');
+});
+
+it('should throw negative polling interval', async({page, server}) => {
+ let error = null;
+ try {
+ await page.waitForFunction(() => !!document.body, {}, {polling: -10});
+ } catch (e) {
+ error = e;
+ }
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('Cannot poll with non-positive interval');
+});
+
+it('should return the success value as a JSHandle', async({page}) => {
+ expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
+});
+
+it('should return the window as a success value', async({ page }) => {
+ expect(await page.waitForFunction(() => window)).toBeTruthy();
+});
+
+it('should accept ElementHandle arguments', async({page}) => {
+ await page.setContent('
');
+ const div = await page.$('div');
+ let resolved = false;
+ const waitForFunction = page.waitForFunction(element => !element.parentElement, div).then(() => resolved = true);
+ expect(resolved).toBe(false);
+ await page.evaluate(element => element.remove(), div);
+ await waitForFunction;
+});
+
+it('should respect timeout', async({page, playwright}) => {
+ let error = null;
+ await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e);
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('page.waitForFunction: Timeout 10ms exceeded');
+ expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
+});
+
+it('should respect default timeout', async({page, playwright}) => {
+ page.setDefaultTimeout(1);
+ let error = null;
+ await page.waitForFunction('false').catch(e => error = e);
+ expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
+ expect(error.message).toContain('page.waitForFunction: Timeout 1ms exceeded');
+});
+
+it('should disable timeout when its set to 0', async({page}) => {
+ const watchdog = page.waitForFunction(() => {
+ window.__counter = (window.__counter || 0) + 1;
+ return window.__injected;
+ }, {}, {timeout: 0, polling: 10});
+ await page.waitForFunction(() => window.__counter > 10);
+ await page.evaluate(() => window.__injected = true);
+ await watchdog;
+});
+
+it('should survive cross-process navigation', async({page, server}) => {
+ let fooFound = false;
+ const waitForFunction = page.waitForFunction('window.__FOO === 1').then(() => fooFound = true);
+ await page.goto(server.EMPTY_PAGE);
+ expect(fooFound).toBe(false);
+ await page.reload();
+ expect(fooFound).toBe(false);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
+ expect(fooFound).toBe(false);
+ await page.evaluate(() => window.__FOO = 1);
+ await waitForFunction;
+ expect(fooFound).toBe(true);
+});
+
+it('should survive navigations', async({page, server}) => {
+ const watchdog = page.waitForFunction(() => window.__done);
+ await page.goto(server.EMPTY_PAGE);
+ await page.goto(server.PREFIX + '/consolelog.html');
+ await page.evaluate(() => window.__done = true);
+ await watchdog;
+});
+
+it('should work with multiline body', async({page, server}) => {
+ const result = await page.waitForFunction(`
+ (() => true)()
+ `);
+ expect(await result.jsonValue()).toBe(true);
+});
+
+it('should wait for predicate with arguments', async({page, server}) => {
+ await page.waitForFunction(({arg1, arg2}) => arg1 + arg2 === 3, { arg1: 1, arg2: 2});
+});
diff --git a/test/wait-for-selector.spec.js b/test/wait-for-selector.spec.js
new file mode 100644
index 0000000000..094e45b2d6
--- /dev/null
+++ b/test/wait-for-selector.spec.js
@@ -0,0 +1,417 @@
+/**
+ * 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, CHANNEL} = testOptions;
+
+async function giveItTimeToLog(frame) {
+ await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
+ await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
+}
+
+const addElement = tag => document.body.appendChild(document.createElement(tag));
+it('should throw on waitFor', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ let error;
+ await page.waitForSelector('*', { waitFor: 'attached' }).catch(e => error = e);
+ expect(error.message).toContain('options.waitFor is not supported, did you mean options.state?');
+});
+
+it('should tolerate waitFor=visible', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ await page.waitForSelector('*', { waitFor: 'visible' }).catch(e => error = e);
+});
+
+it('should immediately resolve promise if node exists', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ await frame.waitForSelector('*');
+ await frame.evaluate(addElement, 'div');
+ await frame.waitForSelector('div', { state: 'attached'});
+});
+
+it('should work with removed MutationObserver', async({page, server}) => {
+ await page.evaluate(() => delete window.MutationObserver);
+ const [handle] = await Promise.all([
+ page.waitForSelector('.zombo'),
+ page.setContent(`anything
`),
+ ]);
+ expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
+});
+
+it('should resolve promise when node is added', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const watchdog = frame.waitForSelector('div', { state: 'attached' });
+ await frame.evaluate(addElement, 'br');
+ await frame.evaluate(addElement, 'div');
+ const eHandle = await watchdog;
+ const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
+ expect(tagName).toBe('DIV');
+});
+
+it('should report logs while waiting for visible', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const watchdog = frame.waitForSelector('div', { timeout: 5000 });
+
+ await frame.evaluate(() => {
+ const div = document.createElement('div');
+ div.className = 'foo bar';
+ div.id = 'mydiv';
+ div.setAttribute('style', 'display: none');
+ div.setAttribute('foo', '123456789012345678901234567890123456789012345678901234567890');
+ div.textContent = 'abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvwyxz';
+ document.body.appendChild(div);
+ });
+ await giveItTimeToLog(frame);
+
+ await frame.evaluate(() => document.querySelector('div').remove());
+ await giveItTimeToLog(frame);
+
+ await frame.evaluate(() => {
+ const div = document.createElement('div');
+ div.className = 'another';
+ div.style.display = 'none';
+ document.body.appendChild(div);
+ });
+ await giveItTimeToLog(frame);
+
+ const error = await watchdog.catch(e => e);
+ expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
+ expect(error.message).toContain(`waiting for selector "div" to be visible`);
+ expect(error.message).toContain(`selector resolved to hidden
`);
+});
+
+it('should report logs while waiting for hidden', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ await frame.evaluate(() => {
+ const div = document.createElement('div');
+ div.className = 'foo bar';
+ div.id = 'mydiv';
+ div.textContent = 'hello';
+ document.body.appendChild(div);
+ });
+
+ const watchdog = frame.waitForSelector('div', { state: 'hidden', timeout: 5000 });
+ await giveItTimeToLog(frame);
+
+ await frame.evaluate(() => {
+ document.querySelector('div').remove();
+ const div = document.createElement('div');
+ div.className = 'another';
+ div.textContent = 'hello';
+ document.body.appendChild(div);
+ });
+ await giveItTimeToLog(frame);
+
+ const error = await watchdog.catch(e => e);
+ expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
+ expect(error.message).toContain(`waiting for selector "div" to be hidden`);
+ expect(error.message).toContain(`selector resolved to visible hello
`);
+ expect(error.message).toContain(`selector resolved to visible hello
`);
+});
+
+it('should resolve promise when node is added in shadow dom', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const watchdog = page.waitForSelector('span');
+ await page.evaluate(() => {
+ const div = document.createElement('div');
+ div.attachShadow({mode: 'open'});
+ document.body.appendChild(div);
+ });
+ await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
+ await page.evaluate(() => {
+ const span = document.createElement('span');
+ span.textContent = 'Hello from shadow';
+ document.querySelector('div').shadowRoot.appendChild(span);
+ });
+ const handle = await watchdog;
+ expect(await handle.evaluate(e => e.textContent)).toBe('Hello from shadow');
+});
+
+it('should work when node is added through innerHTML', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const watchdog = page.waitForSelector('h3 div', { state: 'attached'});
+ await page.evaluate(addElement, 'span');
+ await page.evaluate(() => document.querySelector('span').innerHTML = '
');
+ await watchdog;
+});
+
+it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const otherFrame = page.frames()[1];
+ const watchdog = page.waitForSelector('div', { state: 'attached' });
+ await otherFrame.evaluate(addElement, 'div');
+ await page.evaluate(addElement, 'div');
+ const eHandle = await watchdog;
+ expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
+});
+
+it('should run in specified frame', async({page, server}) => {
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ const frame1 = page.frames()[1];
+ const frame2 = page.frames()[2];
+ const waitForSelectorPromise = frame2.waitForSelector('div', { state: 'attached' });
+ await frame1.evaluate(addElement, 'div');
+ await frame2.evaluate(addElement, 'div');
+ const eHandle = await waitForSelectorPromise;
+ expect(await eHandle.ownerFrame()).toBe(frame2);
+});
+
+it('should throw when frame is detached', async({page, server}) => {
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ let waitError = null;
+ const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e);
+ await utils.detachFrame(page, 'frame1');
+ await waitPromise;
+ expect(waitError).toBeTruthy();
+ expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
+});
+
+it('should survive cross-process navigation', async({page, server}) => {
+ let boxFound = false;
+ const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true);
+ await page.goto(server.EMPTY_PAGE);
+ expect(boxFound).toBe(false);
+ await page.reload();
+ expect(boxFound).toBe(false);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
+ await waitForSelector;
+ expect(boxFound).toBe(true);
+});
+
+it('should wait for visible', async({page, server}) => {
+ let divFound = false;
+ const waitForSelector = page.waitForSelector('div').then(() => divFound = true);
+ await page.setContent(`1
`);
+ expect(divFound).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
+ expect(divFound).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
+ expect(await waitForSelector).toBe(true);
+ expect(divFound).toBe(true);
+});
+
+it('should not consider visible when zero-sized', async({page, server}) => {
+ await page.setContent(`1
`);
+ let error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
+ expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
+ await page.evaluate(() => document.querySelector('div').style.width = '10px');
+ error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
+ expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
+ await page.evaluate(() => document.querySelector('div').style.height = '10px');
+ expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy();
+});
+
+it('should wait for visible recursively', async({page, server}) => {
+ let divVisible = false;
+ const waitForSelector = page.waitForSelector('div#inner').then(() => divVisible = true);
+ await page.setContent(``);
+ expect(divVisible).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
+ expect(divVisible).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
+ expect(await waitForSelector).toBe(true);
+ expect(divVisible).toBe(true);
+});
+
+it('hidden should wait for hidden', async({page, server}) => {
+ let divHidden = false;
+ await page.setContent(`content
`);
+ const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
+ await page.waitForSelector('div'); // do a round trip
+ expect(divHidden).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
+ expect(await waitForSelector).toBe(true);
+ expect(divHidden).toBe(true);
+});
+
+it('hidden should wait for display: none', async({page, server}) => {
+ let divHidden = false;
+ await page.setContent(`content
`);
+ const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
+ await page.waitForSelector('div'); // do a round trip
+ expect(divHidden).toBe(false);
+ await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
+ expect(await waitForSelector).toBe(true);
+ expect(divHidden).toBe(true);
+});
+
+it('hidden should wait for removal', async({page, server}) => {
+ await page.setContent(`content
`);
+ let divRemoved = false;
+ const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divRemoved = true);
+ await page.waitForSelector('div'); // do a round trip
+ expect(divRemoved).toBe(false);
+ await page.evaluate(() => document.querySelector('div').remove());
+ expect(await waitForSelector).toBe(true);
+ expect(divRemoved).toBe(true);
+});
+
+it('should return null if waiting to hide non-existing element', async({page, server}) => {
+ const handle = await page.waitForSelector('non-existing', { state: 'hidden' });
+ expect(handle).toBe(null);
+});
+
+it('should respect timeout', async({page, playwright}) => {
+ let error = null;
+ await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e);
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
+ expect(error.message).toContain('waiting for selector "div"');
+ expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
+});
+
+it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
+ await page.setContent(`content
`);
+ let error = null;
+ await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e);
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
+ expect(error.message).toContain('waiting for selector "div" to be hidden');
+});
+
+it('should respond to node attribute mutation', async({page, server}) => {
+ let divFound = false;
+ const waitForSelector = page.waitForSelector('.zombo', { state: 'attached'}).then(() => divFound = true);
+ await page.setContent(`
`);
+ expect(divFound).toBe(false);
+ await page.evaluate(() => document.querySelector('div').className = 'zombo');
+ expect(await waitForSelector).toBe(true);
+});
+
+it('should return the element handle', async({page, server}) => {
+ const waitForSelector = page.waitForSelector('.zombo');
+ await page.setContent(`anything
`);
+ expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
+});
+
+it('should have correct stack trace for timeout', async({page, server}) => {
+ let error;
+ await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e);
+ expect(error.stack).toContain('wait-for-selector');
+});
+
+it('should throw for unknown state option', async({page, server}) => {
+ await page.setContent('');
+ const error = await page.waitForSelector('section', { state: 'foo' }).catch(e => e);
+ expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
+});
+
+it('should throw for visibility option', async({page, server}) => {
+ await page.setContent('');
+ const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
+ expect(error.message).toContain('options.visibility is not supported, did you mean options.state?');
+});
+
+it('should throw for true state option', async({page, server}) => {
+ await page.setContent('');
+ const error = await page.waitForSelector('section', { state: true }).catch(e => e);
+ expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
+});
+
+it('should throw for false state option', async({page, server}) => {
+ await page.setContent('');
+ const error = await page.waitForSelector('section', { state: false }).catch(e => e);
+ expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
+});
+
+it('should support >> selector syntax', async({page, server}) => {
+ await page.goto(server.EMPTY_PAGE);
+ const frame = page.mainFrame();
+ const watchdog = frame.waitForSelector('css=div >> css=span', { state: 'attached'});
+ await frame.evaluate(addElement, 'br');
+ await frame.evaluate(addElement, 'div');
+ await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
+ const eHandle = await watchdog;
+ const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
+ expect(tagName).toBe('SPAN');
+});
+
+it('should wait for detached if already detached', async({page, server}) => {
+ await page.setContent('');
+ expect(await page.waitForSelector('css=div', { state: 'detached'})).toBe(null);
+});
+
+it('should wait for detached', async({page, server}) => {
+ await page.setContent('');
+ let done = false;
+ const waitFor = page.waitForSelector('css=div', { state: 'detached'}).then(() => done = true);
+ expect(done).toBe(false);
+ await page.waitForSelector('css=section');
+ expect(done).toBe(false);
+ await page.$eval('div', div => div.remove());
+ expect(await waitFor).toBe(true);
+ expect(done).toBe(true);
+});
+
+it('should support some fancy xpath', async({page, server}) => {
+ await page.setContent(`red herring
hello world
`);
+ const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]');
+ expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
+});
+
+it('should respect timeout xpath', async({page, playwright}) => {
+ let error = null;
+ await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
+ expect(error).toBeTruthy();
+ expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
+ expect(error.message).toContain('waiting for selector "//div"');
+ expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
+});
+
+it('should run in specified frame xpath', async({page, server}) => {
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
+ const frame1 = page.frames()[1];
+ const frame2 = page.frames()[2];
+ const waitForXPathPromise = frame2.waitForSelector('//div', { state: 'attached' });
+ await frame1.evaluate(addElement, 'div');
+ await frame2.evaluate(addElement, 'div');
+ const eHandle = await waitForXPathPromise;
+ expect(await eHandle.ownerFrame()).toBe(frame2);
+});
+
+it('should throw when frame is detached xpath', async({page, server}) => {
+ await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
+ const frame = page.frames()[1];
+ let waitError = null;
+ const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e);
+ await utils.detachFrame(page, 'frame1');
+ await waitPromise;
+ expect(waitError).toBeTruthy();
+ expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
+});
+
+it('should return the element handle xpath', async({page, server}) => {
+ const waitForXPath = page.waitForSelector('//*[@class="zombo"]');
+ await page.setContent(`anything
`);
+ expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
+});
+
+it('should allow you to select an element with single slash xpath', async({page, server}) => {
+ await page.setContent(`some text
`);
+ const waitForXPath = page.waitForSelector('//html/body/div');
+ expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
+});
diff --git a/test/waittask.jest.js b/test/waittask.jest.js
deleted file mode 100644
index a54ef4d8a6..0000000000
--- a/test/waittask.jest.js
+++ /dev/null
@@ -1,561 +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, CHANNEL} = testOptions;
-
-async function giveItTimeToLog(frame) {
- await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
- await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f))));
-}
-
-describe('Page.waitForTimeout', function() {
- it('should timeout', async({page, server}) => {
- const startTime = Date.now();
- const timeout = 42;
- await page.waitForTimeout(timeout);
- expect(Date.now() - startTime).not.toBeLessThan(timeout / 2);
- });
-});
-
-describe('Frame.waitForFunction', function() {
- it('should accept a string', async({page, server}) => {
- const watchdog = page.waitForFunction('window.__FOO === 1');
- await page.evaluate(() => window.__FOO = 1);
- await watchdog;
- });
- it('should work when resolved right before execution context disposal', async({page, server}) => {
- await page.addInitScript(() => window.__RELOADED = true);
- await page.waitForFunction(() => {
- if (!window.__RELOADED)
- window.location.reload();
- return true;
- });
- });
- it('should poll on interval', async({page, server}) => {
- const polling = 100;
- const timeDelta = await page.waitForFunction(() => {
- if (!window.__startTime) {
- window.__startTime = Date.now();
- return false;
- }
- return Date.now() - window.__startTime;
- }, {}, {polling});
- expect(await timeDelta.jsonValue()).not.toBeLessThan(polling);
- });
- it('should avoid side effects after timeout', async({page, server}) => {
- let counter = 0;
- page.on('console', () => ++counter);
-
- const error = await page.waitForFunction(() => {
- window.counter = (window.counter || 0) + 1;
- console.log(window.counter);
- }, {}, { polling: 1, timeout: 1000 }).catch(e => e);
-
- const savedCounter = counter;
- await page.waitForTimeout(2000); // Give it some time to produce more logs.
-
- expect(error.message).toContain('page.waitForFunction: Timeout 1000ms exceeded');
- expect(counter).toBe(savedCounter);
- });
- it('should throw on polling:mutation', async({page, server}) => {
- const error = await page.waitForFunction(() => true, {}, {polling: 'mutation'}).catch(e => e);
- expect(error.message).toContain('Unknown polling option: mutation');
- });
- it('should poll on raf', async({page, server}) => {
- const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'});
- await page.evaluate(() => window.__FOO = 'hit');
- await watchdog;
- });
- it('should fail with predicate throwing on first call', async({page, server}) => {
- const error = await page.waitForFunction(() => { throw new Error('oh my'); }).catch(e => e);
- expect(error.message).toContain('oh my');
- });
- it('should fail with predicate throwing sometimes', async({page, server}) => {
- const error = await page.waitForFunction(() => {
- window.counter = (window.counter || 0) + 1;
- if (window.counter === 3)
- throw new Error('Bad counter!');
- return window.counter === 5 ? 'result' : false;
- }).catch(e => e);
- expect(error.message).toContain('Bad counter!');
- });
- it('should fail with ReferenceError on wrong page', async({page, server}) => {
- const error = await page.waitForFunction(() => globalVar === 123).catch(e => e);
- expect(error.message).toContain('globalVar');
- });
- it('should work with strict CSP policy', async({page, server}) => {
- server.setCSP('/empty.html', 'script-src ' + server.PREFIX);
- await page.goto(server.EMPTY_PAGE);
- let error = null;
- await Promise.all([
- page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}).catch(e => error = e),
- page.evaluate(() => window.__FOO = 'hit')
- ]);
- expect(error).toBe(null);
- });
- it('should throw on bad polling value', async({page, server}) => {
- let error = null;
- try {
- await page.waitForFunction(() => !!document.body, {}, {polling: 'unknown'});
- } catch (e) {
- error = e;
- }
- expect(error).toBeTruthy();
- expect(error.message).toContain('polling');
- });
- it('should throw negative polling interval', async({page, server}) => {
- let error = null;
- try {
- await page.waitForFunction(() => !!document.body, {}, {polling: -10});
- } catch (e) {
- error = e;
- }
- expect(error).toBeTruthy();
- expect(error.message).toContain('Cannot poll with non-positive interval');
- });
- it('should return the success value as a JSHandle', async({page}) => {
- expect(await (await page.waitForFunction(() => 5)).jsonValue()).toBe(5);
- });
- it('should return the window as a success value', async({ page }) => {
- expect(await page.waitForFunction(() => window)).toBeTruthy();
- });
- it('should accept ElementHandle arguments', async({page}) => {
- await page.setContent('
');
- const div = await page.$('div');
- let resolved = false;
- const waitForFunction = page.waitForFunction(element => !element.parentElement, div).then(() => resolved = true);
- expect(resolved).toBe(false);
- await page.evaluate(element => element.remove(), div);
- await waitForFunction;
- });
- it('should respect timeout', async({page, playwright}) => {
- let error = null;
- await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('page.waitForFunction: Timeout 10ms exceeded');
- expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
- });
- it('should respect default timeout', async({page, playwright}) => {
- page.setDefaultTimeout(1);
- let error = null;
- await page.waitForFunction('false').catch(e => error = e);
- expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
- expect(error.message).toContain('page.waitForFunction: Timeout 1ms exceeded');
- });
- it('should disable timeout when its set to 0', async({page}) => {
- const watchdog = page.waitForFunction(() => {
- window.__counter = (window.__counter || 0) + 1;
- return window.__injected;
- }, {}, {timeout: 0, polling: 10});
- await page.waitForFunction(() => window.__counter > 10);
- await page.evaluate(() => window.__injected = true);
- await watchdog;
- });
- it('should survive cross-process navigation', async({page, server}) => {
- let fooFound = false;
- const waitForFunction = page.waitForFunction('window.__FOO === 1').then(() => fooFound = true);
- await page.goto(server.EMPTY_PAGE);
- expect(fooFound).toBe(false);
- await page.reload();
- expect(fooFound).toBe(false);
- await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
- expect(fooFound).toBe(false);
- await page.evaluate(() => window.__FOO = 1);
- await waitForFunction;
- expect(fooFound).toBe(true);
- });
- it('should survive navigations', async({page, server}) => {
- const watchdog = page.waitForFunction(() => window.__done);
- await page.goto(server.EMPTY_PAGE);
- await page.goto(server.PREFIX + '/consolelog.html');
- await page.evaluate(() => window.__done = true);
- await watchdog;
- });
- it('should work with multiline body', async({page, server}) => {
- const result = await page.waitForFunction(`
- (() => true)()
- `);
- expect(await result.jsonValue()).toBe(true);
- });
- it('should wait for predicate with arguments', async({page, server}) => {
- await page.waitForFunction(({arg1, arg2}) => arg1 + arg2 === 3, { arg1: 1, arg2: 2});
- });
-});
-
-describe('Frame.waitForSelector', function() {
- const addElement = tag => document.body.appendChild(document.createElement(tag));
- it('should throw on waitFor', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- let error;
- await page.waitForSelector('*', { waitFor: 'attached' }).catch(e => error = e);
- expect(error.message).toContain('options.waitFor is not supported, did you mean options.state?');
- });
- it('should tolerate waitFor=visible', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await page.waitForSelector('*', { waitFor: 'visible' }).catch(e => error = e);
- });
- it('should immediately resolve promise if node exists', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = page.mainFrame();
- await frame.waitForSelector('*');
- await frame.evaluate(addElement, 'div');
- await frame.waitForSelector('div', { state: 'attached'});
- });
- it('should work with removed MutationObserver', async({page, server}) => {
- await page.evaluate(() => delete window.MutationObserver);
- const [handle] = await Promise.all([
- page.waitForSelector('.zombo'),
- page.setContent(`anything
`),
- ]);
- expect(await page.evaluate(x => x.textContent, handle)).toBe('anything');
- });
- it('should resolve promise when node is added', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = page.mainFrame();
- const watchdog = frame.waitForSelector('div', { state: 'attached' });
- await frame.evaluate(addElement, 'br');
- await frame.evaluate(addElement, 'div');
- const eHandle = await watchdog;
- const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
- expect(tagName).toBe('DIV');
- });
- it('should report logs while waiting for visible', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = page.mainFrame();
- const watchdog = frame.waitForSelector('div', { timeout: 5000 });
-
- await frame.evaluate(() => {
- const div = document.createElement('div');
- div.className = 'foo bar';
- div.id = 'mydiv';
- div.setAttribute('style', 'display: none');
- div.setAttribute('foo', '123456789012345678901234567890123456789012345678901234567890');
- div.textContent = 'abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvwyxz';
- document.body.appendChild(div);
- });
- await giveItTimeToLog(frame);
-
- await frame.evaluate(() => document.querySelector('div').remove());
- await giveItTimeToLog(frame);
-
- await frame.evaluate(() => {
- const div = document.createElement('div');
- div.className = 'another';
- div.style.display = 'none';
- document.body.appendChild(div);
- });
- await giveItTimeToLog(frame);
-
- const error = await watchdog.catch(e => e);
- expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
- expect(error.message).toContain(`waiting for selector "div" to be visible`);
- expect(error.message).toContain(`selector resolved to hidden
`);
- });
- it('should report logs while waiting for hidden', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = page.mainFrame();
- await frame.evaluate(() => {
- const div = document.createElement('div');
- div.className = 'foo bar';
- div.id = 'mydiv';
- div.textContent = 'hello';
- document.body.appendChild(div);
- });
-
- const watchdog = frame.waitForSelector('div', { state: 'hidden', timeout: 5000 });
- await giveItTimeToLog(frame);
-
- await frame.evaluate(() => {
- document.querySelector('div').remove();
- const div = document.createElement('div');
- div.className = 'another';
- div.textContent = 'hello';
- document.body.appendChild(div);
- });
- await giveItTimeToLog(frame);
-
- const error = await watchdog.catch(e => e);
- expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`);
- expect(error.message).toContain(`waiting for selector "div" to be hidden`);
- expect(error.message).toContain(`selector resolved to visible hello
`);
- expect(error.message).toContain(`selector resolved to visible hello
`);
- });
- it('should resolve promise when node is added in shadow dom', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const watchdog = page.waitForSelector('span');
- await page.evaluate(() => {
- const div = document.createElement('div');
- div.attachShadow({mode: 'open'});
- document.body.appendChild(div);
- });
- await page.evaluate(() => new Promise(f => setTimeout(f, 100)));
- await page.evaluate(() => {
- const span = document.createElement('span');
- span.textContent = 'Hello from shadow';
- document.querySelector('div').shadowRoot.appendChild(span);
- });
- const handle = await watchdog;
- expect(await handle.evaluate(e => e.textContent)).toBe('Hello from shadow');
- });
- it('should work when node is added through innerHTML', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const watchdog = page.waitForSelector('h3 div', { state: 'attached'});
- await page.evaluate(addElement, 'span');
- await page.evaluate(() => document.querySelector('span').innerHTML = '
');
- await watchdog;
- });
- it('Page.$ waitFor is shortcut for main frame', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- const otherFrame = page.frames()[1];
- const watchdog = page.waitForSelector('div', { state: 'attached' });
- await otherFrame.evaluate(addElement, 'div');
- await page.evaluate(addElement, 'div');
- const eHandle = await watchdog;
- expect(await eHandle.ownerFrame()).toBe(page.mainFrame());
- });
- it('should run in specified frame', async({page, server}) => {
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
- const frame1 = page.frames()[1];
- const frame2 = page.frames()[2];
- const waitForSelectorPromise = frame2.waitForSelector('div', { state: 'attached' });
- await frame1.evaluate(addElement, 'div');
- await frame2.evaluate(addElement, 'div');
- const eHandle = await waitForSelectorPromise;
- expect(await eHandle.ownerFrame()).toBe(frame2);
- });
- it('should throw when frame is detached', async({page, server}) => {
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- const frame = page.frames()[1];
- let waitError = null;
- const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e);
- await utils.detachFrame(page, 'frame1');
- await waitPromise;
- expect(waitError).toBeTruthy();
- expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
- });
- it('should survive cross-process navigation', async({page, server}) => {
- let boxFound = false;
- const waitForSelector = page.waitForSelector('.box').then(() => boxFound = true);
- await page.goto(server.EMPTY_PAGE);
- expect(boxFound).toBe(false);
- await page.reload();
- expect(boxFound).toBe(false);
- await page.goto(server.CROSS_PROCESS_PREFIX + '/grid.html');
- await waitForSelector;
- expect(boxFound).toBe(true);
- });
- it('should wait for visible', async({page, server}) => {
- let divFound = false;
- const waitForSelector = page.waitForSelector('div').then(() => divFound = true);
- await page.setContent(`1
`);
- expect(divFound).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
- expect(divFound).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
- expect(await waitForSelector).toBe(true);
- expect(divFound).toBe(true);
- });
- it('should not consider visible when zero-sized', async({page, server}) => {
- await page.setContent(`1
`);
- let error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
- expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
- await page.evaluate(() => document.querySelector('div').style.width = '10px');
- error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e);
- expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
- await page.evaluate(() => document.querySelector('div').style.height = '10px');
- expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy();
- });
- it('should wait for visible recursively', async({page, server}) => {
- let divVisible = false;
- const waitForSelector = page.waitForSelector('div#inner').then(() => divVisible = true);
- await page.setContent(``);
- expect(divVisible).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.removeProperty('display'));
- expect(divVisible).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.removeProperty('visibility'));
- expect(await waitForSelector).toBe(true);
- expect(divVisible).toBe(true);
- });
- it('hidden should wait for hidden', async({page, server}) => {
- let divHidden = false;
- await page.setContent(`content
`);
- const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
- await page.waitForSelector('div'); // do a round trip
- expect(divHidden).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.setProperty('visibility', 'hidden'));
- expect(await waitForSelector).toBe(true);
- expect(divHidden).toBe(true);
- });
- it('hidden should wait for display: none', async({page, server}) => {
- let divHidden = false;
- await page.setContent(`content
`);
- const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divHidden = true);
- await page.waitForSelector('div'); // do a round trip
- expect(divHidden).toBe(false);
- await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none'));
- expect(await waitForSelector).toBe(true);
- expect(divHidden).toBe(true);
- });
- it('hidden should wait for removal', async({page, server}) => {
- await page.setContent(`content
`);
- let divRemoved = false;
- const waitForSelector = page.waitForSelector('div', { state: 'hidden' }).then(() => divRemoved = true);
- await page.waitForSelector('div'); // do a round trip
- expect(divRemoved).toBe(false);
- await page.evaluate(() => document.querySelector('div').remove());
- expect(await waitForSelector).toBe(true);
- expect(divRemoved).toBe(true);
- });
- it('should return null if waiting to hide non-existing element', async({page, server}) => {
- const handle = await page.waitForSelector('non-existing', { state: 'hidden' });
- expect(handle).toBe(null);
- });
- it('should respect timeout', async({page, playwright}) => {
- let error = null;
- await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
- expect(error.message).toContain('waiting for selector "div"');
- expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
- });
- it('should have an error message specifically for awaiting an element to be hidden', async({page, server}) => {
- await page.setContent(`content
`);
- let error = null;
- await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded');
- expect(error.message).toContain('waiting for selector "div" to be hidden');
- });
- it('should respond to node attribute mutation', async({page, server}) => {
- let divFound = false;
- const waitForSelector = page.waitForSelector('.zombo', { state: 'attached'}).then(() => divFound = true);
- await page.setContent(`
`);
- expect(divFound).toBe(false);
- await page.evaluate(() => document.querySelector('div').className = 'zombo');
- expect(await waitForSelector).toBe(true);
- });
- it('should return the element handle', async({page, server}) => {
- const waitForSelector = page.waitForSelector('.zombo');
- await page.setContent(`anything
`);
- expect(await page.evaluate(x => x.textContent, await waitForSelector)).toBe('anything');
- });
- it('should have correct stack trace for timeout', async({page, server}) => {
- let error;
- await page.waitForSelector('.zombo', { timeout: 10 }).catch(e => error = e);
- expect(error.stack).toContain('waittask');
- });
- it('should throw for unknown state option', async({page, server}) => {
- await page.setContent('');
- const error = await page.waitForSelector('section', { state: 'foo' }).catch(e => e);
- expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
- });
- it('should throw for visibility option', async({page, server}) => {
- await page.setContent('');
- const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e);
- expect(error.message).toContain('options.visibility is not supported, did you mean options.state?');
- });
- it('should throw for true state option', async({page, server}) => {
- await page.setContent('');
- const error = await page.waitForSelector('section', { state: true }).catch(e => e);
- expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
- });
- it('should throw for false state option', async({page, server}) => {
- await page.setContent('');
- const error = await page.waitForSelector('section', { state: false }).catch(e => e);
- expect(error.message).toContain('state: expected one of (attached|detached|visible|hidden)');
- });
- it('should support >> selector syntax', async({page, server}) => {
- await page.goto(server.EMPTY_PAGE);
- const frame = page.mainFrame();
- const watchdog = frame.waitForSelector('css=div >> css=span', { state: 'attached'});
- await frame.evaluate(addElement, 'br');
- await frame.evaluate(addElement, 'div');
- await frame.evaluate(() => document.querySelector('div').appendChild(document.createElement('span')));
- const eHandle = await watchdog;
- const tagName = await eHandle.getProperty('tagName').then(e => e.jsonValue());
- expect(tagName).toBe('SPAN');
- });
- it('should wait for detached if already detached', async({page, server}) => {
- await page.setContent('');
- expect(await page.waitForSelector('css=div', { state: 'detached'})).toBe(null);
- });
- it('should wait for detached', async({page, server}) => {
- await page.setContent('');
- let done = false;
- const waitFor = page.waitForSelector('css=div', { state: 'detached'}).then(() => done = true);
- expect(done).toBe(false);
- await page.waitForSelector('css=section');
- expect(done).toBe(false);
- await page.$eval('div', div => div.remove());
- expect(await waitFor).toBe(true);
- expect(done).toBe(true);
- });
-});
-
-describe('Frame.waitForSelector xpath', function() {
- const addElement = tag => document.body.appendChild(document.createElement(tag));
-
- it('should support some fancy xpath', async({page, server}) => {
- await page.setContent(`red herring
hello world
`);
- const waitForXPath = page.waitForSelector('//p[normalize-space(.)="hello world"]');
- expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world ');
- });
- it('should respect timeout', async({page, playwright}) => {
- let error = null;
- await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
- expect(error).toBeTruthy();
- expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded');
- expect(error.message).toContain('waiting for selector "//div"');
- expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
- });
- it('should run in specified frame', async({page, server}) => {
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
- const frame1 = page.frames()[1];
- const frame2 = page.frames()[2];
- const waitForXPathPromise = frame2.waitForSelector('//div', { state: 'attached' });
- await frame1.evaluate(addElement, 'div');
- await frame2.evaluate(addElement, 'div');
- const eHandle = await waitForXPathPromise;
- expect(await eHandle.ownerFrame()).toBe(frame2);
- });
- it('should throw when frame is detached', async({page, server}) => {
- await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
- const frame = page.frames()[1];
- let waitError = null;
- const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e);
- await utils.detachFrame(page, 'frame1');
- await waitPromise;
- expect(waitError).toBeTruthy();
- expect(waitError.message).toContain('waitForFunction failed: frame got detached.');
- });
- it('should return the element handle', async({page, server}) => {
- const waitForXPath = page.waitForSelector('//*[@class="zombo"]');
- await page.setContent(`anything
`);
- expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything');
- });
- it('should allow you to select an element with single slash', async({page, server}) => {
- await page.setContent(`some text
`);
- const waitForXPath = page.waitForSelector('//html/body/div');
- expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text');
- });
-});
diff --git a/test/workers.jest.js b/test/workers.jest.js
deleted file mode 100644
index e4a9ffcd8a..0000000000
--- a/test/workers.jest.js
+++ /dev/null
@@ -1,142 +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('Workers', function() {
- it('Page.workers', async function({page, server}) {
- await Promise.all([
- page.waitForEvent('worker'),
- page.goto(server.PREFIX + '/worker/worker.html')]);
- const worker = page.workers()[0];
- expect(worker.url()).toContain('worker.js');
-
- expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result');
-
- await page.goto(server.EMPTY_PAGE);
- expect(page.workers().length).toBe(0);
- });
- it('should emit created and destroyed events', async function({page}) {
- const workerCreatedPromise = page.waitForEvent('worker');
- const workerObj = await page.evaluateHandle(() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'}))));
- const worker = await workerCreatedPromise;
- const workerThisObj = await worker.evaluateHandle(() => this);
- const workerDestroyedPromise = new Promise(x => worker.once('close', x));
- await page.evaluate(workerObj => workerObj.terminate(), workerObj);
- expect(await workerDestroyedPromise).toBe(worker);
- const error = await workerThisObj.getProperty('self').catch(error => error);
- expect(error.message).toContain('Most likely the worker has been closed.');
- });
- it('should report console logs', async function({page}) {
- const [message] = await Promise.all([
- page.waitForEvent('console'),
- page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
- ]);
- expect(message.text()).toBe('1');
- });
- it('should have JSHandles for console logs', async function({page}) {
- const logPromise = new Promise(x => page.on('console', x));
- await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'}))));
- const log = await logPromise;
- expect(log.text()).toBe('1 2 3 JSHandle@object');
- expect(log.args().length).toBe(4);
- expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null');
- });
- it('should evaluate', async function({page}) {
- const workerCreatedPromise = page.waitForEvent('worker');
- await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
- const worker = await workerCreatedPromise;
- expect(await worker.evaluate('1+1')).toBe(2);
- });
- it('should report errors', async function({page}) {
- const errorPromise = new Promise(x => page.on('pageerror', x));
- await page.evaluate(() => new Worker(URL.createObjectURL(new Blob([`
- setTimeout(() => {
- // Do a console.log just to check that we do not confuse it with an error.
- console.log('hey');
- throw new Error('this is my error');
- })
- `], {type: 'application/javascript'}))));
- const errorLog = await errorPromise;
- expect(errorLog.message).toContain('this is my error');
- });
- it('should clear upon navigation', async function({server, page}) {
- await page.goto(server.EMPTY_PAGE);
- const workerCreatedPromise = page.waitForEvent('worker');
- await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
- const worker = await workerCreatedPromise;
- expect(page.workers().length).toBe(1);
- let destroyed = false;
- worker.once('close', () => destroyed = true);
- await page.goto(server.PREFIX + '/one-style.html');
- expect(destroyed).toBe(true);
- expect(page.workers().length).toBe(0);
- });
- it('should clear upon cross-process navigation', async function({server, page}) {
- await page.goto(server.EMPTY_PAGE);
- const workerCreatedPromise = page.waitForEvent('worker');
- await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
- const worker = await workerCreatedPromise;
- expect(page.workers().length).toBe(1);
- let destroyed = false;
- worker.once('close', () => destroyed = true);
- await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
- expect(destroyed).toBe(true);
- expect(page.workers().length).toBe(0);
- });
- it('should report network activity', async function({page, server}) {
- const [worker] = await Promise.all([
- page.waitForEvent('worker'),
- page.goto(server.PREFIX + '/worker/worker.html'),
- ]);
- const url = server.PREFIX + '/one-style.css';
- const requestPromise = page.waitForRequest(url);
- const responsePromise = page.waitForResponse(url);
- await worker.evaluate(url => fetch(url).then(response => response.text()).then(console.log), url);
- const request = await requestPromise;
- const response = await responsePromise;
- expect(request.url()).toBe(url);
- expect(response.request()).toBe(request);
- expect(response.ok()).toBe(true);
- });
- it('should report network activity on worker creation', async function({page, server}) {
- // Chromium needs waitForDebugger enabled for this one.
- await page.goto(server.EMPTY_PAGE);
- const url = server.PREFIX + '/one-style.css';
- const requestPromise = page.waitForRequest(url);
- const responsePromise = page.waitForResponse(url);
- await page.evaluate(url => new Worker(URL.createObjectURL(new Blob([`
- fetch("${url}").then(response => response.text()).then(console.log);
- `], {type: 'application/javascript'}))), url);
- const request = await requestPromise;
- const response = await responsePromise;
- expect(request.url()).toBe(url);
- expect(response.request()).toBe(request);
- expect(response.ok()).toBe(true);
- });
- it('should format number using context locale', async({browser, server}) => {
- const context = await browser.newContext({ locale: 'ru-RU' });
- const page = await context.newPage();
- await page.goto(server.EMPTY_PAGE);
- const [worker] = await Promise.all([
- page.waitForEvent('worker'),
- page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
- ]);
- expect(await worker.evaluate(() => (10000.20).toLocaleString())).toBe('10\u00A0000,2');
- await context.close();
- });
-});
diff --git a/test/workers.spec.js b/test/workers.spec.js
new file mode 100644
index 0000000000..d29bcdd39f
--- /dev/null
+++ b/test/workers.spec.js
@@ -0,0 +1,140 @@
+/**
+ * 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('Page.workers', async function({page, server}) {
+ await Promise.all([
+ page.waitForEvent('worker'),
+ page.goto(server.PREFIX + '/worker/worker.html')]);
+ const worker = page.workers()[0];
+ expect(worker.url()).toContain('worker.js');
+
+ expect(await worker.evaluate(() => self['workerFunction']())).toBe('worker function result');
+
+ await page.goto(server.EMPTY_PAGE);
+ expect(page.workers().length).toBe(0);
+});
+it('should emit created and destroyed events', async function({page}) {
+ const workerCreatedPromise = page.waitForEvent('worker');
+ const workerObj = await page.evaluateHandle(() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'}))));
+ const worker = await workerCreatedPromise;
+ const workerThisObj = await worker.evaluateHandle(() => this);
+ const workerDestroyedPromise = new Promise(x => worker.once('close', x));
+ await page.evaluate(workerObj => workerObj.terminate(), workerObj);
+ expect(await workerDestroyedPromise).toBe(worker);
+ const error = await workerThisObj.getProperty('self').catch(error => error);
+ expect(error.message).toContain('Most likely the worker has been closed.');
+});
+it('should report console logs', async function({page}) {
+ const [message] = await Promise.all([
+ page.waitForEvent('console'),
+ page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
+ ]);
+ expect(message.text()).toBe('1');
+});
+it('should have JSHandles for console logs', async function({page}) {
+ const logPromise = new Promise(x => page.on('console', x));
+ await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'}))));
+ const log = await logPromise;
+ expect(log.text()).toBe('1 2 3 JSHandle@object');
+ expect(log.args().length).toBe(4);
+ expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null');
+});
+it('should evaluate', async function({page}) {
+ const workerCreatedPromise = page.waitForEvent('worker');
+ await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
+ const worker = await workerCreatedPromise;
+ expect(await worker.evaluate('1+1')).toBe(2);
+});
+it('should report errors', async function({page}) {
+ const errorPromise = new Promise(x => page.on('pageerror', x));
+ await page.evaluate(() => new Worker(URL.createObjectURL(new Blob([`
+ setTimeout(() => {
+ // Do a console.log just to check that we do not confuse it with an error.
+ console.log('hey');
+ throw new Error('this is my error');
+ })
+ `], {type: 'application/javascript'}))));
+ const errorLog = await errorPromise;
+ expect(errorLog.message).toContain('this is my error');
+});
+it('should clear upon navigation', async function({server, page}) {
+ await page.goto(server.EMPTY_PAGE);
+ const workerCreatedPromise = page.waitForEvent('worker');
+ await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
+ const worker = await workerCreatedPromise;
+ expect(page.workers().length).toBe(1);
+ let destroyed = false;
+ worker.once('close', () => destroyed = true);
+ await page.goto(server.PREFIX + '/one-style.html');
+ expect(destroyed).toBe(true);
+ expect(page.workers().length).toBe(0);
+});
+it('should clear upon cross-process navigation', async function({server, page}) {
+ await page.goto(server.EMPTY_PAGE);
+ const workerCreatedPromise = page.waitForEvent('worker');
+ await page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'}))));
+ const worker = await workerCreatedPromise;
+ expect(page.workers().length).toBe(1);
+ let destroyed = false;
+ worker.once('close', () => destroyed = true);
+ await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html');
+ expect(destroyed).toBe(true);
+ expect(page.workers().length).toBe(0);
+});
+it('should report network activity', async function({page, server}) {
+ const [worker] = await Promise.all([
+ page.waitForEvent('worker'),
+ page.goto(server.PREFIX + '/worker/worker.html'),
+ ]);
+ const url = server.PREFIX + '/one-style.css';
+ const requestPromise = page.waitForRequest(url);
+ const responsePromise = page.waitForResponse(url);
+ await worker.evaluate(url => fetch(url).then(response => response.text()).then(console.log), url);
+ const request = await requestPromise;
+ const response = await responsePromise;
+ expect(request.url()).toBe(url);
+ expect(response.request()).toBe(request);
+ expect(response.ok()).toBe(true);
+});
+it('should report network activity on worker creation', async function({page, server}) {
+ // Chromium needs waitForDebugger enabled for this one.
+ await page.goto(server.EMPTY_PAGE);
+ const url = server.PREFIX + '/one-style.css';
+ const requestPromise = page.waitForRequest(url);
+ const responsePromise = page.waitForResponse(url);
+ await page.evaluate(url => new Worker(URL.createObjectURL(new Blob([`
+ fetch("${url}").then(response => response.text()).then(console.log);
+ `], {type: 'application/javascript'}))), url);
+ const request = await requestPromise;
+ const response = await responsePromise;
+ expect(request.url()).toBe(url);
+ expect(response.request()).toBe(request);
+ expect(response.ok()).toBe(true);
+});
+it('should format number using context locale', async({browser, server}) => {
+ const context = await browser.newContext({ locale: 'ru-RU' });
+ const page = await context.newPage();
+ await page.goto(server.EMPTY_PAGE);
+ const [worker] = await Promise.all([
+ page.waitForEvent('worker'),
+ page.evaluate(() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))),
+ ]);
+ expect(await worker.evaluate(() => (10000.20).toLocaleString())).toBe('10\u00A0000,2');
+ await context.close();
+});