From 1a7995558f101a10cd847dd070b2719a8ea7f27c Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 24 Jan 2019 01:04:42 -0500 Subject: [PATCH] chore(test): prepare tests to be run with Puppeteer-Firefox (#3833) This patch aligns Puppeteer testing infrastructure with the approach we use in Puppeteer-Firefox. This patch: - makes all tests accept Puppeteer object as a function argument rather than require it statically. This way we can pass either Puppeteer or Puppeteer-Firefox to drive tests. - renames the `puppeteer.spec.js` into `launcher.spec.js`. The `puppeteer.spec.js` is now the entry point for all cross-browsers tests. --- test/browser.spec.js | 4 +- test/browsercontext.spec.js | 3 +- .../csscoverage-involved.txt | 0 .../grid-cell-0.png | Bin .../grid-cell-1.png | Bin .../grid-cell-2.png | Bin .../grid-cell-3.png | Bin .../jscoverage-involved.txt | 0 .../mock-binary-response.png | Bin .../nested-frames.txt | 0 .../reconnect-nested-frames.txt | 0 .../screenshot-clip-odd-size.png | Bin .../screenshot-clip-rect.png | Bin .../screenshot-element-bounding-box.png | Bin .../screenshot-element-fractional-offset.png | Bin .../screenshot-element-fractional.png | Bin ...creenshot-element-larger-than-viewport.png | Bin .../screenshot-element-padding-border.png | Bin .../screenshot-element-rotate.png | Bin .../screenshot-element-scrolled-into-view.png | Bin .../screenshot-grid-fullpage.png | Bin .../screenshot-offscreen-clip.png | Bin .../screenshot-sanity.png | Bin .../transparent.png | Bin test/{golden => golden-chromium}/white.jpg | Bin test/headful.spec.js | 3 +- test/ignorehttpserrors.spec.js | 5 +- test/launcher.spec.js | 446 +++++++++++++++ test/puppeteer.spec.js | 511 ++++-------------- test/test.js | 142 +---- test/tracing.spec.js | 4 +- 31 files changed, 574 insertions(+), 544 deletions(-) rename test/{golden => golden-chromium}/csscoverage-involved.txt (100%) rename test/{golden => golden-chromium}/grid-cell-0.png (100%) rename test/{golden => golden-chromium}/grid-cell-1.png (100%) rename test/{golden => golden-chromium}/grid-cell-2.png (100%) rename test/{golden => golden-chromium}/grid-cell-3.png (100%) rename test/{golden => golden-chromium}/jscoverage-involved.txt (100%) rename test/{golden => golden-chromium}/mock-binary-response.png (100%) rename test/{golden => golden-chromium}/nested-frames.txt (100%) rename test/{golden => golden-chromium}/reconnect-nested-frames.txt (100%) rename test/{golden => golden-chromium}/screenshot-clip-odd-size.png (100%) rename test/{golden => golden-chromium}/screenshot-clip-rect.png (100%) rename test/{golden => golden-chromium}/screenshot-element-bounding-box.png (100%) rename test/{golden => golden-chromium}/screenshot-element-fractional-offset.png (100%) rename test/{golden => golden-chromium}/screenshot-element-fractional.png (100%) rename test/{golden => golden-chromium}/screenshot-element-larger-than-viewport.png (100%) rename test/{golden => golden-chromium}/screenshot-element-padding-border.png (100%) rename test/{golden => golden-chromium}/screenshot-element-rotate.png (100%) rename test/{golden => golden-chromium}/screenshot-element-scrolled-into-view.png (100%) rename test/{golden => golden-chromium}/screenshot-grid-fullpage.png (100%) rename test/{golden => golden-chromium}/screenshot-offscreen-clip.png (100%) rename test/{golden => golden-chromium}/screenshot-sanity.png (100%) rename test/{golden => golden-chromium}/transparent.png (100%) rename test/{golden => golden-chromium}/white.jpg (100%) create mode 100644 test/launcher.spec.js diff --git a/test/browser.spec.js b/test/browser.spec.js index c0042d4dd44e8..cbef9ca7d659b 100644 --- a/test/browser.spec.js +++ b/test/browser.spec.js @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const utils = require('./utils'); -const puppeteer = utils.requireRoot('index'); -module.exports.addTests = function({testRunner, expect, headless}) { +module.exports.addTests = function({testRunner, expect, headless, puppeteer}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index fb1ac72790958..6107b886a7073 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -15,10 +15,9 @@ */ const utils = require('./utils'); -const puppeteer = utils.requireRoot('index'); const {TimeoutError} = utils.requireRoot('Errors'); -module.exports.addTests = function({testRunner, expect}) { +module.exports.addTests = function({testRunner, expect, puppeteer}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; diff --git a/test/golden/csscoverage-involved.txt b/test/golden-chromium/csscoverage-involved.txt similarity index 100% rename from test/golden/csscoverage-involved.txt rename to test/golden-chromium/csscoverage-involved.txt diff --git a/test/golden/grid-cell-0.png b/test/golden-chromium/grid-cell-0.png similarity index 100% rename from test/golden/grid-cell-0.png rename to test/golden-chromium/grid-cell-0.png diff --git a/test/golden/grid-cell-1.png b/test/golden-chromium/grid-cell-1.png similarity index 100% rename from test/golden/grid-cell-1.png rename to test/golden-chromium/grid-cell-1.png diff --git a/test/golden/grid-cell-2.png b/test/golden-chromium/grid-cell-2.png similarity index 100% rename from test/golden/grid-cell-2.png rename to test/golden-chromium/grid-cell-2.png diff --git a/test/golden/grid-cell-3.png b/test/golden-chromium/grid-cell-3.png similarity index 100% rename from test/golden/grid-cell-3.png rename to test/golden-chromium/grid-cell-3.png diff --git a/test/golden/jscoverage-involved.txt b/test/golden-chromium/jscoverage-involved.txt similarity index 100% rename from test/golden/jscoverage-involved.txt rename to test/golden-chromium/jscoverage-involved.txt diff --git a/test/golden/mock-binary-response.png b/test/golden-chromium/mock-binary-response.png similarity index 100% rename from test/golden/mock-binary-response.png rename to test/golden-chromium/mock-binary-response.png diff --git a/test/golden/nested-frames.txt b/test/golden-chromium/nested-frames.txt similarity index 100% rename from test/golden/nested-frames.txt rename to test/golden-chromium/nested-frames.txt diff --git a/test/golden/reconnect-nested-frames.txt b/test/golden-chromium/reconnect-nested-frames.txt similarity index 100% rename from test/golden/reconnect-nested-frames.txt rename to test/golden-chromium/reconnect-nested-frames.txt diff --git a/test/golden/screenshot-clip-odd-size.png b/test/golden-chromium/screenshot-clip-odd-size.png similarity index 100% rename from test/golden/screenshot-clip-odd-size.png rename to test/golden-chromium/screenshot-clip-odd-size.png diff --git a/test/golden/screenshot-clip-rect.png b/test/golden-chromium/screenshot-clip-rect.png similarity index 100% rename from test/golden/screenshot-clip-rect.png rename to test/golden-chromium/screenshot-clip-rect.png diff --git a/test/golden/screenshot-element-bounding-box.png b/test/golden-chromium/screenshot-element-bounding-box.png similarity index 100% rename from test/golden/screenshot-element-bounding-box.png rename to test/golden-chromium/screenshot-element-bounding-box.png diff --git a/test/golden/screenshot-element-fractional-offset.png b/test/golden-chromium/screenshot-element-fractional-offset.png similarity index 100% rename from test/golden/screenshot-element-fractional-offset.png rename to test/golden-chromium/screenshot-element-fractional-offset.png diff --git a/test/golden/screenshot-element-fractional.png b/test/golden-chromium/screenshot-element-fractional.png similarity index 100% rename from test/golden/screenshot-element-fractional.png rename to test/golden-chromium/screenshot-element-fractional.png diff --git a/test/golden/screenshot-element-larger-than-viewport.png b/test/golden-chromium/screenshot-element-larger-than-viewport.png similarity index 100% rename from test/golden/screenshot-element-larger-than-viewport.png rename to test/golden-chromium/screenshot-element-larger-than-viewport.png diff --git a/test/golden/screenshot-element-padding-border.png b/test/golden-chromium/screenshot-element-padding-border.png similarity index 100% rename from test/golden/screenshot-element-padding-border.png rename to test/golden-chromium/screenshot-element-padding-border.png diff --git a/test/golden/screenshot-element-rotate.png b/test/golden-chromium/screenshot-element-rotate.png similarity index 100% rename from test/golden/screenshot-element-rotate.png rename to test/golden-chromium/screenshot-element-rotate.png diff --git a/test/golden/screenshot-element-scrolled-into-view.png b/test/golden-chromium/screenshot-element-scrolled-into-view.png similarity index 100% rename from test/golden/screenshot-element-scrolled-into-view.png rename to test/golden-chromium/screenshot-element-scrolled-into-view.png diff --git a/test/golden/screenshot-grid-fullpage.png b/test/golden-chromium/screenshot-grid-fullpage.png similarity index 100% rename from test/golden/screenshot-grid-fullpage.png rename to test/golden-chromium/screenshot-grid-fullpage.png diff --git a/test/golden/screenshot-offscreen-clip.png b/test/golden-chromium/screenshot-offscreen-clip.png similarity index 100% rename from test/golden/screenshot-offscreen-clip.png rename to test/golden-chromium/screenshot-offscreen-clip.png diff --git a/test/golden/screenshot-sanity.png b/test/golden-chromium/screenshot-sanity.png similarity index 100% rename from test/golden/screenshot-sanity.png rename to test/golden-chromium/screenshot-sanity.png diff --git a/test/golden/transparent.png b/test/golden-chromium/transparent.png similarity index 100% rename from test/golden/transparent.png rename to test/golden-chromium/transparent.png diff --git a/test/golden/white.jpg b/test/golden-chromium/white.jpg similarity index 100% rename from test/golden/white.jpg rename to test/golden-chromium/white.jpg diff --git a/test/headful.spec.js b/test/headful.spec.js index 29ff94adc093a..965fc2431f423 100644 --- a/test/headful.spec.js +++ b/test/headful.spec.js @@ -21,7 +21,6 @@ const {helper} = require('../lib/helper'); const rmAsync = helper.promisify(require('rimraf')); const utils = require('./utils'); const {waitEvent} = utils; -const puppeteer = utils.requireRoot('index.js'); const mkdtempAsync = helper.promisify(fs.mkdtemp); const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); @@ -40,7 +39,7 @@ function waitForBackgroundPageTarget(browser) { }); } -module.exports.addTests = function({testRunner, expect, defaultBrowserOptions}) { +module.exports.addTests = function({testRunner, expect, puppeteer, defaultBrowserOptions}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; diff --git a/test/ignorehttpserrors.spec.js b/test/ignorehttpserrors.spec.js index 9730b5f280d51..3a39a415e40d8 100644 --- a/test/ignorehttpserrors.spec.js +++ b/test/ignorehttpserrors.spec.js @@ -14,10 +14,7 @@ * limitations under the License. */ -const utils = require('./utils'); -const puppeteer = utils.requireRoot('index.js'); - -module.exports.addTests = function({testRunner, expect, defaultBrowserOptions}) { +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; diff --git a/test/launcher.spec.js b/test/launcher.spec.js new file mode 100644 index 0000000000000..0a1321d5957e8 --- /dev/null +++ b/test/launcher.spec.js @@ -0,0 +1,446 @@ +/** + * 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 os = require('os'); +const path = require('path'); +const {helper} = require('../lib/helper'); +const rmAsync = helper.promisify(require('rimraf')); +const mkdtempAsync = helper.promisify(fs.mkdtemp); +const readFileAsync = helper.promisify(fs.readFile); +const statAsync = helper.promisify(fs.stat); +const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); +const utils = require('./utils'); + +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer}) { + const {describe, xdescribe, fdescribe} = testRunner; + const {it, fit, xit} = testRunner; + const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; + + describe('Puppeteer', function() { + describe('BrowserFetcher', function() { + it('should download and extract linux binary', async({server}) => { + const downloadsFolder = await mkdtempAsync(TMP_FOLDER); + const browserFetcher = puppeteer.createBrowserFetcher({ + platform: 'linux', + path: downloadsFolder, + host: server.PREFIX + }); + let revisionInfo = browserFetcher.revisionInfo('123456'); + server.setRoute(revisionInfo.url.substring(server.PREFIX.length), (req, res) => { + server.serveFile(req, res, '/chromium-linux.zip'); + }); + + expect(revisionInfo.local).toBe(false); + expect(browserFetcher.platform()).toBe('linux'); + expect(await browserFetcher.canDownload('100000')).toBe(false); + expect(await browserFetcher.canDownload('123456')).toBe(true); + + revisionInfo = await browserFetcher.download('123456'); + expect(revisionInfo.local).toBe(true); + expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe('LINUX BINARY\n'); + const expectedPermissions = os.platform() === 'win32' ? 0666 : 0755; + expect((await statAsync(revisionInfo.executablePath)).mode & 0777).toBe(expectedPermissions); + expect(await browserFetcher.localRevisions()).toEqual(['123456']); + await browserFetcher.remove('123456'); + expect(await browserFetcher.localRevisions()).toEqual([]); + await rmAsync(downloadsFolder); + }); + }); + describe('Browser.disconnect', function() { + it('should reject navigation when browser closes', async({server}) => { + server.setRoute('/one-style.css', () => {}); + const browser = await puppeteer.launch(defaultBrowserOptions); + const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); + const page = await remote.newPage(); + const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e); + await server.waitForRequest('/one-style.css'); + await remote.disconnect(); + const error = await navigationPromise; + expect(error.message).toBe('Navigation failed because browser has disconnected!'); + await browser.close(); + }); + it('should reject waitForSelector when browser closes', async({server}) => { + server.setRoute('/empty.html', () => {}); + const browser = await puppeteer.launch(defaultBrowserOptions); + const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); + const page = await remote.newPage(); + const watchdog = page.waitForSelector('div', {timeout: 60000}).catch(e => e); + await remote.disconnect(); + const error = await watchdog; + expect(error.message).toBe('Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.'); + await browser.close(); + }); + }); + describe('Puppeteer.launch', function() { + it('should reject all promises when browser is closed', async() => { + const browser = await puppeteer.launch(defaultBrowserOptions); + const page = await browser.newPage(); + let error = null; + const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e); + await browser.close(); + await neverResolves; + expect(error.message).toContain('Protocol error'); + }); + it('should reject if executable path is invalid', async({server}) => { + let waitError = null; + const options = Object.assign({}, defaultBrowserOptions, {executablePath: 'random-invalid-path'}); + await puppeteer.launch(options).catch(e => waitError = e); + expect(waitError.message.startsWith('Failed to launch chrome! spawn random-invalid-path ENOENT')).toBe(true); + }); + it('userDataDir option', async({server}) => { + const userDataDir = await mkdtempAsync(TMP_FOLDER); + const options = Object.assign({userDataDir}, defaultBrowserOptions); + const browser = await puppeteer.launch(options); + // Open a page to make sure its functional. + await browser.newPage(); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + await browser.close(); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await rmAsync(userDataDir).catch(e => {}); + }); + it('userDataDir argument', async({server}) => { + const userDataDir = await mkdtempAsync(TMP_FOLDER); + const options = Object.assign({}, defaultBrowserOptions); + options.args = [`--user-data-dir=${userDataDir}`].concat(options.args || []); + const browser = await puppeteer.launch(options); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + await browser.close(); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await rmAsync(userDataDir).catch(e => {}); + }); + it('userDataDir option should restore state', async({server}) => { + const userDataDir = await mkdtempAsync(TMP_FOLDER); + const options = Object.assign({userDataDir}, defaultBrowserOptions); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => localStorage.hey = 'hello'); + await browser.close(); + + const browser2 = await puppeteer.launch(options); + const page2 = await browser2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); + await browser2.close(); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await rmAsync(userDataDir).catch(e => {}); + }); + it('userDataDir option should restore cookies', async({server}) => { + const userDataDir = await mkdtempAsync(TMP_FOLDER); + const options = Object.assign({userDataDir}, defaultBrowserOptions); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); + await browser.close(); + + const browser2 = await puppeteer.launch(options); + const page2 = await browser2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); + await browser2.close(); + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await rmAsync(userDataDir).catch(e => {}); + }); + it('should return the default chrome arguments', async() => { + expect(puppeteer.defaultArgs()).toContain('--no-first-run'); + expect(puppeteer.defaultArgs()).toContain('--headless'); + expect(puppeteer.defaultArgs({headless: false})).not.toContain('--headless'); + expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('--user-data-dir=foo'); + }); + it('should dump browser process stderr', async({server}) => { + const dumpioTextToLog = 'MAGIC_DUMPIO_TEST'; + let dumpioData = ''; + const {spawn} = require('child_process'); + const options = Object.assign({}, defaultBrowserOptions, {dumpio: true}); + const res = spawn('node', + [path.join(__dirname, 'fixtures', 'dumpio.js'), utils.projectRoot(), JSON.stringify(options), server.EMPTY_PAGE, dumpioTextToLog]); + res.stderr.on('data', data => dumpioData += data.toString('utf8')); + await new Promise(resolve => res.on('close', resolve)); + + expect(dumpioData).toContain(dumpioTextToLog); + }); + it('should close the browser when the node process closes', async({ server }) => { + const {spawn, execSync} = require('child_process'); + const res = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), utils.projectRoot(), JSON.stringify(defaultBrowserOptions)]); + let wsEndPointCallback; + const wsEndPointPromise = new Promise(x => wsEndPointCallback = x); + let output = ''; + res.stdout.on('data', data => { + output += data; + if (output.indexOf('\n')) + wsEndPointCallback(output.substring(0, output.indexOf('\n'))); + }); + const browser = await puppeteer.connect({ browserWSEndpoint: await wsEndPointPromise }); + const promises = [ + new Promise(resolve => browser.once('disconnected', resolve)), + new Promise(resolve => res.on('close', resolve))]; + if (process.platform === 'win32') + execSync(`taskkill /pid ${res.pid} /T /F`); + else + process.kill(res.pid); + await Promise.all(promises); + }); + it('should support the pipe option', async() => { + const options = Object.assign({pipe: true}, defaultBrowserOptions); + const browser = await puppeteer.launch(options); + expect((await browser.pages()).length).toBe(1); + expect(browser.wsEndpoint()).toBe(''); + const page = await browser.newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + await browser.close(); + }); + it('should support the pipe argument', async() => { + const options = Object.assign({}, defaultBrowserOptions); + options.args = ['--remote-debugging-pipe'].concat(options.args || []); + const browser = await puppeteer.launch(options); + expect(browser.wsEndpoint()).toBe(''); + const page = await browser.newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + await browser.close(); + }); + it('should fire "disconnected" when closing with pipe', async() => { + const options = Object.assign({pipe: true}, defaultBrowserOptions); + const browser = await puppeteer.launch(options); + const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); + // Emulate user exiting browser. + browser.process().kill(); + await disconnectedEventPromise; + }); + it('should work with no default arguments', async() => { + const options = Object.assign({}, defaultBrowserOptions); + options.ignoreDefaultArgs = true; + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + expect(await page.evaluate('11 * 11')).toBe(121); + await page.close(); + await browser.close(); + }); + it('should filter out ignored default arguments', async() => { + // Make sure we launch with `--enable-automation` by default. + const defaultArgs = puppeteer.defaultArgs(); + const browser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { + // Ignore first and third default argument. + ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ], + })); + const spawnargs = browser.process().spawnargs; + expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); + expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); + expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); + await browser.close(); + }); + it('should have default url when launching browser', async function() { + const browser = await puppeteer.launch(defaultBrowserOptions); + const pages = (await browser.pages()).map(page => page.url()); + expect(pages).toEqual(['about:blank']); + await browser.close(); + }); + it('should have custom url when launching browser', async function({server}) { + const customUrl = server.PREFIX + '/empty.html'; + const options = Object.assign({}, defaultBrowserOptions); + options.args = [customUrl].concat(options.args || []); + const browser = await puppeteer.launch(options); + const pages = await browser.pages(); + expect(pages.length).toBe(1); + if (pages[0].url() !== customUrl) + await pages[0].waitForNavigation(); + expect(pages[0].url()).toBe(customUrl); + await browser.close(); + }); + it('should set the default viewport', async() => { + const options = Object.assign({}, defaultBrowserOptions, { + defaultViewport: { + width: 456, + height: 789 + } + }); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + expect(await page.evaluate('window.innerWidth')).toBe(456); + expect(await page.evaluate('window.innerHeight')).toBe(789); + await browser.close(); + }); + it('should disable the default viewport', async() => { + const options = Object.assign({}, defaultBrowserOptions, { + defaultViewport: null + }); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + expect(page.viewport()).toBe(null); + await browser.close(); + }); + it('should take fullPage screenshots when defaultViewport is null', async({server}) => { + const options = Object.assign({}, defaultBrowserOptions, { + defaultViewport: null + }); + const browser = await puppeteer.launch(options); + const page = await browser.newPage(); + await page.goto(server.PREFIX + '/grid.html'); + const screenshot = await page.screenshot({ + fullPage: true + }); + expect(screenshot).toBeInstanceOf(Buffer); + await browser.close(); + }); + }); + describe('Puppeteer.connect', function() { + it('should be able to connect multiple times to the same browser', async({server}) => { + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browser = await puppeteer.connect({ + browserWSEndpoint: originalBrowser.wsEndpoint() + }); + const page = await browser.newPage(); + expect(await page.evaluate(() => 7 * 8)).toBe(56); + browser.disconnect(); + + const secondPage = await originalBrowser.newPage(); + expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work'); + await originalBrowser.close(); + }); + it('should support ignoreHTTPSErrors option', async({httpsServer}) => { + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browserWSEndpoint = originalBrowser.wsEndpoint(); + + const browser = await puppeteer.connect({browserWSEndpoint, ignoreHTTPSErrors: true}); + const page = await browser.newPage(); + let error = null; + const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); + expect(error).toBe(null); + expect(response.ok()).toBe(true); + expect(response.securityDetails()).toBeTruthy(); + expect(response.securityDetails().protocol()).toBe('TLS 1.2'); + await page.close(); + await browser.close(); + }); + it('should be able to reconnect to a disconnected browser', async({server}) => { + const originalBrowser = await puppeteer.launch(defaultBrowserOptions); + const browserWSEndpoint = originalBrowser.wsEndpoint(); + const page = await originalBrowser.newPage(); + await page.goto(server.PREFIX + '/frames/nested-frames.html'); + originalBrowser.disconnect(); + + 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(utils.dumpFrames(restoredPage.mainFrame())).toBeGolden('reconnect-nested-frames.txt'); + expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); + await browser.close(); + }); + it('should be able to connect using browserUrl, with and without trailing slash', async({server}) => { + const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'] + })); + const browserURL = 'http://127.0.0.1:21222'; + + const browser1 = await puppeteer.connect({browserURL}); + const page1 = await browser1.newPage(); + expect(await page1.evaluate(() => 7 * 8)).toBe(56); + browser1.disconnect(); + + const browser2 = await puppeteer.connect({browserURL: browserURL + '/'}); + const page2 = await browser2.newPage(); + expect(await page2.evaluate(() => 8 * 7)).toBe(56); + browser2.disconnect(); + originalBrowser.close(); + }); + it('should throw when using both browserWSEndpoint and browserURL', async({server}) => { + const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'] + })); + const browserURL = 'http://127.0.0.1:21222'; + + let error = null; + await puppeteer.connect({browserURL, browserWSEndpoint: originalBrowser.wsEndpoint()}).catch(e => error = e); + expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport'); + + originalBrowser.close(); + }); + it('should throw when trying to connect to non-existing browser', async({server}) => { + const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { + args: ['--remote-debugging-port=21222'] + })); + const browserURL = 'http://127.0.0.1:32333'; + + let error = null; + await puppeteer.connect({browserURL}).catch(e => error = e); + expect(error.message).toContain('Failed to fetch browser webSocket url from'); + + originalBrowser.close(); + }); + }); + describe('Puppeteer.executablePath', function() { + it('should work', async({server}) => { + const executablePath = puppeteer.executablePath(); + expect(fs.existsSync(executablePath)).toBe(true); + }); + }); + }); + + describe('Browser target events', function() { + it('should work', async({server}) => { + const browser = await puppeteer.launch(defaultBrowserOptions); + const events = []; + browser.on('targetcreated', () => events.push('CREATED')); + browser.on('targetchanged', () => events.push('CHANGED')); + browser.on('targetdestroyed', () => events.push('DESTROYED')); + const page = await browser.newPage(); + await page.goto(server.EMPTY_PAGE); + await page.close(); + expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']); + await browser.close(); + }); + }); + + describe('Browser.Events.disconnected', function() { + it('should be 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 Promise.all([ + utils.waitEvent(remoteBrowser2, 'disconnected'), + remoteBrowser2.disconnect(), + ]); + + expect(disconnectedOriginal).toBe(0); + expect(disconnectedRemote1).toBe(0); + expect(disconnectedRemote2).toBe(1); + + await Promise.all([ + utils.waitEvent(remoteBrowser1, 'disconnected'), + utils.waitEvent(originalBrowser, 'disconnected'), + originalBrowser.close(), + ]); + + expect(disconnectedOriginal).toBe(1); + expect(disconnectedRemote1).toBe(1); + expect(disconnectedRemote2).toBe(1); + }); + }); + +}; diff --git a/test/puppeteer.spec.js b/test/puppeteer.spec.js index 019c62bfecd4c..985272ec001ac 100644 --- a/test/puppeteer.spec.js +++ b/test/puppeteer.spec.js @@ -1,5 +1,5 @@ /** - * Copyright 2017 Google Inc. All rights reserved. + * Copyright 2019 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. @@ -14,434 +14,119 @@ * limitations under the License. */ const fs = require('fs'); -const os = require('os'); const path = require('path'); -const {helper} = require('../lib/helper'); -const rmAsync = helper.promisify(require('rimraf')); -const mkdtempAsync = helper.promisify(fs.mkdtemp); -const readFileAsync = helper.promisify(fs.readFile); -const statAsync = helper.promisify(fs.stat); -const TMP_FOLDER = path.join(os.tmpdir(), 'pptr_tmp_folder-'); -const utils = require('./utils'); -const puppeteer = utils.requireRoot('index'); +const rm = require('rimraf').sync; +const GoldenUtils = require('./golden-utils'); +const {Matchers} = require('../utils/testrunner/'); -module.exports.addTests = function({testRunner, expect, defaultBrowserOptions}) { +const YELLOW_COLOR = '\x1b[33m'; +const RESET_COLOR = '\x1b[0m'; + +module.exports.addTests = ({testRunner, product, puppeteer, defaultBrowserOptions}) => { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; - describe('Puppeteer', function() { - describe('BrowserFetcher', function() { - it('should download and extract linux binary', async({server}) => { - const downloadsFolder = await mkdtempAsync(TMP_FOLDER); - const browserFetcher = puppeteer.createBrowserFetcher({ - platform: 'linux', - path: downloadsFolder, - host: server.PREFIX - }); - let revisionInfo = browserFetcher.revisionInfo('123456'); - server.setRoute(revisionInfo.url.substring(server.PREFIX.length), (req, res) => { - server.serveFile(req, res, '/chromium-linux.zip'); - }); - - expect(revisionInfo.local).toBe(false); - expect(browserFetcher.platform()).toBe('linux'); - expect(await browserFetcher.canDownload('100000')).toBe(false); - expect(await browserFetcher.canDownload('123456')).toBe(true); + if (defaultBrowserOptions.executablePath) { + console.warn(`${YELLOW_COLOR}WARN: running ${product} tests with ${defaultBrowserOptions.executablePath}${RESET_COLOR}`); + } else { + // Make sure the `npm install` was run after the chromium roll. + if (!fs.existsSync(puppeteer.executablePath())) + throw new Error(`Browser is not downloaded. Run 'npm install' and try to re-run tests`); + } + + const GOLDEN_DIR = path.join(__dirname, 'golden-' + product.toLowerCase()); + const OUTPUT_DIR = path.join(__dirname, 'output-' + product.toLowerCase()); + if (fs.existsSync(OUTPUT_DIR)) + rm(OUTPUT_DIR); + const {expect} = new Matchers({ + toBeGolden: GoldenUtils.compare.bind(null, GOLDEN_DIR, OUTPUT_DIR) + }); - revisionInfo = await browserFetcher.download('123456'); - expect(revisionInfo.local).toBe(true); - expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe('LINUX BINARY\n'); - const expectedPermissions = os.platform() === 'win32' ? 0666 : 0755; - expect((await statAsync(revisionInfo.executablePath)).mode & 0777).toBe(expectedPermissions); - expect(await browserFetcher.localRevisions()).toEqual(['123456']); - await browserFetcher.remove('123456'); - expect(await browserFetcher.localRevisions()).toEqual([]); - await rmAsync(downloadsFolder); - }); - }); - describe('Browser.disconnect', function() { - it('should reject navigation when browser closes', async({server}) => { - server.setRoute('/one-style.css', () => {}); - const browser = await puppeteer.launch(defaultBrowserOptions); - const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); - const page = await remote.newPage(); - const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e); - await server.waitForRequest('/one-style.css'); - await remote.disconnect(); - const error = await navigationPromise; - expect(error.message).toBe('Navigation failed because browser has disconnected!'); - await browser.close(); - }); - it('should reject waitForSelector when browser closes', async({server}) => { - server.setRoute('/empty.html', () => {}); - const browser = await puppeteer.launch(defaultBrowserOptions); - const remote = await puppeteer.connect({browserWSEndpoint: browser.wsEndpoint()}); - const page = await remote.newPage(); - const watchdog = page.waitForSelector('div', {timeout: 60000}).catch(e => e); - await remote.disconnect(); - const error = await watchdog; - expect(error.message).toBe('Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.'); - await browser.close(); - }); + const testOptions = { + testRunner, + product, + puppeteer, + expect, + defaultBrowserOptions, + headless: !!defaultBrowserOptions.headless, + }; + + describe('Browser', function() { + beforeAll(async state => { + state.browser = await puppeteer.launch(defaultBrowserOptions); }); - describe('Puppeteer.launch', function() { - it('should reject all promises when browser is closed', async() => { - const browser = await puppeteer.launch(defaultBrowserOptions); - const page = await browser.newPage(); - let error = null; - const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e); - await browser.close(); - await neverResolves; - expect(error.message).toContain('Protocol error'); - }); - it('should reject if executable path is invalid', async({server}) => { - let waitError = null; - const options = Object.assign({}, defaultBrowserOptions, {executablePath: 'random-invalid-path'}); - await puppeteer.launch(options).catch(e => waitError = e); - expect(waitError.message.startsWith('Failed to launch chrome! spawn random-invalid-path ENOENT')).toBe(true); - }); - it('userDataDir option', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browser = await puppeteer.launch(options); - // Open a page to make sure its functional. - await browser.newPage(); - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - await browser.close(); - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); - }); - it('userDataDir argument', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({}, defaultBrowserOptions); - options.args = [`--user-data-dir=${userDataDir}`].concat(options.args || []); - const browser = await puppeteer.launch(options); - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - await browser.close(); - expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); - }); - it('userDataDir option should restore state', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => localStorage.hey = 'hello'); - await browser.close(); - - const browser2 = await puppeteer.launch(options); - const page2 = await browser2.newPage(); - await page2.goto(server.EMPTY_PAGE); - expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); - await browser2.close(); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); - }); - it('userDataDir option should restore cookies', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); - await browser.close(); - const browser2 = await puppeteer.launch(options); - const page2 = await browser2.newPage(); - await page2.goto(server.EMPTY_PAGE); - expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); - await browser2.close(); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); - }); - it('should return the default chrome arguments', async() => { - expect(puppeteer.defaultArgs()).toContain('--no-first-run'); - expect(puppeteer.defaultArgs()).toContain('--headless'); - expect(puppeteer.defaultArgs({headless: false})).not.toContain('--headless'); - expect(puppeteer.defaultArgs({userDataDir: 'foo'})).toContain('--user-data-dir=foo'); - }); - it('should dump browser process stderr', async({server}) => { - const dumpioTextToLog = 'MAGIC_DUMPIO_TEST'; - let dumpioData = ''; - const {spawn} = require('child_process'); - const options = Object.assign({}, defaultBrowserOptions, {dumpio: true}); - const res = spawn('node', - [path.join(__dirname, 'fixtures', 'dumpio.js'), utils.projectRoot(), JSON.stringify(options), server.EMPTY_PAGE, dumpioTextToLog]); - res.stderr.on('data', data => dumpioData += data.toString('utf8')); - await new Promise(resolve => res.on('close', resolve)); - - expect(dumpioData).toContain(dumpioTextToLog); - }); - it('should close the browser when the node process closes', async({ server }) => { - const {spawn, execSync} = require('child_process'); - const res = spawn('node', [path.join(__dirname, 'fixtures', 'closeme.js'), utils.projectRoot(), JSON.stringify(defaultBrowserOptions)]); - let wsEndPointCallback; - const wsEndPointPromise = new Promise(x => wsEndPointCallback = x); - let output = ''; - res.stdout.on('data', data => { - output += data; - if (output.indexOf('\n')) - wsEndPointCallback(output.substring(0, output.indexOf('\n'))); - }); - const browser = await puppeteer.connect({ browserWSEndpoint: await wsEndPointPromise }); - const promises = [ - new Promise(resolve => browser.once('disconnected', resolve)), - new Promise(resolve => res.on('close', resolve))]; - if (process.platform === 'win32') - execSync(`taskkill /pid ${res.pid} /T /F`); - else - process.kill(res.pid); - await Promise.all(promises); - }); - it('should support the pipe option', async() => { - const options = Object.assign({pipe: true}, defaultBrowserOptions); - const browser = await puppeteer.launch(options); - expect((await browser.pages()).length).toBe(1); - expect(browser.wsEndpoint()).toBe(''); - const page = await browser.newPage(); - expect(await page.evaluate('11 * 11')).toBe(121); - await page.close(); - await browser.close(); - }); - it('should support the pipe argument', async() => { - const options = Object.assign({}, defaultBrowserOptions); - options.args = ['--remote-debugging-pipe'].concat(options.args || []); - const browser = await puppeteer.launch(options); - expect(browser.wsEndpoint()).toBe(''); - const page = await browser.newPage(); - expect(await page.evaluate('11 * 11')).toBe(121); - await page.close(); - await browser.close(); - }); - it('should fire "disconnected" when closing with pipe', async() => { - const options = Object.assign({pipe: true}, defaultBrowserOptions); - const browser = await puppeteer.launch(options); - const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve)); - // Emulate user exiting browser. - browser.process().kill(); - await disconnectedEventPromise; - }); - it('should work with no default arguments', async() => { - const options = Object.assign({}, defaultBrowserOptions); - options.ignoreDefaultArgs = true; - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - expect(await page.evaluate('11 * 11')).toBe(121); - await page.close(); - await browser.close(); - }); - it('should filter out ignored default arguments', async() => { - // Make sure we launch with `--enable-automation` by default. - const defaultArgs = puppeteer.defaultArgs(); - const browser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - // Ignore first and third default argument. - ignoreDefaultArgs: [ defaultArgs[0], defaultArgs[2] ], - })); - const spawnargs = browser.process().spawnargs; - expect(spawnargs.indexOf(defaultArgs[0])).toBe(-1); - expect(spawnargs.indexOf(defaultArgs[1])).not.toBe(-1); - expect(spawnargs.indexOf(defaultArgs[2])).toBe(-1); - await browser.close(); - }); - it('should have default url when launching browser', async function() { - const browser = await puppeteer.launch(defaultBrowserOptions); - const pages = (await browser.pages()).map(page => page.url()); - expect(pages).toEqual(['about:blank']); - await browser.close(); - }); - it('should have custom url when launching browser', async function({server}) { - const customUrl = server.PREFIX + '/empty.html'; - const options = Object.assign({}, defaultBrowserOptions); - options.args = [customUrl].concat(options.args || []); - const browser = await puppeteer.launch(options); - const pages = await browser.pages(); - expect(pages.length).toBe(1); - if (pages[0].url() !== customUrl) - await pages[0].waitForNavigation(); - expect(pages[0].url()).toBe(customUrl); - await browser.close(); - }); - it('should set the default viewport', async() => { - const options = Object.assign({}, defaultBrowserOptions, { - defaultViewport: { - width: 456, - height: 789 - } - }); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - expect(await page.evaluate('window.innerWidth')).toBe(456); - expect(await page.evaluate('window.innerHeight')).toBe(789); - await browser.close(); - }); - it('should disable the default viewport', async() => { - const options = Object.assign({}, defaultBrowserOptions, { - defaultViewport: null - }); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - expect(page.viewport()).toBe(null); - await browser.close(); - }); - it('should take fullPage screenshots when defaultViewport is null', async({server}) => { - const options = Object.assign({}, defaultBrowserOptions, { - defaultViewport: null - }); - const browser = await puppeteer.launch(options); - const page = await browser.newPage(); - await page.goto(server.PREFIX + '/grid.html'); - const screenshot = await page.screenshot({ - fullPage: true - }); - expect(screenshot).toBeInstanceOf(Buffer); - await browser.close(); - }); + afterAll(async state => { + await state.browser.close(); + state.browser = null; }); - describe('Puppeteer.connect', function() { - it('should be able to connect multiple times to the same browser', async({server}) => { - const originalBrowser = await puppeteer.launch(defaultBrowserOptions); - const browser = await puppeteer.connect({ - browserWSEndpoint: originalBrowser.wsEndpoint() - }); - const page = await browser.newPage(); - expect(await page.evaluate(() => 7 * 8)).toBe(56); - browser.disconnect(); - - const secondPage = await originalBrowser.newPage(); - expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work'); - await originalBrowser.close(); - }); - it('should support ignoreHTTPSErrors option', async({httpsServer}) => { - const originalBrowser = await puppeteer.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.wsEndpoint(); - - const browser = await puppeteer.connect({browserWSEndpoint, ignoreHTTPSErrors: true}); - const page = await browser.newPage(); - let error = null; - const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - expect(error).toBe(null); - expect(response.ok()).toBe(true); - expect(response.securityDetails()).toBeTruthy(); - expect(response.securityDetails().protocol()).toBe('TLS 1.2'); - await page.close(); - await browser.close(); - }); - it('should be able to reconnect to a disconnected browser', async({server}) => { - const originalBrowser = await puppeteer.launch(defaultBrowserOptions); - const browserWSEndpoint = originalBrowser.wsEndpoint(); - const page = await originalBrowser.newPage(); - await page.goto(server.PREFIX + '/frames/nested-frames.html'); - originalBrowser.disconnect(); - - 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(utils.dumpFrames(restoredPage.mainFrame())).toBeGolden('reconnect-nested-frames.txt'); - expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56); - await browser.close(); - }); - it('should be able to connect using browserUrl, with and without trailing slash', async({server}) => { - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); - const browserURL = 'http://127.0.0.1:21222'; - - const browser1 = await puppeteer.connect({browserURL}); - const page1 = await browser1.newPage(); - expect(await page1.evaluate(() => 7 * 8)).toBe(56); - browser1.disconnect(); - - const browser2 = await puppeteer.connect({browserURL: browserURL + '/'}); - const page2 = await browser2.newPage(); - expect(await page2.evaluate(() => 8 * 7)).toBe(56); - browser2.disconnect(); - originalBrowser.close(); - }); - it('should throw when using both browserWSEndpoint and browserURL', async({server}) => { - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); - const browserURL = 'http://127.0.0.1:21222'; - - let error = null; - await puppeteer.connect({browserURL, browserWSEndpoint: originalBrowser.wsEndpoint()}).catch(e => error = e); - expect(error.message).toContain('Exactly one of browserWSEndpoint, browserURL or transport'); - - originalBrowser.close(); - }); - it('should throw when trying to connect to non-existing browser', async({server}) => { - const originalBrowser = await puppeteer.launch(Object.assign({}, defaultBrowserOptions, { - args: ['--remote-debugging-port=21222'] - })); - const browserURL = 'http://127.0.0.1:32333'; - - let error = null; - await puppeteer.connect({browserURL}).catch(e => error = e); - expect(error.message).toContain('Failed to fetch browser webSocket url from'); - originalBrowser.close(); - }); + beforeEach(async(state, test) => { + const rl = require('readline').createInterface({input: state.browser.process().stderr}); + test.output = ''; + rl.on('line', onLine); + state.tearDown = () => { + rl.removeListener('line', onLine); + rl.close(); + }; + function onLine(line) { + test.output += line + '\n'; + } }); - describe('Puppeteer.executablePath', function() { - it('should work', async({server}) => { - const executablePath = puppeteer.executablePath(); - expect(fs.existsSync(executablePath)).toBe(true); - }); - }); - }); - describe('Browser target events', function() { - it('should work', async({server}) => { - const browser = await puppeteer.launch(defaultBrowserOptions); - const events = []; - browser.on('targetcreated', () => events.push('CREATED')); - browser.on('targetchanged', () => events.push('CHANGED')); - browser.on('targetdestroyed', () => events.push('DESTROYED')); - const page = await browser.newPage(); - await page.goto(server.EMPTY_PAGE); - await page.close(); - expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']); - await browser.close(); + afterEach(async state => { + state.tearDown(); }); - }); - describe('Browser.Events.disconnected', function() { - it('should be 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 Promise.all([ - utils.waitEvent(remoteBrowser2, 'disconnected'), - remoteBrowser2.disconnect(), - ]); - - expect(disconnectedOriginal).toBe(0); - expect(disconnectedRemote1).toBe(0); - expect(disconnectedRemote2).toBe(1); - - await Promise.all([ - utils.waitEvent(remoteBrowser1, 'disconnected'), - utils.waitEvent(originalBrowser, 'disconnected'), - originalBrowser.close(), - ]); - - expect(disconnectedOriginal).toBe(1); - expect(disconnectedRemote1).toBe(1); - expect(disconnectedRemote2).toBe(1); + describe('Page', function() { + beforeEach(async state => { + state.context = await state.browser.createIncognitoBrowserContext(); + state.page = await state.context.newPage(); + }); + + afterEach(async state => { + // This closes all pages. + await state.context.close(); + state.context = null; + state.page = null; + }); + + // Page-level tests that are given a browser, a context and a page. + // Each test is launched in a new browser context. + require('./CDPSession.spec.js').addTests(testOptions); + require('./accessibility.spec.js').addTests(testOptions); + require('./browser.spec.js').addTests(testOptions); + require('./cookies.spec.js').addTests(testOptions); + require('./coverage.spec.js').addTests(testOptions); + require('./elementhandle.spec.js').addTests(testOptions); + require('./queryselector.spec.js').addTests(testOptions); + require('./waittask.spec.js').addTests(testOptions); + require('./frame.spec.js').addTests(testOptions); + require('./input.spec.js').addTests(testOptions); + require('./mouse.spec.js').addTests(testOptions); + require('./keyboard.spec.js').addTests(testOptions); + require('./touchscreen.spec.js').addTests(testOptions); + require('./click.spec.js').addTests(testOptions); + require('./jshandle.spec.js').addTests(testOptions); + require('./network.spec.js').addTests(testOptions); + require('./page.spec.js').addTests(testOptions); + require('./dialog.spec.js').addTests(testOptions); + require('./navigation.spec.js').addTests(testOptions); + require('./evaluation.spec.js').addTests(testOptions); + require('./emulation.spec.js').addTests(testOptions); + require('./screenshot.spec.js').addTests(testOptions); + require('./target.spec.js').addTests(testOptions); + require('./worker.spec.js').addTests(testOptions); }); + + // Browser-level tests that are given a browser. + require('./browsercontext.spec.js').addTests(testOptions); }); + // Top-level tests that launch Browser themselves. + require('./ignorehttpserrors.spec.js').addTests(testOptions); + require('./launcher.spec.js').addTests(testOptions); + require('./headful.spec.js').addTests(testOptions); + require('./tracing.spec.js').addTests(testOptions); }; diff --git a/test/test.js b/test/test.js index b8c4a77b3c11c..392cfec11f663 100644 --- a/test/test.js +++ b/test/test.js @@ -13,37 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const fs = require('fs'); -const rm = require('rimraf').sync; const path = require('path'); const {TestServer} = require('../utils/testserver/'); -const GoldenUtils = require('./golden-utils'); -const GOLDEN_DIR = path.join(__dirname, 'golden'); -const OUTPUT_DIR = path.join(__dirname, 'output'); -const {TestRunner, Reporter, Matchers} = require('../utils/testrunner/'); +const {TestRunner, Reporter} = require('../utils/testrunner/'); const utils = require('./utils'); -const {helper, assert} = require('../lib/helper'); -if (process.env.COVERAGE) - helper.recordPublicAPICoverage(); - -const puppeteer = utils.requireRoot('index'); - -const YELLOW_COLOR = '\x1b[33m'; -const RESET_COLOR = '\x1b[0m'; - const headless = (process.env.HEADLESS || 'true').trim().toLowerCase() === 'true'; -const executablePath = process.env.CHROME; - -if (executablePath) - console.warn(`${YELLOW_COLOR}WARN: running tests with ${executablePath}${RESET_COLOR}`); -// Make sure the `npm install` was run after the chromium roll. -assert(fs.existsSync(puppeteer.executablePath()), `Chromium is not Downloaded. Run 'npm install' and try to re-run tests`); - const slowMo = parseInt((process.env.SLOW_MO || '0').trim(), 10); const defaultBrowserOptions = { handleSIGINT: false, - executablePath, + executablePath: process.env.CHROME, slowMo, headless, dumpio: (process.env.DUMPIO || 'false').trim().toLowerCase() === 'true', @@ -59,14 +38,8 @@ require('events').defaultMaxListeners *= parallel; const timeout = slowMo ? 0 : 10 * 1000; const testRunner = new TestRunner({timeout, parallel}); -const {expect} = new Matchers({ - toBeGolden: GoldenUtils.compare.bind(null, GOLDEN_DIR, OUTPUT_DIR) -}); const {describe, it, xit, beforeAll, afterAll, beforeEach, afterEach} = testRunner; -if (fs.existsSync(OUTPUT_DIR)) - rm(OUTPUT_DIR); - console.log('Testing on Node', process.version); beforeAll(async state => { @@ -102,99 +75,34 @@ beforeEach(async({server, httpsServer}) => { httpsServer.reset(); }); -describe('Browser', function() { - beforeAll(async state => { - state.browser = await puppeteer.launch(defaultBrowserOptions); - }); - - afterAll(async state => { - await state.browser.close(); - state.browser = null; - }); - - beforeEach(async(state, test) => { - const rl = require('readline').createInterface({input: state.browser.process().stderr}); - test.output = ''; - rl.on('line', onLine); - state.tearDown = () => { - rl.removeListener('line', onLine); - rl.close(); - }; - function onLine(line) { - test.output += line + '\n'; - } - }); +describe('Chromium', () => { + const {helper} = require('../lib/helper'); + if (process.env.COVERAGE) + helper.recordPublicAPICoverage(); - afterEach(async state => { - state.tearDown(); + require('./puppeteer.spec.js').addTests({ + product: 'Chromium', + puppeteer: utils.requireRoot('index'), + defaultBrowserOptions, + testRunner, }); - - describe('Page', function() { - beforeEach(async state => { - state.context = await state.browser.createIncognitoBrowserContext(); - state.page = await state.context.newPage(); - }); - - afterEach(async state => { - // This closes all pages. - await state.context.close(); - state.context = null; - state.page = null; + if (process.env.COVERAGE) { + describe('COVERAGE', function() { + const coverage = helper.publicAPICoverage(); + const disabled = new Set(['page.bringToFront']); + if (!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}) => { + if (!coverage.get(method)) + throw new Error('NOT CALLED!'); + }); + } }); - - // Page-level tests that are given a browser, a context and a page. - // Each test is launched in a new browser context. - require('./CDPSession.spec.js').addTests({testRunner, expect}); - require('./accessibility.spec.js').addTests({testRunner, expect}); - require('./browser.spec.js').addTests({testRunner, expect, headless}); - require('./cookies.spec.js').addTests({testRunner, expect}); - require('./coverage.spec.js').addTests({testRunner, expect}); - require('./elementhandle.spec.js').addTests({testRunner, expect}); - require('./queryselector.spec.js').addTests({testRunner, expect}); - require('./waittask.spec.js').addTests({testRunner, expect}); - require('./frame.spec.js').addTests({testRunner, expect}); - require('./input.spec.js').addTests({testRunner, expect}); - require('./mouse.spec.js').addTests({testRunner, expect}); - require('./keyboard.spec.js').addTests({testRunner, expect}); - require('./touchscreen.spec.js').addTests({testRunner, expect}); - require('./click.spec.js').addTests({testRunner, expect}); - require('./jshandle.spec.js').addTests({testRunner, expect}); - require('./network.spec.js').addTests({testRunner, expect}); - require('./page.spec.js').addTests({testRunner, expect, headless}); - require('./dialog.spec.js').addTests({testRunner, expect, headless}); - require('./navigation.spec.js').addTests({testRunner, expect, headless}); - require('./evaluation.spec.js').addTests({testRunner, expect, headless}); - require('./emulation.spec.js').addTests({testRunner, expect, headless}); - require('./screenshot.spec.js').addTests({testRunner, expect}); - require('./target.spec.js').addTests({testRunner, expect}); - require('./worker.spec.js').addTests({testRunner, expect}); - }); - - // Browser-level tests that are given a browser. - require('./browsercontext.spec.js').addTests({testRunner, expect}); + } }); -// Top-level tests that launch Browser themselves. -require('./ignorehttpserrors.spec.js').addTests({testRunner, expect, defaultBrowserOptions}); -require('./puppeteer.spec.js').addTests({testRunner, expect, defaultBrowserOptions}); -require('./headful.spec.js').addTests({testRunner, expect, defaultBrowserOptions}); -require('./tracing.spec.js').addTests({testRunner, expect, defaultBrowserOptions}); - -if (process.env.COVERAGE) { - describe('COVERAGE', function() { - const coverage = helper.publicAPICoverage(); - const disabled = new Set(['page.bringToFront']); - if (!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 && testRunner.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 index 3bce32e585599..f89e3b8d5cebb 100644 --- a/test/tracing.spec.js +++ b/test/tracing.spec.js @@ -16,10 +16,8 @@ const fs = require('fs'); const path = require('path'); -const utils = require('./utils'); -const puppeteer = utils.requireRoot('index'); -module.exports.addTests = function({testRunner, expect, defaultBrowserOptions}) { +module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, puppeteer}) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;