From 47481967c5344dc0ca17ebaf4bd3d9650da771d0 Mon Sep 17 00:00:00 2001 From: Yaniv Efraim Date: Tue, 20 Mar 2018 05:00:12 +0200 Subject: [PATCH] test: Break 'page.spec.js' to smaller files (#2218) This patch breaks huge `page.spec.js` into a bunch of smaller files. --- test/browser.spec.js | 75 ++ test/elementhandle.spec.js | 272 ++++++ test/frame.spec.js | 371 ++++++++ test/input.spec.js | 444 +++++++++ test/jshandle.spec.js | 116 +++ test/network.spec.js | 188 ++++ test/page.spec.js | 1458 +---------------------------- test/puppeteer.spec.js | 27 +- test/test.js | 37 +- test/tracing.spec.js | 56 ++ test/{frame-utils.js => utils.js} | 54 ++ 11 files changed, 1637 insertions(+), 1461 deletions(-) create mode 100644 test/browser.spec.js create mode 100644 test/elementhandle.spec.js create mode 100644 test/frame.spec.js create mode 100644 test/input.spec.js create mode 100644 test/jshandle.spec.js create mode 100644 test/network.spec.js create mode 100644 test/tracing.spec.js rename test/{frame-utils.js => utils.js} (61%) diff --git a/test/browser.spec.js b/test/browser.spec.js new file mode 100644 index 00000000000..17cbecc6f6a --- /dev/null +++ b/test/browser.spec.js @@ -0,0 +1,75 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + const headless = defaultBrowserOptions.headless; + + describe('Browser.version', function() { + it('should return whether we are in headless', async({browser}) => { + const version = await browser.version(); + expect(version.length).toBeGreaterThan(0); + expect(version.startsWith('Headless')).toBe(headless); + }); + }); + + describe('Browser.userAgent', function() { + it('should include WebKit', async({browser}) => { + const userAgent = await browser.userAgent(); + expect(userAgent.length).toBeGreaterThan(0); + expect(userAgent).toContain('WebKit'); + }); + }); + + describe('Browser.process', function() { + it('should return child_process instance', async function({browser}) { + const process = await browser.process(); + expect(process.pid).toBeGreaterThan(0); + const browserWSEndpoint = browser.wsEndpoint(); + const remoteBrowser = await puppeteer.connect({browserWSEndpoint}); + expect(remoteBrowser.process()).toBe(null); + await remoteBrowser.disconnect(); + }); + }); + + describe('Browser.Events.disconnected', function() { + it('should emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => { + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browserWSEndpoint = originalBrowser.wsEndpoint(); + const remoteBrowser1 = await puppeteer.connect({browserWSEndpoint}); + const remoteBrowser2 = await puppeteer.connect({browserWSEndpoint}); + + let disconnectedOriginal = 0; + let disconnectedRemote1 = 0; + let disconnectedRemote2 = 0; + originalBrowser.on('disconnected', () => ++disconnectedOriginal); + remoteBrowser1.on('disconnected', () => ++disconnectedRemote1); + remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); + + await remoteBrowser2.disconnect(); + expect(disconnectedOriginal).toBe(0); + expect(disconnectedRemote1).toBe(0); + expect(disconnectedRemote2).toBe(1); + + await originalBrowser.close(); + expect(disconnectedOriginal).toBe(1); + expect(disconnectedRemote1).toBe(1); + expect(disconnectedRemote2).toBe(1); + }); + }); +}; \ No newline at end of file diff --git a/test/elementhandle.spec.js b/test/elementhandle.spec.js new file mode 100644 index 00000000000..aded656c7cd --- /dev/null +++ b/test/elementhandle.spec.js @@ -0,0 +1,272 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const utils = require('./utils'); + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('ElementHandle.boundingBox', function() { + it('should work', async({page, server}) => { + await page.setViewport({width: 500, height: 500}); + await page.goto(server.PREFIX + '/grid.html'); + const elementHandle = await page.$('.box:nth-of-type(13)'); + const box = await elementHandle.boundingBox(); + expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 }); + }); + it('should handle nested frames', async({page, server}) => { + await page.setViewport({width: 500, height: 500}); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + const nestedFrame = page.frames()[1].childFrames()[1]; + const elementHandle = await nestedFrame.$('div'); + const box = await elementHandle.boundingBox(); + expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 }); + }); + it('should return null for invisible elements', async({page, server}) => { + await page.setContent('
hi
'); + const element = await page.$('div'); + expect(await element.boundingBox()).toBe(null); + }); + it('should force a layout', async({page, server}) => { + await page.setViewport({ width: 500, height: 500 }); + await page.setContent('
hello
'); + const elementHandle = await page.$('div'); + await page.evaluate(element => element.style.height = '200px', elementHandle); + const box = await elementHandle.boundingBox(); + expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); + }); + }); + + describe('ElementHandle.contentFrame', function() { + it('should work', async({page,server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const elementHandle = await page.$('#frame1'); + const frame = await elementHandle.contentFrame(); + expect(frame).toBe(page.frames()[1]); + }); + }); + + describe('ElementHandle.click', function() { + it('should work', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await button.click(); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should work for Shadow DOM v1', async({page, server}) => { + await page.goto(server.PREFIX + '/shadow.html'); + const buttonHandle = await page.evaluateHandle(() => button); + await buttonHandle.click(); + expect(await page.evaluate(() => clicked)).toBe(true); + }); + it('should work for TextNodes', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); + let error = null; + await buttonTextNode.click().catch(err => error = err); + expect(error.message).toBe('Node is not of type HTMLElement'); + }); + it('should throw for detached nodes', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.remove(), button); + let error = null; + await button.click().catch(err => error = err); + expect(error.message).toBe('Node is detached from document'); + }); + it('should throw for hidden nodes', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.style.display = 'none', button); + const error = await button.click().catch(err => err); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + }); + it('should throw for recursively hidden nodes', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + const button = await page.$('button'); + await page.evaluate(button => button.parentElement.style.display = 'none', button); + const error = await button.click().catch(err => err); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + }); + it('should throw for
elements', async({page, server}) => { + await page.setContent('hello
goodbye'); + const br = await page.$('br'); + const error = await br.click().catch(err => err); + expect(error.message).toBe('Node is either not visible or not an HTMLElement'); + }); + }); + + describe('ElementHandle.hover', function() { + it('should work', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + const button = await page.$('#button-6'); + await button.hover(); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); + }); + }); + + describe('ElementHandle.screenshot', function() { + it('should work', async({page, server}) => { + await page.setViewport({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, server}) => { + await page.setViewport({width: 500, height: 500}); + await page.setContent(` + something above + +
+ `); + const elementHandle = await page.$('div'); + const screenshot = await elementHandle.screenshot(); + expect(screenshot).toBeGolden('screenshot-element-padding-border.png'); + }); + it('should capture full element when larger than viewport', async({page, server}) => { + await page.setViewport({width: 500, height: 500}); + + await page.setContent(` + something above + +
+ `); + const elementHandle = await page.$('div.to-screenshot'); + const screenshot = await elementHandle.screenshot(); + expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png'); + + expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 }); + }); + it('should scroll element into view', async({page, server}) => { + await page.setViewport({width: 500, height: 500}); + await page.setContent(` + something above + +
+
+ `); + 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, server}) => { + await page.setViewport({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).toBe('Node is either not visible or not an HTMLElement'); + }); + }); + + describe('ElementHandle.$', function() { + it('should query existing element', async({page, server}) => { + await page.goto(server.PREFIX + '/playground.html'); + await page.setContent('
A
'); + const html = await page.$('html'); + const second = await html.$('.second'); + const inner = await second.$('.inner'); + const content = await page.evaluate(e => e.textContent, inner); + expect(content).toBe('A'); + }); + + it('should return null for non-existing element', async({page, server}) => { + await page.setContent('
B
'); + const html = await page.$('html'); + const second = await html.$('.third'); + expect(second).toBe(null); + }); + }); + + describe('ElementHandle.$$', function() { + it('should query existing elements', async({page, server}) => { + await page.setContent('
A

B
'); + const html = await page.$('html'); + const elements = await html.$$('div'); + expect(elements.length).toBe(2); + const promises = elements.map(element => page.evaluate(e => e.textContent, element)); + expect(await Promise.all(promises)).toEqual(['A', 'B']); + }); + + it('should return empty array for non-existing elements', async({page, server}) => { + await page.setContent('A
B'); + const html = await page.$('html'); + const elements = await html.$$('div'); + expect(elements.length).toBe(0); + }); + }); + + + describe('ElementHandle.$x', function() { + it('should query existing element', async({page, server}) => { + await page.goto(server.PREFIX + '/playground.html'); + await page.setContent('
A
'); + const html = await page.$('html'); + const second = await html.$x(`./body/div[contains(@class, 'second')]`); + const inner = await second[0].$x(`./div[contains(@class, 'inner')]`); + const content = await page.evaluate(e => e.textContent, inner[0]); + expect(content).toBe('A'); + }); + + it('should return null for non-existing element', async({page, server}) => { + await page.setContent('
B
'); + const html = await page.$('html'); + const second = await html.$x(`/div[contains(@class, 'third')]`); + expect(second).toEqual([]); + }); + }); +}; \ No newline at end of file diff --git a/test/frame.spec.js b/test/frame.spec.js new file mode 100644 index 00000000000..88c39b25cde --- /dev/null +++ b/test/frame.spec.js @@ -0,0 +1,371 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const utils = require('./utils'); + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('Frame.context', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + expect(page.frames().length).toBe(2); + const [frame1, frame2] = page.frames(); + const context1 = await frame1.executionContext(); + const context2 = await frame2.executionContext(); + expect(context1).toBeTruthy(); + expect(context2).toBeTruthy(); + expect(context1 !== context2).toBeTruthy(); + expect(context1.frame()).toBe(frame1); + expect(context2.frame()).toBe(frame2); + + await Promise.all([ + context1.evaluate(() => window.a = 1), + context2.evaluate(() => window.a = 2) + ]); + const [a1, a2] = await Promise.all([ + context1.evaluate(() => window.a), + context2.evaluate(() => window.a) + ]); + expect(a1).toBe(1); + expect(a2).toBe(2); + }); + }); + + describe('Frame.evaluateHandle', function() { + it('should work', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const mainFrame = page.mainFrame(); + const windowHandle = await mainFrame.evaluateHandle(() => window); + expect(windowHandle).toBeTruthy(); + }); + }); + + describe('Frame.evaluate', function() { + it('should have different execution contexts', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + expect(page.frames().length).toBe(2); + const frame1 = page.frames()[0]; + const frame2 = page.frames()[1]; + await frame1.evaluate(() => window.FOO = 'foo'); + await frame2.evaluate(() => window.FOO = 'bar'); + expect(await frame1.evaluate(() => window.FOO)).toBe('foo'); + expect(await frame2.evaluate(() => window.FOO)).toBe('bar'); + }); + it('should execute after cross-site navigation', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const mainFrame = page.mainFrame(); + expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost'); + await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); + expect(await mainFrame.evaluate(() => window.location.href)).toContain('127'); + }); + }); + + 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 poll on interval', async({page, server}) => { + let success = false; + const startTime = Date.now(); + const polling = 100; + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling}) + .then(() => success = true); + await page.evaluate(() => window.__FOO = 'hit'); + expect(success).toBe(false); + await page.evaluate(() => document.body.appendChild(document.createElement('div'))); + await watchdog; + expect(Date.now() - startTime).not.toBeLessThan(polling / 2); + }); + it('should poll on mutation', async({page, server}) => { + let success = false; + const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'mutation'}) + .then(() => success = true); + await page.evaluate(() => window.__FOO = 'hit'); + expect(success).toBe(false); + await page.evaluate(() => document.body.appendChild(document.createElement('div'))); + await watchdog; + }); + 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 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; + }); + }); + + describe('Frame.waitForSelector', function() { + const addElement = tag => document.body.appendChild(document.createElement(tag)); + + 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'); + }); + + 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'); + 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 work when node is added through innerHTML', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + const watchdog = page.waitForSelector('h3 div'); + await page.evaluate(addElement, 'span'); + await page.evaluate(() => document.querySelector('span').innerHTML = '

'); + await watchdog; + }); + + it('Page.waitForSelector 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'); + await otherFrame.evaluate(addElement, 'div'); + await page.evaluate(addElement, 'div'); + const eHandle = await watchdog; + expect(eHandle.executionContext().frame()).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'); + await frame1.evaluate(addElement, 'div'); + await frame2.evaluate(addElement, 'div'); + const eHandle = await waitForSelectorPromise; + expect(eHandle.executionContext().frame()).toBe(frame2); + }); + + it('should throw if evaluation failed', async({page, server}) => { + await page.evaluateOnNewDocument(function() { + document.querySelector = null; + }); + await page.goto(server.EMPTY_PAGE); + let error = null; + await page.waitForSelector('*').catch(e => error = e); + expect(error.message).toContain('document.querySelector is not a function'); + }); + 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', {visible: true}).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 wait for visible recursively', async({page, server}) => { + let divVisible = false; + const waitForSelector = page.waitForSelector('div#inner', {visible: true}).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 visibility: hidden', async({page, server}) => { + let divHidden = false; + await page.setContent(`
`); + const waitForSelector = page.waitForSelector('div', {hidden: true}).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(`
`); + const waitForSelector = page.waitForSelector('div', {hidden: true}).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(`
`); + let divRemoved = false; + const waitForSelector = page.waitForSelector('div', {hidden: true}).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 respect timeout', async({page, server}) => { + let error = null; + await page.waitForSelector('div', {timeout: 10}).catch(e => error = e); + expect(error).toBeTruthy(); + expect(error.message).toContain('waiting failed: timeout'); + }); + + it('should respond to node attribute mutation', async({page, server}) => { + let divFound = false; + const waitForSelector = page.waitForSelector('.zombo').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'); + }); + }); + + describe('Frame.waitForXPath', 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.waitForXPath('//p[normalize-space(.)="hello world"]'); + expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world '); + }); + 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.waitForXPath('//div'); + await frame1.evaluate(addElement, 'div'); + await frame2.evaluate(addElement, 'div'); + const eHandle = await waitForXPathPromise; + expect(eHandle.executionContext().frame()).toBe(frame2); + }); + it('should throw if evaluation failed', async({page, server}) => { + await page.evaluateOnNewDocument(function() { + document.evaluate = null; + }); + await page.goto(server.EMPTY_PAGE); + let error = null; + await page.waitForXPath('*').catch(e => error = e); + expect(error.message).toContain('document.evaluate is not a function'); + }); + 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.waitForXPath('//*[@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('hidden should wait for display: none', async({page, server}) => { + let divHidden = false; + await page.setContent(`
`); + const waitForXPath = page.waitForXPath('//div', {hidden: true}).then(() => divHidden = true); + await page.waitForXPath('//div'); // do a round trip + expect(divHidden).toBe(false); + await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); + expect(await waitForXPath).toBe(true); + expect(divHidden).toBe(true); + }); + it('should return the element handle', async({page, server}) => { + const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); + await page.setContent(`
anything
`); + expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); + }); + it('should allow you to select a text node', async({page, server}) => { + await page.setContent(`
some text
`); + const text = await page.waitForXPath('//div/text()'); + expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(3 /* Node.TEXT_NODE */); + }); + it('should allow you to select an element with single slash', async({page, server}) => { + await page.setContent(`
some text
`); + const waitForXPath = page.waitForXPath('/html/body/div'); + expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); + }); + }); +}; \ No newline at end of file diff --git a/test/input.spec.js b/test/input.spec.js new file mode 100644 index 00000000000..ff07275257d --- /dev/null +++ b/test/input.spec.js @@ -0,0 +1,444 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); +const utils = require('./utils'); + +module.exports.addTests = function({testRunner, expect, PROJECT_ROOT}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + const DeviceDescriptors = require(path.join(PROJECT_ROOT, 'DeviceDescriptors')); + const iPhone = DeviceDescriptors['iPhone 6']; + describe('input', function() { + it('should click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + + it('should click on checkbox input and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'mouseover', + 'mouseenter', + 'mousemove', + 'mousedown', + 'mouseup', + 'click', + 'input', + 'change', + ]); + await page.click('input#agree'); + expect(await page.evaluate(() => result.check)).toBe(false); + }); + + it('should click on checkbox label and toggle', async({page, server}) => { + await page.goto(server.PREFIX + '/input/checkbox.html'); + expect(await page.evaluate(() => result.check)).toBe(null); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(true); + expect(await page.evaluate(() => result.events)).toEqual([ + 'click', + 'input', + 'change', + ]); + await page.click('label[for="agree"]'); + expect(await page.evaluate(() => result.check)).toBe(false); + }); + + it('should fail to click a missing button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + let error = null; + await page.click('button.does-not-exist').catch(e => error = e); + expect(error.message).toBe('No node found for selector: button.does-not-exist'); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/161 + it('should not hang with touch-enabled viewports', async({page, server}) => { + await page.setViewport(iPhone.viewport); + await page.mouse.down(); + await page.mouse.move(100, 10); + await page.mouse.up(); + }); + it('should type into the textarea', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + + const textarea = await page.$('textarea'); + await textarea.type('Type in this text!'); + expect(await page.evaluate(() => result)).toBe('Type in this text!'); + }); + it('should click the button after navigation ', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + await page.goto(server.PREFIX + '/input/button.html'); + await page.click('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + it('should upload the file', async({page, server}) => { + await page.goto(server.PREFIX + '/input/fileupload.html'); + const filePath = path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'); + const input = await page.$('input'); + await input.uploadFile(filePath); + expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); + expect(await page.evaluate(e => { + const reader = new FileReader(); + const promise = new Promise(fulfill => reader.onload = fulfill); + reader.readAsText(e.files[0]); + return promise.then(() => reader.result); + }, input)).toBe('contents of the file'); + }); + it('should move with the arrow keys', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.type('textarea', 'Hello World!'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); + for (let i = 0; i < 'World!'.length; i++) + page.keyboard.press('ArrowLeft'); + await page.keyboard.type('inserted '); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!'); + page.keyboard.down('Shift'); + for (let i = 0; i < 'inserted '.length; i++) + page.keyboard.press('ArrowLeft'); + page.keyboard.up('Shift'); + await page.keyboard.press('Backspace'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); + }); + it('should send a character with ElementHandle.press', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const textarea = await page.$('textarea'); + await textarea.press('a', {text: 'f'}); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('f'); + + await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + + await textarea.press('a', {text: 'y'}); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('f'); + }); + it('should send a character with sendCharacter', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + await page.keyboard.sendCharacter('嗨'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨'); + await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); + await page.keyboard.sendCharacter('a'); + expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); + }); + it('should report shiftKey', async({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + const codeForKey = {'Shift': 16, 'Alt': 18, 'Meta': 91, 'Control': 17}; + for (const modifierKey in codeForKey) { + await keyboard.down(modifierKey); + expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']'); + await keyboard.down('!'); + // Shift+! will generate a keypress + if (modifierKey === 'Shift') + expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 33 [' + modifierKey + ']'); + else + expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); + + await keyboard.up('!'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); + await keyboard.up(modifierKey); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []'); + } + }); + it('should report multiple modifiers', async({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + await keyboard.down('Control'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]'); + await keyboard.down('Meta'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: Meta MetaLeft 91 [Control Meta]'); + await keyboard.down(';'); + expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Control Meta]'); + await keyboard.up(';'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Control Meta]'); + await keyboard.up('Control'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Meta]'); + await keyboard.up('Meta'); + expect(await page.evaluate(() => getResult())).toBe('Keyup: Meta MetaLeft 91 []'); + }); + it('should send proper codes while typing', async({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + await page.keyboard.type('!'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: ! Digit1 49 []', + 'Keypress: ! Digit1 33 33 33 []', + 'Keyup: ! Digit1 49 []'].join('\n')); + await page.keyboard.type('^'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: ^ Digit6 54 []', + 'Keypress: ^ Digit6 94 94 94 []', + 'Keyup: ^ Digit6 54 []'].join('\n')); + }); + it('should send proper codes while typing with shift', async({page, server}) => { + await page.goto(server.PREFIX + '/input/keyboard.html'); + const keyboard = page.keyboard; + await keyboard.down('Shift'); + await page.keyboard.type('~'); + expect(await page.evaluate(() => getResult())).toBe( + [ 'Keydown: Shift ShiftLeft 16 [Shift]', + 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode + 'Keypress: ~ Backquote 126 126 126 [Shift]', // 126 is ~ charCode + 'Keyup: ~ Backquote 192 [Shift]'].join('\n')); + await keyboard.up('Shift'); + }); + it('should not type canceled events', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + await page.evaluate(() => { + window.addEventListener('keydown', event => { + event.stopPropagation(); + event.stopImmediatePropagation(); + if (event.key === 'l') + event.preventDefault(); + if (event.key === 'o') + Promise.resolve().then(() => event.preventDefault()); + }, false); + }); + await page.keyboard.type('Hello World!'); + expect(await page.evaluate(() => textarea.value)).toBe('He Wrd!'); + }); + it('keyboard.modifiers()', async({page, server}) => { + const keyboard = page.keyboard; + expect(keyboard._modifiers).toBe(0); + await keyboard.down('Shift'); + expect(keyboard._modifiers).toBe(8); + await keyboard.down('Alt'); + expect(keyboard._modifiers).toBe(9); + await keyboard.up('Shift'); + await keyboard.up('Alt'); + expect(keyboard._modifiers).toBe(0); + }); + it('should resize the textarea', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + const {x, y, width, height} = await page.evaluate(dimensions); + const mouse = page.mouse; + await mouse.move(x + width - 4, y + height - 4); + await mouse.down(); + await mouse.move(x + width + 100, y + height + 100); + await mouse.up(); + const newDimensions = await page.evaluate(dimensions); + expect(newDimensions.width).toBe(width + 104); + expect(newDimensions.height).toBe(height + 104); + }); + it('should scroll and click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-5'); + expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); + await page.click('#button-80'); + expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked'); + }); + it('should double click the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + window.double = false; + const button = document.querySelector('button'); + button.addEventListener('dblclick', event => { + window.double = true; + }); + }); + const button = await page.$('button'); + await button.click({ clickCount: 2 }); + expect(await page.evaluate('double')).toBe(true); + expect(await page.evaluate('result')).toBe('Clicked'); + }); + it('should click a partially obscured button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.evaluate(() => { + const button = document.querySelector('button'); + button.textContent = 'Some really long text that will go offscreen'; + button.style.position = 'absolute'; + button.style.left = '368px'; + }); + await page.click('button'); + expect(await page.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should select the text with mouse', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; + await page.keyboard.type(text); + await page.evaluate(() => document.querySelector('textarea').scrollTop = 0); + const {x, y} = await page.evaluate(dimensions); + await page.mouse.move(x + 2,y + 2); + await page.mouse.down(); + await page.mouse.move(100,100); + await page.mouse.up(); + expect(await page.evaluate(() => window.getSelection().toString())).toBe(text); + }); + it('should select the text by triple clicking', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; + await page.keyboard.type(text); + await page.click('textarea'); + await page.click('textarea', {clickCount: 2}); + await page.click('textarea', {clickCount: 3}); + expect(await page.evaluate(() => window.getSelection().toString())).toBe(text); + }); + it('should trigger hover state', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.hover('#button-6'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); + await page.hover('#button-2'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2'); + await page.hover('#button-91'); + expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); + }); + it('should fire contextmenu event on right click', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.click('#button-8', {button: 'right'}); + expect(await page.evaluate(() => document.querySelector('#button-8').textContent)).toBe('context menu'); + }); + it('should set modifier keys on click', async({page, server}) => { + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true)); + const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'}; + for (const modifier in modifiers) { + await page.keyboard.down(modifier); + await page.click('#button-3'); + if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) + fail(modifiers[modifier] + ' should be true'); + await page.keyboard.up(modifier); + } + await page.click('#button-3'); + for (const modifier in modifiers) { + if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) + fail(modifiers[modifier] + ' should be false'); + } + }); + it('should specify repeat property', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + await page.evaluate(() => document.querySelector('textarea').addEventListener('keydown', e => window.lastEvent = e, true)); + await page.keyboard.down('a'); + expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); + await page.keyboard.press('a'); + expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true); + + await page.keyboard.down('b'); + expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); + await page.keyboard.down('b'); + expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true); + + await page.keyboard.up('a'); + await page.keyboard.down('a'); + expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); + }); + // @see https://github.com/GoogleChrome/puppeteer/issues/206 + it('should click links which cause navigation', async({page, server}) => { + await page.setContent(`empty.html`); + // This await should not hang. + await page.click('a'); + }); + it('should tween mouse movement', async({page, server}) => { + await page.mouse.move(100, 100); + await page.evaluate(() => { + window.result = []; + document.addEventListener('mousemove', event => { + window.result.push([event.clientX, event.clientY]); + }); + }); + await page.mouse.move(200, 300, {steps: 5}); + expect(await page.evaluate('result')).toEqual([ + [120, 140], + [140, 180], + [160, 220], + [180, 260], + [200, 300] + ]); + }); + it('should tap the button', async({page, server}) => { + await page.goto(server.PREFIX + '/input/button.html'); + await page.tap('button'); + expect(await page.evaluate(() => result)).toBe('Clicked'); + }); + xit('should report touches', async({page, server}) => { + await page.goto(server.PREFIX + '/input/touches.html'); + const button = await page.$('button'); + await button.tap(); + expect(await page.evaluate(() => getResult())).toEqual(['Touchstart: 0', 'Touchend: 0']); + }); + it('should click the button inside an iframe', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should click the button with deviceScaleFactor set', async({page, server}) => { + await page.setViewport({width: 400, height: 400, deviceScaleFactor: 5}); + expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); + await page.setContent('
spacer
'); + await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + const frame = page.frames()[1]; + const button = await frame.$('button'); + await button.click(); + expect(await frame.evaluate(() => window.result)).toBe('Clicked'); + }); + it('should type all kinds of characters', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.focus('textarea'); + const text = 'This text goes onto two lines.\nThis character is 嗨.'; + await page.keyboard.type(text); + expect(await page.evaluate('result')).toBe(text); + }); + it('should specify location', async({page, server}) => { + await page.goto(server.PREFIX + '/input/textarea.html'); + await page.evaluate(() => { + window.addEventListener('keydown', event => window.keyLocation = event.location, true); + }); + const textarea = await page.$('textarea'); + + await textarea.press('Digit5'); + expect(await page.evaluate('keyLocation')).toBe(0); + + await textarea.press('ControlLeft'); + expect(await page.evaluate('keyLocation')).toBe(1); + + await textarea.press('ControlRight'); + expect(await page.evaluate('keyLocation')).toBe(2); + + await textarea.press('NumpadSubtract'); + expect(await page.evaluate('keyLocation')).toBe(3); + }); + it('should throw on unknown keys', async({page, server}) => { + let error = await page.keyboard.press('NotARealKey').catch(e => e); + expect(error.message).toBe('Unknown key: "NotARealKey"'); + + error = await page.keyboard.press('ё').catch(e => e); + expect(error && error.message).toBe('Unknown key: "ё"'); + + error = await page.keyboard.press('😊').catch(e => e); + expect(error && error.message).toBe('Unknown key: "😊"'); + }); + function dimensions() { + const rect = document.querySelector('textarea').getBoundingClientRect(); + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height + }; + } + }); +}; \ No newline at end of file diff --git a/test/jshandle.spec.js b/test/jshandle.spec.js new file mode 100644 index 00000000000..7ba74cfed78 --- /dev/null +++ b/test/jshandle.spec.js @@ -0,0 +1,116 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + 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); + }); + }); + + 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 not work with dates', async({page, server}) => { + const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z')); + const json = await dateHandle.jsonValue(); + expect(json).toEqual({}); + }); + 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('Object reference chain is too long'); + }); + }); + + 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 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)); + }); + }); + + 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'); + }); + }); +}; \ No newline at end of file diff --git a/test/network.spec.js b/test/network.spec.js new file mode 100644 index 00000000000..ad48ff6f741 --- /dev/null +++ b/test/network.spec.js @@ -0,0 +1,188 @@ +/** + * Copyright 2018 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {waitForEvents} = require('./utils'); + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + 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(requests[0].response()).toBeTruthy(); + expect(requests[0].frame() === page.mainFrame()).toBe(true); + expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); + }); + it('Page.Events.Request should report 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.postData()).toBe('{"foo":"bar"}'); + }); + 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].fromCache()).toBe(false); + expect(responses[0].fromServiceWorker()).toBe(false); + expect(responses[0].request()).toBeTruthy(); + }); + + it('Response.fromCache()', async({page, server}) => { + const responses = new Map(); + page.on('response', r => responses.set(r.url().split('/').pop(), r)); + + // Load and re-load to make sure it's cached. + await page.goto(server.PREFIX + '/cached/one-style.html'); + await page.reload(); + + expect(responses.size).toBe(2); + expect(responses.get('one-style.html').status()).toBe(304); + expect(responses.get('one-style.html').fromCache()).toBe(false); + expect(responses.get('one-style.css').status()).toBe(200); + expect(responses.get('one-style.css').fromCache()).toBe(true); + }); + it('Response.fromServiceWorker', async({page, server}) => { + const responses = new Map(); + page.on('response', r => responses.set(r.url().split('/').pop(), r)); + + // Load and re-load to make sure serviceworker is installed and running. + await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {waitUntil: 'networkidle2'}); + await page.evaluate(async() => await window.activationPromise); + await page.reload(); + + expect(responses.size).toBe(2); + expect(responses.get('sw.html').status()).toBe(200); + expect(responses.get('sw.html').fromServiceWorker()).toBe(true); + expect(responses.get('style.css').status()).toBe(200); + expect(responses.get('style.css').fromServiceWorker()).toBe(true); + }); + + it('Page.Events.Response should provide body', async({page, server}) => { + let response = null; + page.on('response', r => response = r); + await page.goto(server.PREFIX + '/simple.json'); + expect(response).toBeTruthy(); + expect(await response.text()).toBe('{"foo": "bar"}\n'); + expect(await response.json()).toEqual({foo: 'bar'}); + }); + it('Page.Events.Response should not report body unless request is finished', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + // Setup server to trap request. + let serverResponse = null; + server.setRoute('/get', (req, res) => { + serverResponse = res; + res.write('hello '); + }); + // Setup page to trap response. + let pageResponse = null; + let requestFinished = false; + page.on('response', r => pageResponse = r); + page.on('requestfinished', () => requestFinished = true); + // send request and wait for server response + await Promise.all([ + page.evaluate(() => fetch('./get', { method: 'GET'})), + waitForEvents(page, 'response') + ]); + + 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('Page.Events.RequestFailed', async({page, server}) => { + await page.setRequestInterception(true); + page.on('request', request => { + if (request.url().endsWith('css')) + request.abort(); + else + request.continue(); + }); + 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(failedRequests[0].response()).toBe(null); + expect(failedRequests[0].resourceType()).toBe('stylesheet'); + expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED'); + expect(failedRequests[0].frame()).toBeTruthy(); + }); + it('Page.Events.RequestFinished', async({page, server}) => { + const requests = []; + page.on('requestfinished', 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].response()).toBeTruthy(); + expect(requests[0].frame() === page.mainFrame()).toBe(true); + expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); + }); + 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')); + page.on('requestfinished', request => events.push('requestfinished')); + await page.goto(server.EMPTY_PAGE); + 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); + expect(events).toEqual([ + `GET ${FOO_URL}`, + `302 ${FOO_URL}`, + `DONE ${FOO_URL}`, + `GET ${server.EMPTY_PAGE}`, + `200 ${server.EMPTY_PAGE}`, + `DONE ${server.EMPTY_PAGE}` + ]); + + // Check redirect chain + const redirectChain = response.request().redirectChain(); + expect(redirectChain.length).toBe(1); + expect(redirectChain[0].url()).toContain('/foo.html'); + }); + }); +}; diff --git a/test/page.spec.js b/test/page.spec.js index fe71e765187..7b2c345707e 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -15,13 +15,13 @@ */ const fs = require('fs'); const path = require('path'); -const FrameUtils = require('./frame-utils'); - -module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppeteer, PROJECT_ROOT) { - const {describe, xdescribe, fdescribe} = runner; - const {it, fit, xit} = runner; - const {beforeAll, beforeEach, afterAll, afterEach} = runner; +const utils = require('./utils'); +const {waitForEvents, getPDFPages, cssPixelsToInches} = require('./utils'); +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer, PROJECT_ROOT}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const DeviceDescriptors = require(path.join(PROJECT_ROOT, 'DeviceDescriptors')); const iPhone = DeviceDescriptors['iPhone 6']; const iPhoneLandscape = DeviceDescriptors['iPhone 6 landscape']; @@ -46,58 +46,21 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet state.page = null; }); - describe('Browser.version', function() { - it('should return whether we are in headless', async({browser}) => { - const version = await browser.version(); - expect(version.length).toBeGreaterThan(0); - expect(version.startsWith('Headless')).toBe(headless); - }); - }); + const testFiles = [ + 'browser.spec.js', + 'elementhandle.spec.js', + 'jshandle.spec.js', + 'tracing.spec.js', + 'frame.spec.js', + 'input.spec.js', + 'network.spec.js' + ]; - describe('Browser.userAgent', function() { - it('should include WebKit', async({browser}) => { - const userAgent = await browser.userAgent(); - expect(userAgent.length).toBeGreaterThan(0); - expect(userAgent).toContain('WebKit'); - }); - }); - - describe('Browser.process', function() { - it('should return child_process instance', async function({browser}) { - const process = await browser.process(); - expect(process.pid).toBeGreaterThan(0); - const browserWSEndpoint = browser.wsEndpoint(); - const remoteBrowser = await puppeteer.connect({browserWSEndpoint}); - expect(remoteBrowser.process()).toBe(null); - await remoteBrowser.disconnect(); - }); - }); - - describe('Browser.Events.disconnected', function() { - it('should emitted when: browser gets closed, disconnected or underlying websocket gets closed', async() => { - const originalBrowser = await puppeteer.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.wsEndpoint(); - const remoteBrowser1 = await puppeteer.connect({browserWSEndpoint}); - const remoteBrowser2 = await puppeteer.connect({browserWSEndpoint}); - - let disconnectedOriginal = 0; - let disconnectedRemote1 = 0; - let disconnectedRemote2 = 0; - originalBrowser.on('disconnected', () => ++disconnectedOriginal); - remoteBrowser1.on('disconnected', () => ++disconnectedRemote1); - remoteBrowser2.on('disconnected', () => ++disconnectedRemote2); - - await remoteBrowser2.disconnect(); - expect(disconnectedOriginal).toBe(0); - expect(disconnectedRemote1).toBe(0); - expect(disconnectedRemote2).toBe(1); - - await originalBrowser.close(); - expect(disconnectedOriginal).toBe(1); - expect(disconnectedRemote1).toBe(1); - expect(disconnectedRemote2).toBe(1); - }); - }); + testFiles + .map(file => path.join(__dirname, file)) + .forEach(file => + require(file).addTests({testRunner, expect, defaultBrowserOptions, PROJECT_ROOT, puppeteer}) + ); describe('Page.close', function() { it('should reject all promises when page is closed', async({browser}) => { @@ -239,7 +202,7 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet expect(error.message).toContain('JSHandle is disposed'); }); it('should throw if elementHandles are from other frames', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const bodyHandle = await page.frames()[1].$('body'); let error = null; await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => error = e); @@ -310,449 +273,6 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); - 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); - }); - }); - - 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 not work with dates', async({page, server}) => { - const dateHandle = await page.evaluateHandle(() => new Date('2017-09-26T00:00:00.000Z')); - const json = await dateHandle.jsonValue(); - expect(json).toEqual({}); - }); - 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('Object reference chain is too long'); - }); - }); - - 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 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)); - }); - }); - - 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'); - }); - }); - - describe('Frame.context', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - expect(page.frames().length).toBe(2); - const [frame1, frame2] = page.frames(); - const context1 = await frame1.executionContext(); - const context2 = await frame2.executionContext(); - expect(context1).toBeTruthy(); - expect(context2).toBeTruthy(); - expect(context1 !== context2).toBeTruthy(); - expect(context1.frame()).toBe(frame1); - expect(context2.frame()).toBe(frame2); - - await Promise.all([ - context1.evaluate(() => window.a = 1), - context2.evaluate(() => window.a = 2) - ]); - const [a1, a2] = await Promise.all([ - context1.evaluate(() => window.a), - context2.evaluate(() => window.a) - ]); - expect(a1).toBe(1); - expect(a2).toBe(2); - }); - }); - - describe('Frame.evaluateHandle', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const mainFrame = page.mainFrame(); - const windowHandle = await mainFrame.evaluateHandle(() => window); - expect(windowHandle).toBeTruthy(); - }); - }); - - describe('Frame.evaluate', function() { - it('should have different execution contexts', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - expect(page.frames().length).toBe(2); - const frame1 = page.frames()[0]; - const frame2 = page.frames()[1]; - await frame1.evaluate(() => window.FOO = 'foo'); - await frame2.evaluate(() => window.FOO = 'bar'); - expect(await frame1.evaluate(() => window.FOO)).toBe('foo'); - expect(await frame2.evaluate(() => window.FOO)).toBe('bar'); - }); - it('should execute after cross-site navigation', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const mainFrame = page.mainFrame(); - expect(await mainFrame.evaluate(() => window.location.href)).toContain('localhost'); - await page.goto(server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(await mainFrame.evaluate(() => window.location.href)).toContain('127'); - }); - }); - - 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 poll on interval', async({page, server}) => { - let success = false; - const startTime = Date.now(); - const polling = 100; - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling}) - .then(() => success = true); - await page.evaluate(() => window.__FOO = 'hit'); - expect(success).toBe(false); - await page.evaluate(() => document.body.appendChild(document.createElement('div'))); - await watchdog; - expect(Date.now() - startTime).not.toBeLessThan(polling / 2); - }); - it('should poll on mutation', async({page, server}) => { - let success = false; - const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {polling: 'mutation'}) - .then(() => success = true); - await page.evaluate(() => window.__FOO = 'hit'); - expect(success).toBe(false); - await page.evaluate(() => document.body.appendChild(document.createElement('div'))); - await watchdog; - }); - 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 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; - }); - }); - - describe('Frame.waitForSelector', function() { - const addElement = tag => document.body.appendChild(document.createElement(tag)); - - 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'); - }); - - 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'); - 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 work when node is added through innerHTML', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - const watchdog = page.waitForSelector('h3 div'); - await page.evaluate(addElement, 'span'); - await page.evaluate(() => document.querySelector('span').innerHTML = '

'); - await watchdog; - }); - - it('Page.waitForSelector is shortcut for main frame', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const otherFrame = page.frames()[1]; - const watchdog = page.waitForSelector('div'); - await otherFrame.evaluate(addElement, 'div'); - await page.evaluate(addElement, 'div'); - const eHandle = await watchdog; - expect(eHandle.executionContext().frame()).toBe(page.mainFrame()); - }); - - it('should run in specified frame', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame2', server.EMPTY_PAGE); - const frame1 = page.frames()[1]; - const frame2 = page.frames()[2]; - const waitForSelectorPromise = frame2.waitForSelector('div'); - await frame1.evaluate(addElement, 'div'); - await frame2.evaluate(addElement, 'div'); - const eHandle = await waitForSelectorPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); - }); - - it('should throw if evaluation failed', async({page, server}) => { - await page.evaluateOnNewDocument(function() { - document.querySelector = null; - }); - await page.goto(server.EMPTY_PAGE); - let error = null; - await page.waitForSelector('*').catch(e => error = e); - expect(error.message).toContain('document.querySelector is not a function'); - }); - it('should throw when frame is detached', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const frame = page.frames()[1]; - let waitError = null; - const waitPromise = frame.waitForSelector('.box').catch(e => waitError = e); - await FrameUtils.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', {visible: true}).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 wait for visible recursively', async({page, server}) => { - let divVisible = false; - const waitForSelector = page.waitForSelector('div#inner', {visible: true}).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 visibility: hidden', async({page, server}) => { - let divHidden = false; - await page.setContent(`
`); - const waitForSelector = page.waitForSelector('div', {hidden: true}).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(`
`); - const waitForSelector = page.waitForSelector('div', {hidden: true}).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(`
`); - let divRemoved = false; - const waitForSelector = page.waitForSelector('div', {hidden: true}).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 respect timeout', async({page, server}) => { - let error = null; - await page.waitForSelector('div', {timeout: 10}).catch(e => error = e); - expect(error).toBeTruthy(); - expect(error.message).toContain('waiting failed: timeout'); - }); - - it('should respond to node attribute mutation', async({page, server}) => { - let divFound = false; - const waitForSelector = page.waitForSelector('.zombo').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'); - }); - }); - - describe('Frame.waitForXPath', 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.waitForXPath('//p[normalize-space(.)="hello world"]'); - expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('hello world '); - }); - it('should run in specified frame', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame2', server.EMPTY_PAGE); - const frame1 = page.frames()[1]; - const frame2 = page.frames()[2]; - const waitForXPathPromise = frame2.waitForXPath('//div'); - await frame1.evaluate(addElement, 'div'); - await frame2.evaluate(addElement, 'div'); - const eHandle = await waitForXPathPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); - }); - it('should throw if evaluation failed', async({page, server}) => { - await page.evaluateOnNewDocument(function() { - document.evaluate = null; - }); - await page.goto(server.EMPTY_PAGE); - let error = null; - await page.waitForXPath('*').catch(e => error = e); - expect(error.message).toContain('document.evaluate is not a function'); - }); - it('should throw when frame is detached', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const frame = page.frames()[1]; - let waitError = null; - const waitPromise = frame.waitForXPath('//*[@class="box"]').catch(e => waitError = e); - await FrameUtils.detachFrame(page, 'frame1'); - await waitPromise; - expect(waitError).toBeTruthy(); - expect(waitError.message).toContain('waitForFunction failed: frame got detached.'); - }); - it('hidden should wait for display: none', async({page, server}) => { - let divHidden = false; - await page.setContent(`
`); - const waitForXPath = page.waitForXPath('//div', {hidden: true}).then(() => divHidden = true); - await page.waitForXPath('//div'); // do a round trip - expect(divHidden).toBe(false); - await page.evaluate(() => document.querySelector('div').style.setProperty('display', 'none')); - expect(await waitForXPath).toBe(true); - expect(divHidden).toBe(true); - }); - it('should return the element handle', async({page, server}) => { - const waitForXPath = page.waitForXPath('//*[@class="zombo"]'); - await page.setContent(`
anything
`); - expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('anything'); - }); - it('should allow you to select a text node', async({page, server}) => { - await page.setContent(`
some text
`); - const text = await page.waitForXPath('//div/text()'); - expect(await (await text.getProperty('nodeType')).jsonValue()).toBe(3 /* Node.TEXT_NODE */); - }); - it('should allow you to select an element with single slash', async({page, server}) => { - await page.setContent(`
some text
`); - const waitForXPath = page.waitForXPath('/html/body/div'); - expect(await page.evaluate(x => x.textContent, await waitForXPath)).toBe('some text'); - }); - }); - describe('Page.waitFor', function() { it('should wait for selector', async({page, server}) => { let found = false; @@ -1555,7 +1075,7 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet const requests = []; page.on('request', request => requests.push(request)); await page.goto(server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); expect(requests.length).toBe(2); expect(requests[0].url()).toBe(server.EMPTY_PAGE); expect(requests[0].frame() === page.mainFrame()).toBe(true); @@ -1569,28 +1089,28 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet describe('Frame Management', function() { it('should handle nested frames', async({page, server}) => { await page.goto(server.PREFIX + '/frames/nested-frames.html'); - expect(FrameUtils.dumpFrames(page.mainFrame())).toBeGolden('nested-frames.txt'); + expect(utils.dumpFrames(page.mainFrame())).toBeGolden('nested-frames.txt'); }); it('should send events when frames are manipulated dynamically', async({page, server}) => { await page.goto(server.EMPTY_PAGE); // validate frameattached events const attachedFrames = []; page.on('frameattached', frame => attachedFrames.push(frame)); - await FrameUtils.attachFrame(page, 'frame1', './assets/frame.html'); + await utils.attachFrame(page, 'frame1', './assets/frame.html'); expect(attachedFrames.length).toBe(1); expect(attachedFrames[0].url()).toContain('/assets/frame.html'); // validate framenavigated events const navigatedFrames = []; page.on('framenavigated', frame => navigatedFrames.push(frame)); - await FrameUtils.navigateFrame(page, 'frame1', './empty.html'); + await utils.navigateFrame(page, 'frame1', './empty.html'); expect(navigatedFrames.length).toBe(1); expect(navigatedFrames[0].url()).toBe(server.EMPTY_PAGE); // validate framedetached events const detachedFrames = []; page.on('framedetached', frame => detachedFrames.push(frame)); - await FrameUtils.detachFrame(page, 'frame1'); + await utils.detachFrame(page, 'frame1'); expect(detachedFrames.length).toBe(1); expect(detachedFrames[0].isDetached()).toBe(true); }); @@ -1628,7 +1148,7 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet expect(navigatedFrames.length).toBe(1); }); it('should report frame.name()', async({page, server}) => { - await FrameUtils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); + await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); await page.evaluate(url => { const frame = document.createElement('iframe'); frame.name = 'theFrameName'; @@ -1641,8 +1161,8 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet expect(page.frames()[2].name()).toBe('theFrameName'); }); it('should report frame.parent()', async({page, server}) => { - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame2', server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); expect(page.frames()[0].parentFrame()).toBe(null); expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); @@ -1726,674 +1246,6 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); - describe('ElementHandle.boundingBox', function() { - it('should work', async({page, server}) => { - await page.setViewport({width: 500, height: 500}); - await page.goto(server.PREFIX + '/grid.html'); - const elementHandle = await page.$('.box:nth-of-type(13)'); - const box = await elementHandle.boundingBox(); - expect(box).toEqual({ x: 100, y: 50, width: 50, height: 50 }); - }); - it('should handle nested frames', async({page, server}) => { - await page.setViewport({width: 500, height: 500}); - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - const nestedFrame = page.frames()[1].childFrames()[1]; - const elementHandle = await nestedFrame.$('div'); - const box = await elementHandle.boundingBox(); - expect(box).toEqual({ x: 28, y: 260, width: 264, height: 18 }); - }); - it('should return null for invisible elements', async({page, server}) => { - await page.setContent('
hi
'); - const element = await page.$('div'); - expect(await element.boundingBox()).toBe(null); - }); - it('should force a layout', async({page, server}) => { - await page.setViewport({ width: 500, height: 500 }); - await page.setContent('
hello
'); - const elementHandle = await page.$('div'); - await page.evaluate(element => element.style.height = '200px', elementHandle); - const box = await elementHandle.boundingBox(); - expect(box).toEqual({ x: 8, y: 8, width: 100, height: 200 }); - }); - }); - - describe('ElementHandle.contentFrame', function() { - it('should work', async({page,server}) => { - await page.goto(server.EMPTY_PAGE); - await FrameUtils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - const elementHandle = await page.$('#frame1'); - const frame = await elementHandle.contentFrame(); - expect(frame).toBe(page.frames()[1]); - }); - }); - - describe('ElementHandle.click', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await button.click(); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should work for Shadow DOM v1', async({page, server}) => { - await page.goto(server.PREFIX + '/shadow.html'); - const buttonHandle = await page.evaluateHandle(() => button); - await buttonHandle.click(); - expect(await page.evaluate(() => clicked)).toBe(true); - }); - it('should work for TextNodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const buttonTextNode = await page.evaluateHandle(() => document.querySelector('button').firstChild); - let error = null; - await buttonTextNode.click().catch(err => error = err); - expect(error.message).toBe('Node is not of type HTMLElement'); - }); - it('should throw for detached nodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.remove(), button); - let error = null; - await button.click().catch(err => error = err); - expect(error.message).toBe('Node is detached from document'); - }); - it('should throw for hidden nodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.style.display = 'none', button); - const error = await button.click().catch(err => err); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); - }); - it('should throw for recursively hidden nodes', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - const button = await page.$('button'); - await page.evaluate(button => button.parentElement.style.display = 'none', button); - const error = await button.click().catch(err => err); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); - }); - it('should throw for
elements', async({page, server}) => { - await page.setContent('hello
goodbye'); - const br = await page.$('br'); - const error = await br.click().catch(err => err); - expect(error.message).toBe('Node is either not visible or not an HTMLElement'); - }); - }); - - describe('ElementHandle.hover', function() { - it('should work', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - const button = await page.$('#button-6'); - await button.hover(); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); - }); - }); - - describe('ElementHandle.screenshot', function() { - it('should work', async({page, server}) => { - await page.setViewport({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, server}) => { - await page.setViewport({width: 500, height: 500}); - await page.setContent(` - something above - -
- `); - const elementHandle = await page.$('div'); - const screenshot = await elementHandle.screenshot(); - expect(screenshot).toBeGolden('screenshot-element-padding-border.png'); - }); - it('should capture full element when larger than viewport', async({page, server}) => { - await page.setViewport({width: 500, height: 500}); - - await page.setContent(` - something above - -
- `); - const elementHandle = await page.$('div.to-screenshot'); - const screenshot = await elementHandle.screenshot(); - expect(screenshot).toBeGolden('screenshot-element-larger-than-viewport.png'); - - expect(await page.evaluate(() => ({ w: window.innerWidth, h: window.innerHeight }))).toEqual({ w: 500, h: 500 }); - }); - it('should scroll element into view', async({page, server}) => { - await page.setViewport({width: 500, height: 500}); - await page.setContent(` - something above - -
-
- `); - 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, server}) => { - await page.setViewport({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).toBe('Node is either not visible or not an HTMLElement'); - }); - }); - - describe('ElementHandle.$', function() { - it('should query existing element', async({page, server}) => { - await page.goto(server.PREFIX + '/playground.html'); - await page.setContent('
A
'); - const html = await page.$('html'); - const second = await html.$('.second'); - const inner = await second.$('.inner'); - const content = await page.evaluate(e => e.textContent, inner); - expect(content).toBe('A'); - }); - - it('should return null for non-existing element', async({page, server}) => { - await page.setContent('
B
'); - const html = await page.$('html'); - const second = await html.$('.third'); - expect(second).toBe(null); - }); - }); - - describe('ElementHandle.$$', function() { - it('should query existing elements', async({page, server}) => { - await page.setContent('
A

B
'); - const html = await page.$('html'); - const elements = await html.$$('div'); - expect(elements.length).toBe(2); - const promises = elements.map(element => page.evaluate(e => e.textContent, element)); - expect(await Promise.all(promises)).toEqual(['A', 'B']); - }); - - it('should return empty array for non-existing elements', async({page, server}) => { - await page.setContent('A
B'); - const html = await page.$('html'); - const elements = await html.$$('div'); - expect(elements.length).toBe(0); - }); - }); - - - describe('ElementHandle.$x', function() { - it('should query existing element', async({page, server}) => { - await page.goto(server.PREFIX + '/playground.html'); - await page.setContent('
A
'); - const html = await page.$('html'); - const second = await html.$x(`./body/div[contains(@class, 'second')]`); - const inner = await second[0].$x(`./div[contains(@class, 'inner')]`); - const content = await page.evaluate(e => e.textContent, inner[0]); - expect(content).toBe('A'); - }); - - it('should return null for non-existing element', async({page, server}) => { - await page.setContent('
B
'); - const html = await page.$('html'); - const second = await html.$x(`/div[contains(@class, 'third')]`); - expect(second).toEqual([]); - }); - }); - - describe('input', function() { - it('should click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - - it('should click on checkbox input and toggle', async({page, server}) => { - await page.goto(server.PREFIX + '/input/checkbox.html'); - expect(await page.evaluate(() => result.check)).toBe(null); - await page.click('input#agree'); - expect(await page.evaluate(() => result.check)).toBe(true); - expect(await page.evaluate(() => result.events)).toEqual([ - 'mouseover', - 'mouseenter', - 'mousemove', - 'mousedown', - 'mouseup', - 'click', - 'input', - 'change', - ]); - await page.click('input#agree'); - expect(await page.evaluate(() => result.check)).toBe(false); - }); - - it('should click on checkbox label and toggle', async({page, server}) => { - await page.goto(server.PREFIX + '/input/checkbox.html'); - expect(await page.evaluate(() => result.check)).toBe(null); - await page.click('label[for="agree"]'); - expect(await page.evaluate(() => result.check)).toBe(true); - expect(await page.evaluate(() => result.events)).toEqual([ - 'click', - 'input', - 'change', - ]); - await page.click('label[for="agree"]'); - expect(await page.evaluate(() => result.check)).toBe(false); - }); - - it('should fail to click a missing button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - let error = null; - await page.click('button.does-not-exist').catch(e => error = e); - expect(error.message).toBe('No node found for selector: button.does-not-exist'); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/161 - it('should not hang with touch-enabled viewports', async({page, server}) => { - await page.setViewport(iPhone.viewport); - await page.mouse.down(); - await page.mouse.move(100, 10); - await page.mouse.up(); - }); - it('should type into the textarea', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - - const textarea = await page.$('textarea'); - await textarea.type('Type in this text!'); - expect(await page.evaluate(() => result)).toBe('Type in this text!'); - }); - it('should click the button after navigation ', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - await page.goto(server.PREFIX + '/input/button.html'); - await page.click('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - it('should upload the file', async({page, server}) => { - await page.goto(server.PREFIX + '/input/fileupload.html'); - const filePath = path.relative(process.cwd(), __dirname + '/assets/file-to-upload.txt'); - const input = await page.$('input'); - await input.uploadFile(filePath); - expect(await page.evaluate(e => e.files[0].name, input)).toBe('file-to-upload.txt'); - expect(await page.evaluate(e => { - const reader = new FileReader(); - const promise = new Promise(fulfill => reader.onload = fulfill); - reader.readAsText(e.files[0]); - return promise.then(() => reader.result); - }, input)).toBe('contents of the file'); - }); - it('should move with the arrow keys', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.type('textarea', 'Hello World!'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); - for (let i = 0; i < 'World!'.length; i++) - page.keyboard.press('ArrowLeft'); - await page.keyboard.type('inserted '); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello inserted World!'); - page.keyboard.down('Shift'); - for (let i = 0; i < 'inserted '.length; i++) - page.keyboard.press('ArrowLeft'); - page.keyboard.up('Shift'); - await page.keyboard.press('Backspace'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('Hello World!'); - }); - it('should send a character with ElementHandle.press', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const textarea = await page.$('textarea'); - await textarea.press('a', {text: 'f'}); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('f'); - - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); - - await textarea.press('a', {text: 'y'}); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('f'); - }); - it('should send a character with sendCharacter', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - await page.keyboard.sendCharacter('嗨'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨'); - await page.evaluate(() => window.addEventListener('keydown', e => e.preventDefault(), true)); - await page.keyboard.sendCharacter('a'); - expect(await page.evaluate(() => document.querySelector('textarea').value)).toBe('嗨a'); - }); - it('should report shiftKey', async({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - const codeForKey = {'Shift': 16, 'Alt': 18, 'Meta': 91, 'Control': 17}; - for (const modifierKey in codeForKey) { - await keyboard.down(modifierKey); - expect(await page.evaluate(() => getResult())).toBe('Keydown: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' [' + modifierKey + ']'); - await keyboard.down('!'); - // Shift+! will generate a keypress - if (modifierKey === 'Shift') - expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']\nKeypress: ! Digit1 33 33 33 [' + modifierKey + ']'); - else - expect(await page.evaluate(() => getResult())).toBe('Keydown: ! Digit1 49 [' + modifierKey + ']'); - - await keyboard.up('!'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ! Digit1 49 [' + modifierKey + ']'); - await keyboard.up(modifierKey); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ' + modifierKey + ' ' + modifierKey + 'Left ' + codeForKey[modifierKey] + ' []'); - } - }); - it('should report multiple modifiers', async({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - await keyboard.down('Control'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: Control ControlLeft 17 [Control]'); - await keyboard.down('Meta'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: Meta MetaLeft 91 [Control Meta]'); - await keyboard.down(';'); - expect(await page.evaluate(() => getResult())).toBe('Keydown: ; Semicolon 186 [Control Meta]'); - await keyboard.up(';'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: ; Semicolon 186 [Control Meta]'); - await keyboard.up('Control'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: Control ControlLeft 17 [Meta]'); - await keyboard.up('Meta'); - expect(await page.evaluate(() => getResult())).toBe('Keyup: Meta MetaLeft 91 []'); - }); - it('should send proper codes while typing', async({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - await page.keyboard.type('!'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: ! Digit1 49 []', - 'Keypress: ! Digit1 33 33 33 []', - 'Keyup: ! Digit1 49 []'].join('\n')); - await page.keyboard.type('^'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: ^ Digit6 54 []', - 'Keypress: ^ Digit6 94 94 94 []', - 'Keyup: ^ Digit6 54 []'].join('\n')); - }); - it('should send proper codes while typing with shift', async({page, server}) => { - await page.goto(server.PREFIX + '/input/keyboard.html'); - const keyboard = page.keyboard; - await keyboard.down('Shift'); - await page.keyboard.type('~'); - expect(await page.evaluate(() => getResult())).toBe( - [ 'Keydown: Shift ShiftLeft 16 [Shift]', - 'Keydown: ~ Backquote 192 [Shift]', // 192 is ` keyCode - 'Keypress: ~ Backquote 126 126 126 [Shift]', // 126 is ~ charCode - 'Keyup: ~ Backquote 192 [Shift]'].join('\n')); - await keyboard.up('Shift'); - }); - it('should not type canceled events', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - await page.evaluate(() => { - window.addEventListener('keydown', event => { - event.stopPropagation(); - event.stopImmediatePropagation(); - if (event.key === 'l') - event.preventDefault(); - if (event.key === 'o') - Promise.resolve().then(() => event.preventDefault()); - }, false); - }); - await page.keyboard.type('Hello World!'); - expect(await page.evaluate(() => textarea.value)).toBe('He Wrd!'); - }); - it('keyboard.modifiers()', async({page, server}) => { - const keyboard = page.keyboard; - expect(keyboard._modifiers).toBe(0); - await keyboard.down('Shift'); - expect(keyboard._modifiers).toBe(8); - await keyboard.down('Alt'); - expect(keyboard._modifiers).toBe(9); - await keyboard.up('Shift'); - await keyboard.up('Alt'); - expect(keyboard._modifiers).toBe(0); - }); - it('should resize the textarea', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - const {x, y, width, height} = await page.evaluate(dimensions); - const mouse = page.mouse; - await mouse.move(x + width - 4, y + height - 4); - await mouse.down(); - await mouse.move(x + width + 100, y + height + 100); - await mouse.up(); - const newDimensions = await page.evaluate(dimensions); - expect(newDimensions.width).toBe(width + 104); - expect(newDimensions.height).toBe(height + 104); - }); - it('should scroll and click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.click('#button-5'); - expect(await page.evaluate(() => document.querySelector('#button-5').textContent)).toBe('clicked'); - await page.click('#button-80'); - expect(await page.evaluate(() => document.querySelector('#button-80').textContent)).toBe('clicked'); - }); - it('should double click the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => { - window.double = false; - const button = document.querySelector('button'); - button.addEventListener('dblclick', event => { - window.double = true; - }); - }); - const button = await page.$('button'); - await button.click({ clickCount: 2 }); - expect(await page.evaluate('double')).toBe(true); - expect(await page.evaluate('result')).toBe('Clicked'); - }); - it('should click a partially obscured button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.evaluate(() => { - const button = document.querySelector('button'); - button.textContent = 'Some really long text that will go offscreen'; - button.style.position = 'absolute'; - button.style.left = '368px'; - }); - await page.click('button'); - expect(await page.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should select the text with mouse', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; - await page.keyboard.type(text); - await page.evaluate(() => document.querySelector('textarea').scrollTop = 0); - const {x, y} = await page.evaluate(dimensions); - await page.mouse.move(x + 2,y + 2); - await page.mouse.down(); - await page.mouse.move(100,100); - await page.mouse.up(); - expect(await page.evaluate(() => window.getSelection().toString())).toBe(text); - }); - it('should select the text by triple clicking', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const text = 'This is the text that we are going to try to select. Let\'s see how it goes.'; - await page.keyboard.type(text); - await page.click('textarea'); - await page.click('textarea', {clickCount: 2}); - await page.click('textarea', {clickCount: 3}); - expect(await page.evaluate(() => window.getSelection().toString())).toBe(text); - }); - it('should trigger hover state', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.hover('#button-6'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-6'); - await page.hover('#button-2'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-2'); - await page.hover('#button-91'); - expect(await page.evaluate(() => document.querySelector('button:hover').id)).toBe('button-91'); - }); - it('should fire contextmenu event on right click', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.click('#button-8', {button: 'right'}); - expect(await page.evaluate(() => document.querySelector('#button-8').textContent)).toBe('context menu'); - }); - it('should set modifier keys on click', async({page, server}) => { - await page.goto(server.PREFIX + '/input/scrollable.html'); - await page.evaluate(() => document.querySelector('#button-3').addEventListener('mousedown', e => window.lastEvent = e, true)); - const modifiers = {'Shift': 'shiftKey', 'Control': 'ctrlKey', 'Alt': 'altKey', 'Meta': 'metaKey'}; - for (const modifier in modifiers) { - await page.keyboard.down(modifier); - await page.click('#button-3'); - if (!(await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) - fail(modifiers[modifier] + ' should be true'); - await page.keyboard.up(modifier); - } - await page.click('#button-3'); - for (const modifier in modifiers) { - if ((await page.evaluate(mod => window.lastEvent[mod], modifiers[modifier]))) - fail(modifiers[modifier] + ' should be false'); - } - }); - it('should specify repeat property', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - await page.evaluate(() => document.querySelector('textarea').addEventListener('keydown', e => window.lastEvent = e, true)); - await page.keyboard.down('a'); - expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); - await page.keyboard.press('a'); - expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true); - - await page.keyboard.down('b'); - expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); - await page.keyboard.down('b'); - expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(true); - - await page.keyboard.up('a'); - await page.keyboard.down('a'); - expect(await page.evaluate(() => window.lastEvent.repeat)).toBe(false); - }); - // @see https://github.com/GoogleChrome/puppeteer/issues/206 - it('should click links which cause navigation', async({page, server}) => { - await page.setContent(`empty.html`); - // This await should not hang. - await page.click('a'); - }); - it('should tween mouse movement', async({page, server}) => { - await page.mouse.move(100, 100); - await page.evaluate(() => { - window.result = []; - document.addEventListener('mousemove', event => { - window.result.push([event.clientX, event.clientY]); - }); - }); - await page.mouse.move(200, 300, {steps: 5}); - expect(await page.evaluate('result')).toEqual([ - [120, 140], - [140, 180], - [160, 220], - [180, 260], - [200, 300] - ]); - }); - it('should tap the button', async({page, server}) => { - await page.goto(server.PREFIX + '/input/button.html'); - await page.tap('button'); - expect(await page.evaluate(() => result)).toBe('Clicked'); - }); - xit('should report touches', async({page, server}) => { - await page.goto(server.PREFIX + '/input/touches.html'); - const button = await page.$('button'); - await button.tap(); - expect(await page.evaluate(() => getResult())).toEqual(['Touchstart: 0', 'Touchend: 0']); - }); - it('should click the button inside an iframe', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await page.setContent('
spacer
'); - await FrameUtils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); - const frame = page.frames()[1]; - const button = await frame.$('button'); - await button.click(); - expect(await frame.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should click the button with deviceScaleFactor set', async({page, server}) => { - await page.setViewport({width: 400, height: 400, deviceScaleFactor: 5}); - expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); - await page.setContent('
spacer
'); - await FrameUtils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); - const frame = page.frames()[1]; - const button = await frame.$('button'); - await button.click(); - expect(await frame.evaluate(() => window.result)).toBe('Clicked'); - }); - it('should type all kinds of characters', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.focus('textarea'); - const text = 'This text goes onto two lines.\nThis character is 嗨.'; - await page.keyboard.type(text); - expect(await page.evaluate('result')).toBe(text); - }); - it('should specify location', async({page, server}) => { - await page.goto(server.PREFIX + '/input/textarea.html'); - await page.evaluate(() => { - window.addEventListener('keydown', event => window.keyLocation = event.location, true); - }); - const textarea = await page.$('textarea'); - - await textarea.press('Digit5'); - expect(await page.evaluate('keyLocation')).toBe(0); - - await textarea.press('ControlLeft'); - expect(await page.evaluate('keyLocation')).toBe(1); - - await textarea.press('ControlRight'); - expect(await page.evaluate('keyLocation')).toBe(2); - - await textarea.press('NumpadSubtract'); - expect(await page.evaluate('keyLocation')).toBe(3); - }); - it('should throw on unknown keys', async({page, server}) => { - let error = await page.keyboard.press('NotARealKey').catch(e => e); - expect(error.message).toBe('Unknown key: "NotARealKey"'); - - error = await page.keyboard.press('ё').catch(e => e); - expect(error && error.message).toBe('Unknown key: "ё"'); - - error = await page.keyboard.press('😊').catch(e => e); - expect(error && error.message).toBe('Unknown key: "😊"'); - }); - function dimensions() { - const rect = document.querySelector('textarea').getBoundingClientRect(); - return { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height - }; - } - }); - describe('Page.setUserAgent', function() { it('should work', async({page, server}) => { expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla'); @@ -2494,171 +1346,6 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); - 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(requests[0].response()).toBeTruthy(); - expect(requests[0].frame() === page.mainFrame()).toBe(true); - expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); - }); - it('Page.Events.Request should report 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.postData()).toBe('{"foo":"bar"}'); - }); - 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].fromCache()).toBe(false); - expect(responses[0].fromServiceWorker()).toBe(false); - expect(responses[0].request()).toBeTruthy(); - }); - - it('Response.fromCache()', async({page, server}) => { - const responses = new Map(); - page.on('response', r => responses.set(r.url().split('/').pop(), r)); - - // Load and re-load to make sure it's cached. - await page.goto(server.PREFIX + '/cached/one-style.html'); - await page.reload(); - - expect(responses.size).toBe(2); - expect(responses.get('one-style.html').status()).toBe(304); - expect(responses.get('one-style.html').fromCache()).toBe(false); - expect(responses.get('one-style.css').status()).toBe(200); - expect(responses.get('one-style.css').fromCache()).toBe(true); - }); - it('Response.fromServiceWorker', async({page, server}) => { - const responses = new Map(); - page.on('response', r => responses.set(r.url().split('/').pop(), r)); - - // Load and re-load to make sure serviceworker is installed and running. - await page.goto(server.PREFIX + '/serviceworkers/fetch/sw.html', {waitUntil: 'networkidle2'}); - await page.evaluate(async() => await window.activationPromise); - await page.reload(); - - expect(responses.size).toBe(2); - expect(responses.get('sw.html').status()).toBe(200); - expect(responses.get('sw.html').fromServiceWorker()).toBe(true); - expect(responses.get('style.css').status()).toBe(200); - expect(responses.get('style.css').fromServiceWorker()).toBe(true); - }); - - it('Page.Events.Response should provide body', async({page, server}) => { - let response = null; - page.on('response', r => response = r); - await page.goto(server.PREFIX + '/simple.json'); - expect(response).toBeTruthy(); - expect(await response.text()).toBe('{"foo": "bar"}\n'); - expect(await response.json()).toEqual({foo: 'bar'}); - }); - it('Page.Events.Response should not report body unless request is finished', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - // Setup server to trap request. - let serverResponse = null; - server.setRoute('/get', (req, res) => { - serverResponse = res; - res.write('hello '); - }); - // Setup page to trap response. - let pageResponse = null; - let requestFinished = false; - page.on('response', r => pageResponse = r); - page.on('requestfinished', () => requestFinished = true); - // send request and wait for server response - await Promise.all([ - page.evaluate(() => fetch('./get', { method: 'GET'})), - waitForEvents(page, 'response') - ]); - - 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('Page.Events.RequestFailed', async({page, server}) => { - await page.setRequestInterception(true); - page.on('request', request => { - if (request.url().endsWith('css')) - request.abort(); - else - request.continue(); - }); - 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(failedRequests[0].response()).toBe(null); - expect(failedRequests[0].resourceType()).toBe('stylesheet'); - expect(failedRequests[0].failure().errorText).toBe('net::ERR_FAILED'); - expect(failedRequests[0].frame()).toBeTruthy(); - }); - it('Page.Events.RequestFinished', async({page, server}) => { - const requests = []; - page.on('requestfinished', 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].response()).toBeTruthy(); - expect(requests[0].frame() === page.mainFrame()).toBe(true); - expect(requests[0].frame().url()).toBe(server.EMPTY_PAGE); - }); - 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')); - page.on('requestfinished', request => events.push('requestfinished')); - await page.goto(server.EMPTY_PAGE); - 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); - expect(events).toEqual([ - `GET ${FOO_URL}`, - `302 ${FOO_URL}`, - `DONE ${FOO_URL}`, - `GET ${server.EMPTY_PAGE}`, - `200 ${server.EMPTY_PAGE}`, - `DONE ${server.EMPTY_PAGE}` - ]); - - // Check redirect chain - const redirectChain = response.request().redirectChain(); - expect(redirectChain.length).toBe(1); - expect(redirectChain[0].url()).toContain('/foo.html'); - }); - }); - describe('Page.addScriptTag', function() { it('should throw an error if no options are provided', async({page, server}) => { let error = null; @@ -3170,38 +1857,6 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); - describe('Tracing', function() { - beforeEach(function(state) { - state.outputFile = path.join(__dirname, 'assets', `trace-${state.parallelIndex}.json`); - }); - afterEach(function(state) { - fs.unlinkSync(state.outputFile); - state.outputFile = null; - }); - it('should output a trace', async({page, server, outputFile}) => { - await page.tracing.start({screenshots: true, path: outputFile}); - await page.goto(server.PREFIX + '/grid.html'); - await page.tracing.stop(); - expect(fs.existsSync(outputFile)).toBe(true); - }); - it('should run with custom categories if provided', async({page, outputFile}) => { - await page.tracing.start({path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']}); - await page.tracing.stop(); - - const traceJson = JSON.parse(fs.readFileSync(outputFile)); - expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires'); - }); - it('should throw if tracing on two pages', async({page, server, browser, outputFile}) => { - await page.tracing.start({path: outputFile}); - const newPage = await browser.newPage(); - let error = null; - await newPage.tracing.start({path: outputFile}).catch(e => error = e); - await newPage.close(); - expect(error).toBeTruthy(); - await page.tracing.stop(); - }); - }); - describe('Cookies', function() { afterEach(async({page, server}) => { const cookies = await page.cookies(server.PREFIX + '/grid.html', server.CROSS_PROCESS_PREFIX); @@ -3743,59 +2398,4 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); }); - - - /** - * @param {!EventEmitter} emitter - * @param {string} eventName - * @param {number=} eventCount - * @return {!Promise} - */ - function waitForEvents(emitter, eventName, eventCount = 1) { - let fulfill; - const promise = new Promise(x => fulfill = x); - emitter.on(eventName, onEvent); - return promise; - - function onEvent(event) { - --eventCount; - if (eventCount) - return; - emitter.removeListener(eventName, onEvent); - fulfill(event); - } - } }; - -/** - * @param {!Buffer} pdfBuffer - * @return {!Promise>} - */ -async function getPDFPages(pdfBuffer) { - const PDFJS = require('pdfjs-dist'); - PDFJS.disableWorker = true; - const data = new Uint8Array(pdfBuffer); - const doc = await PDFJS.getDocument(data); - const pages = []; - for (let i = 0; i < doc.numPages; ++i) { - const page = await doc.getPage(i + 1); - const viewport = page.getViewport(1); - // Viewport width and height is in PDF points, which is - // 1/72 of an inch. - pages.push({ - width: viewport.width / 72, - height: viewport.height / 72, - }); - page.cleanup(); - } - doc.cleanup(); - return pages; -} - -/** - * @param {number} px - * @return {number} - */ -function cssPixelsToInches(px) { - return px / 96; -} diff --git a/test/puppeteer.spec.js b/test/puppeteer.spec.js index a193d4ef393..e8ec2e1c59e 100644 --- a/test/puppeteer.spec.js +++ b/test/puppeteer.spec.js @@ -21,12 +21,12 @@ const {helper} = require('../lib/helper'); const mkdtempAsync = helper.promisify(fs.mkdtemp); const readFileAsync = helper.promisify(fs.readFile); const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); -const FrameUtils = require('./frame-utils'); +const utils = require('./utils'); -module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppeteer, PROJECT_ROOT) { - const {describe, xdescribe, fdescribe} = runner; - const {it, fit, xit} = runner; - const {beforeAll, beforeEach, afterAll, afterEach} = runner; +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer, PROJECT_ROOT}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; describe('Puppeteer', function() { describe('BrowserFetcher', function() { @@ -244,7 +244,7 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet const browser = await puppeteer.connect({browserWSEndpoint}); const pages = await browser.pages(); const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html'); - expect(FrameUtils.dumpFrames(restoredPage.mainFrame())).toBeGolden('reconnect-nested-frames.txt'); + expect(utils.dumpFrames(restoredPage.mainFrame())).toBeGolden('reconnect-nested-frames.txt'); expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); await browser.close(); }); @@ -256,19 +256,4 @@ module.exports.addTests = function(runner, expect, defaultBrowserOptions, puppet }); }); }); - - if (process.env.COVERAGE) { - describe('COVERAGE', function(){ - const coverage = helper.publicAPICoverage(); - const disabled = new Set(['page.bringToFront']); - if (!defaultBrowserOptions.headless) - disabled.add('page.pdf'); - - for (const method of coverage.keys()) { - (disabled.has(method) ? xit : it)(`public api '${method}' should be called`, async({page, server}) => { - expect(coverage.get(method)).toBe(true); - }); - } - }); - } }; diff --git a/test/test.js b/test/test.js index 0a7df7381c9..c236bcb321d 100644 --- a/test/test.js +++ b/test/test.js @@ -59,7 +59,7 @@ require('events').defaultMaxListeners *= parallel; const timeout = slowMo ? 0 : 10 * 1000; const runner = new TestRunner({timeout, parallel}); new Reporter(runner); - +const {beforeAll, beforeEach, afterAll, describe, xit, it} = runner; const {expect} = new Matchers({ toBeGolden: GoldenUtils.compare.bind(null, GOLDEN_DIR, OUTPUT_DIR) }); @@ -69,7 +69,7 @@ if (fs.existsSync(OUTPUT_DIR)) console.log('Testing on Node', process.version); -runner.beforeAll(async state => { +beforeAll(async state => { const assetsPath = path.join(__dirname, 'assets'); const cachedPath = path.join(__dirname, 'assets', 'cached'); @@ -88,12 +88,12 @@ runner.beforeAll(async state => { state.httpsServer.EMPTY_PAGE = `https://localhost:${httpsPort}/empty.html`; }); -runner.beforeEach(async({server, httpsServer}) => { +beforeEach(async({server, httpsServer}) => { server.reset(); httpsServer.reset(); }); -runner.afterAll(async({server, httpsServer}) => { +afterAll(async({server, httpsServer}) => { await Promise.all([ server.stop(), httpsServer.stop(), @@ -108,15 +108,30 @@ const testFiles = [ testFiles .map(file => path.join(__dirname, file)) .forEach(file => - require(file).addTests( - runner, - expect, - defaultBrowserOptions, - puppeteer, - PROJECT_ROOT - ) + require(file).addTests({ + testRunner: runner, + expect, + defaultBrowserOptions, + puppeteer, + PROJECT_ROOT + }) ); +if (process.env.COVERAGE) { + describe('COVERAGE', function(){ + const coverage = helper.publicAPICoverage(); + const disabled = new Set(['page.bringToFront']); + if (!defaultBrowserOptions.headless) + disabled.add('page.pdf'); + + for (const method of coverage.keys()) { + (disabled.has(method) ? xit : it)(`public api '${method}' should be called`, async({page, server}) => { + expect(coverage.get(method)).toBe(true); + }); + } + }); +} + if (process.env.CI && runner.hasFocusedTestsOrSuites()) { console.error('ERROR: "focused" tests/suites are prohibitted on bots. Remove any "fit"/"fdescribe" declarations.'); process.exit(1); diff --git a/test/tracing.spec.js b/test/tracing.spec.js new file mode 100644 index 00000000000..bcbd849f644 --- /dev/null +++ b/test/tracing.spec.js @@ -0,0 +1,56 @@ +/** + * Copyright 2017 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs'); +const path = require('path'); + +module.exports.addTests = function({testRunner, expect}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('Tracing', function() { + beforeEach(function(state) { + state.outputFile = path.join(__dirname, 'assets', `trace-${state.parallelIndex}.json`); + }); + afterEach(function(state) { + fs.unlinkSync(state.outputFile); + state.outputFile = null; + }); + it('should output a trace', async({page, server, outputFile}) => { + await page.tracing.start({screenshots: true, path: outputFile}); + await page.goto(server.PREFIX + '/grid.html'); + await page.tracing.stop(); + expect(fs.existsSync(outputFile)).toBe(true); + }); + it('should run with custom categories if provided', async({page, outputFile}) => { + await page.tracing.start({path: outputFile, categories: ['disabled-by-default-v8.cpu_profiler.hires']}); + await page.tracing.stop(); + + const traceJson = JSON.parse(fs.readFileSync(outputFile)); + expect(traceJson.metadata['trace-config']).toContain('disabled-by-default-v8.cpu_profiler.hires'); + }); + it('should throw if tracing on two pages', async({page, server, browser, outputFile}) => { + await page.tracing.start({path: outputFile}); + const newPage = await browser.newPage(); + let error = null; + await newPage.tracing.start({path: outputFile}).catch(e => error = e); + await newPage.close(); + expect(error).toBeTruthy(); + await page.tracing.stop(); + }); + }); +}; \ No newline at end of file diff --git a/test/frame-utils.js b/test/utils.js similarity index 61% rename from test/frame-utils.js rename to test/utils.js index 2a53c8d9977..cfc980582d9 100644 --- a/test/frame-utils.js +++ b/test/utils.js @@ -72,4 +72,58 @@ const utils = module.exports = { result += '\n' + utils.dumpFrames(child, ' ' + indentation); return result; }, + + /** + * @param {!EventEmitter} emitter + * @param {string} eventName + * @param {number=} eventCount + * @return {!Promise} + */ + waitForEvents: function(emitter, eventName, eventCount = 1) { + let fulfill; + const promise = new Promise(x => fulfill = x); + emitter.on(eventName, onEvent); + return promise; + + function onEvent(event) { + --eventCount; + if (eventCount) + return; + emitter.removeListener(eventName, onEvent); + fulfill(event); + } + }, + + /** + * @param {!Buffer} pdfBuffer + * @return {!Promise>} + */ + getPDFPages: async function(pdfBuffer) { + const PDFJS = require('pdfjs-dist'); + PDFJS.disableWorker = true; + const data = new Uint8Array(pdfBuffer); + const doc = await PDFJS.getDocument(data); + const pages = []; + for (let i = 0; i < doc.numPages; ++i) { + const page = await doc.getPage(i + 1); + const viewport = page.getViewport(1); + // Viewport width and height is in PDF points, which is + // 1/72 of an inch. + pages.push({ + width: viewport.width / 72, + height: viewport.height / 72, + }); + page.cleanup(); + } + doc.cleanup(); + return pages; + }, + + /** + * @param {number} px + * @return {number} + */ + cssPixelsToInches: function(px) { + return px / 96; + }, };