From 573f580fa8e3b5097e1f92b0d8791486e76f3819 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 3 Aug 2020 16:30:37 -0700 Subject: [PATCH] test: remove describes (3) (#3278) --- test/elementhandle-screenshot.spec.js | 348 +++++++++++ test/jshandle-as-element.spec.js | 46 ++ test/jshandle-json-value.spec.js | 37 ++ test/jshandle-properties.spec.js | 94 +++ test/jshandle-to-string.spec.js | 59 ++ test/jshandle.jest.js | 279 --------- test/multiclient.jest.js | 109 ---- test/multiclient.spec.js | 104 ++++ test/network-request.spec.js | 192 ++++++ test/network-response.spec.js | 118 ++++ test/network.jest.js | 488 --------------- test/page-evaluate-handle.spec.js | 109 ++++ test/page-event-network.spec.js | 119 ++++ ...event.spec.js => page-event-popup.spec.js} | 0 test/page-event-request.spec.js | 58 ++ test/page-screenshot.spec.js | 249 ++++++++ test/page-set-extra-http-headers.spec.js | 86 +++ test/screenshot.jest.js | 558 ----------------- test/wait-for-function.spec.js | 210 +++++++ test/wait-for-selector.spec.js | 417 +++++++++++++ test/waittask.jest.js | 561 ------------------ test/workers.jest.js | 142 ----- test/workers.spec.js | 140 +++++ 23 files changed, 2386 insertions(+), 2137 deletions(-) create mode 100644 test/elementhandle-screenshot.spec.js create mode 100644 test/jshandle-as-element.spec.js create mode 100644 test/jshandle-json-value.spec.js create mode 100644 test/jshandle-properties.spec.js create mode 100644 test/jshandle-to-string.spec.js delete mode 100644 test/jshandle.jest.js delete mode 100644 test/multiclient.jest.js create mode 100644 test/multiclient.spec.js create mode 100644 test/network-request.spec.js create mode 100644 test/network-response.spec.js delete mode 100644 test/network.jest.js create mode 100644 test/page-evaluate-handle.spec.js create mode 100644 test/page-event-network.spec.js rename test/{page-popup-event.spec.js => page-event-popup.spec.js} (100%) create mode 100644 test/page-event-request.spec.js create mode 100644 test/page-screenshot.spec.js create mode 100644 test/page-set-extra-http-headers.spec.js delete mode 100644 test/screenshot.jest.js create mode 100644 test/wait-for-function.spec.js create mode 100644 test/wait-for-selector.spec.js delete mode 100644 test/waittask.jest.js delete mode 100644 test/workers.jest.js create mode 100644 test/workers.spec.js 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(``); + 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('
test
'); + 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('
test
'); - 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(``); - 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(`
hi
`); + 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('
test
'); + 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('
test
'); + 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('
test
'); + 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('
test
'); + 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('
43543
'); + expect(await page.waitForSelector('css=div', { state: 'detached'})).toBe(null); +}); + +it('should wait for detached', async({page, server}) => { + await page.setContent('
43543
'); + 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(`
hi
`); - 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('
test
'); - 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('
test
'); - 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('
test
'); - 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('
test
'); - 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('
43543
'); - expect(await page.waitForSelector('css=div', { state: 'detached'})).toBe(null); - }); - it('should wait for detached', async({page, server}) => { - await page.setContent('
43543
'); - 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(); +});