From 4e2d75d9f77fcc20e7a5a98e20436ed786d4266b Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 18 Sep 2020 15:52:14 -0700 Subject: [PATCH] test: migrate some helpers to fixtures, use testOutputDir (#3926) --- test/browsercontext-basic.spec.ts | 8 +- test/browsercontext-csp.spec.ts | 8 +- test/browsercontext-user-agent.spec.ts | 6 +- test/browsercontext-viewport.spec.ts | 10 +- test/browsertype-connect.spec.ts | 7 +- test/chromium/launcher.spec.ts | 7 +- test/chromium/tracing.spec.ts | 11 +- test/click.spec.ts | 9 +- test/defaultbrowsercontext-1.spec.ts | 7 +- test/defaultbrowsercontext-2.spec.ts | 55 ++--- test/dispatchevent.spec.ts | 4 +- test/download.spec.ts | 43 ++-- test/downloads-path.spec.ts | 30 +-- test/elementhandle-content-frame.spec.ts | 14 +- test/elementhandle-convenience.spec.ts | 9 +- test/elementhandle-owner-frame.spec.ts | 13 +- test/elementhandle-screenshot.spec.ts | 23 +-- test/emulation-focus.spec.ts | 7 +- test/frame-evaluate.spec.ts | 13 +- test/frame-frame-element.spec.ts | 14 +- test/frame-goto.spec.ts | 10 +- test/frame-hierarchy.spec.ts | 34 +++- test/headful.spec.ts | 17 +- test/keyboard.spec.ts | 5 +- test/launcher.spec.ts | 6 +- test/network-request.spec.ts | 5 +- test/page-emulate-media.spec.ts | 5 +- test/page-event-request.spec.ts | 5 +- test/page-goto.spec.ts | 9 +- test/page-screenshot.spec.ts | 19 +- test/page-wait-for-navigation.spec.ts | 8 +- test/pdf.spec.ts | 5 +- test/playwright.fixtures.ts | 106 ++++++++-- test/selectors-register.spec.ts | 15 +- test/slowmo.spec.ts | 3 +- test/utils.js | 245 ----------------------- test/wait-for-selector-1.spec.ts | 13 +- test/wait-for-selector-2.spec.ts | 11 +- 38 files changed, 298 insertions(+), 521 deletions(-) delete mode 100644 test/utils.js diff --git a/test/browsercontext-basic.spec.ts b/test/browsercontext-basic.spec.ts index 4c43c25752..3efd814a77 100644 --- a/test/browsercontext-basic.spec.ts +++ b/test/browsercontext-basic.spec.ts @@ -15,9 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, verifyViewport } from './playwright.fixtures'; it('should create new context', async function({browser}) { expect(browser.contexts().length).toBe(0); @@ -90,7 +88,7 @@ it('should isolate localStorage and cookies', async function({browser, server}) it('should propagate default viewport to the page', async ({ browser }) => { const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); const page = await context.newPage(); - await utils.verifyViewport(page, 456, 789); + await verifyViewport(page, 456, 789); await context.close(); }); @@ -99,7 +97,7 @@ it('should make a copy of default viewport', async ({ browser }) => { const context = await browser.newContext({ viewport }); viewport.width = 567; const page = await context.newPage(); - await utils.verifyViewport(page, 456, 789); + await verifyViewport(page, 456, 789); await context.close(); }); diff --git a/test/browsercontext-csp.spec.ts b/test/browsercontext-csp.spec.ts index 5ca1d68a80..8e1efbac3f 100644 --- a/test/browsercontext-csp.spec.ts +++ b/test/browsercontext-csp.spec.ts @@ -14,9 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import * as utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should bypass CSP meta tag', async ({browser, server}) => { // Make sure CSP prohibits addScriptTag. @@ -83,7 +81,7 @@ it('should bypass CSP in iframes as well', async ({browser, server}) => { const context = await browser.newContext(); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + const frame = await attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); await frame.addScriptTag({content: 'window["__injected"] = 42;'}).catch(e => void e); expect(await frame.evaluate('window["__injected"]')).toBe(undefined); await context.close(); @@ -94,7 +92,7 @@ it('should bypass CSP in iframes as well', async ({browser, server}) => { const context = await browser.newContext({ bypassCSP: true }); const page = await context.newPage(); await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); + const frame = await attachFrame(page, 'frame1', server.PREFIX + '/csp.html'); await frame.addScriptTag({content: 'window["__injected"] = 42;'}).catch(e => void e); expect(await frame.evaluate('window["__injected"]')).toBe(42); await context.close(); diff --git a/test/browsercontext-user-agent.spec.ts b/test/browsercontext-user-agent.spec.ts index 8e6d531ffd..83cffcc930 100644 --- a/test/browsercontext-user-agent.spec.ts +++ b/test/browsercontext-user-agent.spec.ts @@ -14,9 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should work', async ({browser, server}) => { { @@ -49,7 +47,7 @@ it('should work for subframes', async ({browser, server}) => { const page = await context.newPage(); const [request] = await Promise.all([ server.waitForRequest('/empty.html'), - utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), + attachFrame(page, 'frame1', server.EMPTY_PAGE), ]); expect(request.headers['user-agent']).toBe('foobar'); await context.close(); diff --git a/test/browsercontext-viewport.spec.ts b/test/browsercontext-viewport.spec.ts index 4bd37a5da3..6e6231d7a7 100644 --- a/test/browsercontext-viewport.spec.ts +++ b/test/browsercontext-viewport.spec.ts @@ -14,18 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, verifyViewport } from './playwright.fixtures'; it('should get the proper default viewport size', async ({page, server}) => { - await utils.verifyViewport(page, 1280, 720); + await verifyViewport(page, 1280, 720); }); it('should set the proper viewport size', async ({page, server}) => { - await utils.verifyViewport(page, 1280, 720); + await verifyViewport(page, 1280, 720); await page.setViewportSize({width: 123, height: 456}); - await utils.verifyViewport(page, 123, 456); + await verifyViewport(page, 123, 456); }); it('should return correct outerWidth and outerHeight', async ({page}) => { diff --git a/test/browsertype-connect.spec.ts b/test/browsertype-connect.spec.ts index ff9668ad60..03fcdb9085 100644 --- a/test/browsertype-connect.spec.ts +++ b/test/browsertype-connect.spec.ts @@ -16,7 +16,6 @@ */ import { options } from './playwright.fixtures'; -import utils from './utils'; import { serverFixtures } from './remoteServer.fixture'; const { it, expect, describe } = serverFixtures; @@ -175,13 +174,13 @@ describe('connect', suite => { } }); // Register one engine before connecting. - await utils.registerEngine(playwright, 'mycss1', mycss); + await playwright.selectors.register('mycss1', mycss); const browser1 = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() }); const context1 = await browser1.newContext(); // Register another engine after creating context. - await utils.registerEngine(playwright, 'mycss2', mycss); + await playwright.selectors.register('mycss2', mycss); const page1 = await context1.newPage(); await page1.setContent(`
hello
`); @@ -192,7 +191,7 @@ describe('connect', suite => { const browser2 = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() }); // Register third engine after second connect. - await utils.registerEngine(playwright, 'mycss3', mycss); + await playwright.selectors.register('mycss3', mycss); const page2 = await browser2.newPage(); await page2.setContent(`
hello
`); diff --git a/test/chromium/launcher.spec.ts b/test/chromium/launcher.spec.ts index 1e55925c34..3af5bb2485 100644 --- a/test/chromium/launcher.spec.ts +++ b/test/chromium/launcher.spec.ts @@ -16,9 +16,7 @@ import { it, expect, options } from '../playwright.fixtures'; import path from 'path'; -import utils from '../utils'; import type { ChromiumBrowser, ChromiumBrowserContext } from '../..'; -const { makeUserDataDir, removeUserDataDir } = utils; it('should throw with remote-debugging-pipe argument', (test, parameters) => { test.skip(options.WIRE || !options.CHROMIUM(parameters)); @@ -58,8 +56,8 @@ it('should open devtools when "devtools: true" option is given', (test, paramete it('should return background pages', (test, parameters) => { test.skip(!options.CHROMIUM(parameters)); -}, async ({browserType, defaultBrowserOptions}) => { - const userDataDir = await makeUserDataDir(); +}, async ({browserType, defaultBrowserOptions, createUserDataDir}) => { + const userDataDir = await createUserDataDir(); const extensionPath = path.join(__dirname, '..', 'assets', 'simple-extension'); const extensionOptions = {...defaultBrowserOptions, headless: false, @@ -77,7 +75,6 @@ it('should return background pages', (test, parameters) => { expect(context.backgroundPages()).toContain(backgroundPage); expect(context.pages()).not.toContain(backgroundPage); await context.close(); - await removeUserDataDir(userDataDir); }); it('should not create pages automatically', (test, parameters) => { diff --git a/test/chromium/tracing.spec.ts b/test/chromium/tracing.spec.ts index 53749f6bda..39e3224485 100644 --- a/test/chromium/tracing.spec.ts +++ b/test/chromium/tracing.spec.ts @@ -26,11 +26,8 @@ const fixtures = playwrightFixtures.declareTestFixtures(); const { it, expect, describe, defineTestFixture } = fixtures; -defineTestFixture('outputTraceFile', async ({tmpDir}, test) => { - const outputTraceFile = path.join(tmpDir, `trace.json`); - await test(outputTraceFile); - if (fs.existsSync(outputTraceFile)) - fs.unlinkSync(outputTraceFile); +defineTestFixture('outputTraceFile', async ({testOutputDir}, test) => { + await test(path.join(testOutputDir, `trace.json`)); }); describe('oopif', (suite, parameters) => { @@ -43,8 +40,8 @@ describe('oopif', (suite, parameters) => { expect(fs.existsSync(outputTraceFile)).toBe(true); }); - it('should create directories as needed', async ({browser, page, server, tmpDir}) => { - const filePath = path.join(tmpDir, 'these', 'are', 'directories'); + it('should create directories as needed', async ({browser, page, server, testOutputDir}) => { + const filePath = path.join(testOutputDir, 'these', 'are', 'directories', 'trace.json'); await (browser as ChromiumBrowser).startTracing(page, {screenshots: true, path: filePath}); await page.goto(server.PREFIX + '/grid.html'); await (browser as ChromiumBrowser).stopTracing(); diff --git a/test/click.spec.ts b/test/click.spec.ts index 5a4a554305..2bb80d33ec 100644 --- a/test/click.spec.ts +++ b/test/click.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, options, attachFrame } from './playwright.fixtures'; async function giveItAChanceToClick(page) { for (let i = 0; i < 5; i++) @@ -315,7 +314,7 @@ it('should click links which cause navigation', async ({page, server}) => { 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'); + await attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); const frame = page.frames()[1]; const button = await frame.$('button'); await button.click(); @@ -331,7 +330,7 @@ it('should click the button with fixed position inside an iframe', (test, parame await page.goto(server.EMPTY_PAGE); await page.setViewportSize({width: 500, height: 500}); await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); + await attachFrame(page, 'button-test', server.CROSS_PROCESS_PREFIX + '/input/button.html'); const frame = page.frames()[1]; await frame.$eval('button', button => button.style.setProperty('position', 'fixed')); await frame.click('button'); @@ -343,7 +342,7 @@ it('should click the button with deviceScaleFactor set', async ({browser, server const page = await context.newPage(); expect(await page.evaluate(() => window.devicePixelRatio)).toBe(5); await page.setContent('
spacer
'); - await utils.attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); + await attachFrame(page, 'button-test', server.PREFIX + '/input/button.html'); const frame = page.frames()[1]; const button = await frame.$('button'); await button.click(); diff --git a/test/defaultbrowsercontext-1.spec.ts b/test/defaultbrowsercontext-1.spec.ts index 712e1a92cc..18538d9d5c 100644 --- a/test/defaultbrowsercontext-1.spec.ts +++ b/test/defaultbrowsercontext-1.spec.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; +import { it, expect, verifyViewport } from './playwright.fixtures'; import fs from 'fs'; -import utils from './utils'; it('context.cookies() should work', async ({server, launchPersistent}) => { const {page} = await launchPersistent(); @@ -119,9 +118,9 @@ it('should(not) block third party cookies', async ({server, launchPersistent, is it('should support viewport option', async ({launchPersistent}) => { const {page, context} = await launchPersistent({viewport: { width: 456, height: 789 }}); - await utils.verifyViewport(page, 456, 789); + await verifyViewport(page, 456, 789); const page2 = await context.newPage(); - await utils.verifyViewport(page2, 456, 789); + await verifyViewport(page2, 456, 789); }); it('should support deviceScaleFactor option', async ({launchPersistent}) => { diff --git a/test/defaultbrowsercontext-2.spec.ts b/test/defaultbrowsercontext-2.spec.ts index 8005e1fe37..1fea3b1030 100644 --- a/test/defaultbrowsercontext-2.spec.ts +++ b/test/defaultbrowsercontext-2.spec.ts @@ -17,8 +17,6 @@ import { it, expect, options } from './playwright.fixtures'; import fs from 'fs'; -import utils from './utils'; -const { removeUserDataDir, makeUserDataDir } = utils; it('should support hasTouch option', async ({server, launchPersistent}) => { const {page} = await launchPersistent({hasTouch: true}); @@ -81,20 +79,18 @@ it('should support extraHTTPHeaders option', (test, parameters) => { it('should accept userDataDir', (test, parameters) => { test.flaky(options.CHROMIUM(parameters)); -}, async ({launchPersistent, tmpDir}) => { - const {context} = await launchPersistent(); - // Note: we need an open page to make sure its functional. - expect(fs.readdirSync(tmpDir).length).toBeGreaterThan(0); +}, async ({createUserDataDir, browserType, defaultBrowserOptions}) => { + const userDataDir = await createUserDataDir(); + const context = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); await context.close(); - expect(fs.readdirSync(tmpDir).length).toBeGreaterThan(0); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(tmpDir); + expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); }); it('should restore state from userDataDir', (test, parameters) => { test.slow(); -}, async ({browserType, defaultBrowserOptions, server, launchPersistent}) => { - const userDataDir = await makeUserDataDir(); +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir}) => { + const userDataDir = await createUserDataDir(); const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); const page = await browserContext.newPage(); await page.goto(server.EMPTY_PAGE); @@ -107,23 +103,19 @@ it('should restore state from userDataDir', (test, parameters) => { expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); await browserContext2.close(); - const userDataDir2 = await makeUserDataDir(); + const userDataDir2 = await createUserDataDir(); const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); const page3 = await browserContext3.newPage(); await page3.goto(server.EMPTY_PAGE); expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello'); await browserContext3.close(); - - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - await removeUserDataDir(userDataDir2); }); it('should restore cookies from userDataDir', (test, parameters) => { test.slow(); test.flaky(options.CHROMIUM(parameters)); -}, async ({browserType, defaultBrowserOptions, server, launchPersistent}) => { - const userDataDir = await makeUserDataDir(); +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir}) => { + const userDataDir = await createUserDataDir(); const browserContext = await browserType.launchPersistentContext(userDataDir, defaultBrowserOptions); const page = await browserContext.newPage(); await page.goto(server.EMPTY_PAGE); @@ -140,16 +132,12 @@ it('should restore cookies from userDataDir', (test, parameters) => { expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); await browserContext2.close(); - const userDataDir2 = await makeUserDataDir(); + const userDataDir2 = await createUserDataDir(); const browserContext3 = await browserType.launchPersistentContext(userDataDir2, defaultBrowserOptions); const page3 = await browserContext3.newPage(); await page3.goto(server.EMPTY_PAGE); expect(await page3.evaluate(() => document.cookie)).not.toBe('doSomethingOnlyOnce=true'); await browserContext3.close(); - - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); - await removeUserDataDir(userDataDir2); }); it('should have default URL when launching browser', async ({launchPersistent}) => { @@ -160,22 +148,23 @@ it('should have default URL when launching browser', async ({launchPersistent}) it('should throw if page argument is passed', (test, parameters) => { test.skip(options.FIREFOX(parameters)); -}, async ({browserType, defaultBrowserOptions, server, tmpDir}) => { +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir}) => { const options = {...defaultBrowserOptions, args: [server.EMPTY_PAGE] }; - const error = await browserType.launchPersistentContext(tmpDir, options).catch(e => e); + const error = await browserType.launchPersistentContext(await createUserDataDir(), options).catch(e => e); expect(error.message).toContain('can not specify page'); }); it('should have passed URL when launching with ignoreDefaultArgs: true', (test, parameters) => { test.skip(options.WIRE); -}, async ({browserType, defaultBrowserOptions, server, tmpDir, toImpl}) => { - const args = toImpl(browserType)._defaultArgs(defaultBrowserOptions, 'persistent', tmpDir, 0).filter(a => a !== 'about:blank'); +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir, toImpl}) => { + const userDataDir = await createUserDataDir(); + const args = toImpl(browserType)._defaultArgs(defaultBrowserOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank'); const options = { ...defaultBrowserOptions, args: [...args, server.EMPTY_PAGE], ignoreDefaultArgs: true, }; - const browserContext = await browserType.launchPersistentContext(tmpDir, options); + const browserContext = await browserType.launchPersistentContext(userDataDir, options); if (!browserContext.pages().length) await browserContext.waitForEvent('page'); await browserContext.pages()[0].waitForLoadState(); @@ -186,18 +175,18 @@ it('should have passed URL when launching with ignoreDefaultArgs: true', (test, it('should handle timeout', (test, parameters) => { test.skip(options.WIRE); -}, async ({browserType, defaultBrowserOptions, tmpDir}) => { +}, async ({browserType, defaultBrowserOptions, createUserDataDir}) => { const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; - const error = await browserType.launchPersistentContext(tmpDir, options).catch(e => e); + const error = await browserType.launchPersistentContext(await createUserDataDir(), options).catch(e => e); expect(error.message).toContain(`browserType.launchPersistentContext: Timeout 5000ms exceeded.`); }); it('should handle exception', (test, parameters) => { test.skip(options.WIRE); -}, async ({browserType, defaultBrowserOptions, tmpDir}) => { +}, async ({browserType, defaultBrowserOptions, createUserDataDir}) => { const e = new Error('Dummy'); const options = { ...defaultBrowserOptions, __testHookBeforeCreateBrowser: () => { throw e; } }; - const error = await browserType.launchPersistentContext(tmpDir, options).catch(e => e); + const error = await browserType.launchPersistentContext(await createUserDataDir(), options).catch(e => e); expect(error.message).toContain('Dummy'); }); @@ -240,7 +229,7 @@ it('should respect selectors', async ({playwright, launchPersistent}) => { return Array.from(root.querySelectorAll(selector)); } }); - await utils.registerEngine(playwright, 'defaultContextCSS', defaultContextCSS); + await playwright.selectors.register('defaultContextCSS', defaultContextCSS); await page.setContent(`
hello
`); expect(await page.innerHTML('css=div')).toBe('hello'); diff --git a/test/dispatchevent.spec.ts b/test/dispatchevent.spec.ts index 4b0aa66819..544d8a1b96 100644 --- a/test/dispatchevent.spec.ts +++ b/test/dispatchevent.spec.ts @@ -16,8 +16,6 @@ import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; - it('should dispatch click event', async ({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); await page.dispatchEvent('button', 'click'); @@ -121,7 +119,7 @@ it('should be atomic', async ({playwright, page}) => { return result; } }); - await utils.registerEngine(playwright, 'dispatchEvent', createDummySelector); + await playwright.selectors.register('dispatchEvent', createDummySelector); await page.setContent(`
Hello
`); await page.dispatchEvent('dispatchEvent=div', 'click'); expect(await page.evaluate(() => window['_clicked'])).toBe(true); diff --git a/test/download.spec.ts b/test/download.spec.ts index 33f54546f9..039aa7237d 100644 --- a/test/download.spec.ts +++ b/test/download.spec.ts @@ -62,28 +62,28 @@ it('should report downloads with acceptDownloads: true', async ({browser, server await page.close(); }); -it('should save to user-specified path', async ({tmpDir, browser, server}) => { +it('should save to user-specified path', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); await download.saveAs(userPath); expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); await page.close(); }); -it('should save to user-specified path without updating original path', async ({tmpDir, browser, server}) => { +it('should save to user-specified path without updating original path', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); await download.saveAs(userPath); expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); @@ -94,50 +94,51 @@ it('should save to user-specified path without updating original path', async ({ await page.close(); }); -it('should save to two different paths with multiple saveAs calls', async ({tmpDir, browser, server}) => { +it('should save to two different paths with multiple saveAs calls', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); await download.saveAs(userPath); expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); - const anotherUserPath = path.join(tmpDir, 'download (2).txt'); + const anotherUserPath = path.join(testOutputDir, 'download (2).txt'); await download.saveAs(anotherUserPath); expect(fs.existsSync(anotherUserPath)).toBeTruthy(); expect(fs.readFileSync(anotherUserPath).toString()).toBe('Hello world'); await page.close(); }); -it('should save to overwritten filepath', async ({tmpDir, browser, server}) => { +it('should save to overwritten filepath', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const dir = path.join(testOutputDir, 'downloads'); + const userPath = path.join(dir, 'download.txt'); await download.saveAs(userPath); - expect((await util.promisify(fs.readdir)(tmpDir)).length).toBe(1); + expect((await util.promisify(fs.readdir)(dir)).length).toBe(1); await download.saveAs(userPath); - expect((await util.promisify(fs.readdir)(tmpDir)).length).toBe(1); + expect((await util.promisify(fs.readdir)(dir)).length).toBe(1); expect(fs.existsSync(userPath)).toBeTruthy(); expect(fs.readFileSync(userPath).toString()).toBe('Hello world'); await page.close(); }); -it('should create subdirectories when saving to non-existent user-specified path', async ({tmpDir, browser, server}) => { +it('should create subdirectories when saving to non-existent user-specified path', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const nestedPath = path.join(tmpDir, 'these', 'are', 'directories', 'download.txt'); + const nestedPath = path.join(testOutputDir, 'these', 'are', 'directories', 'download.txt'); await download.saveAs(nestedPath); expect(fs.existsSync(nestedPath)).toBeTruthy(); expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world'); @@ -146,7 +147,7 @@ it('should create subdirectories when saving to non-existent user-specified path it('should save when connected remotely', (test, parameters) => { test.skip(options.WIRE); -}, async ({tmpDir, server, browserType, remoteServer}) => { +}, async ({testOutputDir, server, browserType, remoteServer}) => { const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() }); const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); @@ -154,7 +155,7 @@ it('should save when connected remotely', (test, parameters) => { page.waitForEvent('download'), page.click('a') ]); - const nestedPath = path.join(tmpDir, 'these', 'are', 'directories', 'download.txt'); + const nestedPath = path.join(testOutputDir, 'these', 'are', 'directories', 'download.txt'); await download.saveAs(nestedPath); expect(fs.existsSync(nestedPath)).toBeTruthy(); expect(fs.readFileSync(nestedPath).toString()).toBe('Hello world'); @@ -163,27 +164,27 @@ it('should save when connected remotely', (test, parameters) => { await browser.close(); }); -it('should error when saving with downloads disabled', async ({tmpDir, browser, server}) => { +it('should error when saving with downloads disabled', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: false }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); const { message } = await download.saveAs(userPath).catch(e => e); expect(message).toContain('Pass { acceptDownloads: true } when you are creating your browser context'); await page.close(); }); -it('should error when saving after deletion', async ({tmpDir, browser, server}) => { +it('should error when saving after deletion', async ({testOutputDir, browser, server}) => { const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); await download.delete(); const { message } = await download.saveAs(userPath).catch(e => e); expect(message).toContain('Download already deleted. Save before deleting.'); @@ -192,7 +193,7 @@ it('should error when saving after deletion', async ({tmpDir, browser, server}) it('should error when saving after deletion when connected remotely', (test, parameters) => { test.skip(options.WIRE); -}, async ({tmpDir, server, browserType, remoteServer}) => { +}, async ({testOutputDir, server, browserType, remoteServer}) => { const browser = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() }); const page = await browser.newPage({ acceptDownloads: true }); await page.setContent(`download`); @@ -200,7 +201,7 @@ it('should error when saving after deletion when connected remotely', (test, par page.waitForEvent('download'), page.click('a') ]); - const userPath = path.join(tmpDir, 'download.txt'); + const userPath = path.join(testOutputDir, 'download.txt'); await download.delete(); const { message } = await download.saveAs(userPath).catch(e => e); expect(message).toContain('Download already deleted. Save before deleting.'); diff --git a/test/downloads-path.spec.ts b/test/downloads-path.spec.ts index 83c4119444..1198c365ec 100644 --- a/test/downloads-path.spec.ts +++ b/test/downloads-path.spec.ts @@ -16,10 +16,7 @@ import { playwrightFixtures } from './playwright.fixtures'; -import path from 'path'; import fs from 'fs'; -import os from 'os'; -import {mkdtempAsync, removeFolderAsync} from './utils'; import type { Browser, BrowserContext } from '..'; type TestState = { @@ -29,7 +26,7 @@ type TestState = { const fixtures = playwrightFixtures.declareTestFixtures(); const { it, expect, defineTestFixture } = fixtures; -defineTestFixture('downloadsBrowser', async ({server, browserType, defaultBrowserOptions, tmpDir}, test) => { +defineTestFixture('downloadsBrowser', async ({server, browserType, defaultBrowserOptions, testOutputDir}, test) => { server.setRoute('/download', (req, res) => { res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); @@ -37,35 +34,30 @@ defineTestFixture('downloadsBrowser', async ({server, browserType, defaultBrowse }); const browser = await browserType.launch({ ...defaultBrowserOptions, - downloadsPath: tmpDir, + downloadsPath: testOutputDir, }); await test(browser); await browser.close(); }); -defineTestFixture('persistentDownloadsContext', async ({server, browserType, defaultBrowserOptions, tmpDir}, test) => { - const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright-test-')); +defineTestFixture('persistentDownloadsContext', async ({server, launchPersistent, testOutputDir}, test) => { server.setRoute('/download', (req, res) => { res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); res.end(`Hello world`); }); - const context = await browserType.launchPersistentContext( - userDataDir, + const { context, page } = await launchPersistent( { - ...defaultBrowserOptions, - downloadsPath: tmpDir, + downloadsPath: testOutputDir, acceptDownloads: true } ); - const page = context.pages()[0]; page.setContent(`download`); await test(context); await context.close(); - await removeFolderAsync(userDataDir); }); -it('should keep downloadsPath folder', async ({downloadsBrowser, tmpDir, server}) => { +it('should keep downloadsPath folder', async ({downloadsBrowser, testOutputDir, server}) => { const page = await downloadsBrowser.newPage(); await page.setContent(`download`); const [ download ] = await Promise.all([ @@ -77,7 +69,7 @@ it('should keep downloadsPath folder', async ({downloadsBrowser, tmpDir, server} await download.path().catch(e => void 0); await page.close(); await downloadsBrowser.close(); - expect(fs.existsSync(tmpDir)).toBeTruthy(); + expect(fs.existsSync(testOutputDir)).toBeTruthy(); }); it('should delete downloads when context closes', async ({downloadsBrowser, server}) => { @@ -94,7 +86,7 @@ it('should delete downloads when context closes', async ({downloadsBrowser, serv }); -it('should report downloads in downloadsPath folder', async ({downloadsBrowser, tmpDir, server}) => { +it('should report downloads in downloadsPath folder', async ({downloadsBrowser, testOutputDir, server}) => { const page = await downloadsBrowser.newPage({ acceptDownloads: true }); await page.setContent(`download`); const [ download ] = await Promise.all([ @@ -102,11 +94,11 @@ it('should report downloads in downloadsPath folder', async ({downloadsBrowser, page.click('a') ]); const path = await download.path(); - expect(path.startsWith(tmpDir)).toBeTruthy(); + expect(path.startsWith(testOutputDir)).toBeTruthy(); await page.close(); }); -it('should accept downloads', async ({persistentDownloadsContext, tmpDir, server}) => { +it('should accept downloads', async ({persistentDownloadsContext, testOutputDir, server}) => { const page = persistentDownloadsContext.pages()[0]; const [ download ] = await Promise.all([ page.waitForEvent('download'), @@ -115,7 +107,7 @@ it('should accept downloads', async ({persistentDownloadsContext, tmpDir, server expect(download.url()).toBe(`${server.PREFIX}/download`); expect(download.suggestedFilename()).toBe(`file.txt`); const path = await download.path(); - expect(path.startsWith(tmpDir)).toBeTruthy(); + expect(path.startsWith(testOutputDir)).toBeTruthy(); }); it('should not delete downloads when the context closes', async ({persistentDownloadsContext}) => { diff --git a/test/elementhandle-content-frame.spec.ts b/test/elementhandle-content-frame.spec.ts index e2d427d54c..c09c952836 100644 --- a/test/elementhandle-content-frame.spec.ts +++ b/test/elementhandle-content-frame.spec.ts @@ -15,13 +15,11 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should work', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const elementHandle = await page.$('#frame1'); const frame = await elementHandle.contentFrame(); expect(frame).toBe(page.frames()[1]); @@ -29,7 +27,7 @@ it('should work', async ({ page, server }) => { it('should work for cross-process iframes', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); + await attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); const elementHandle = await page.$('#frame1'); const frame = await elementHandle.contentFrame(); expect(frame).toBe(page.frames()[1]); @@ -37,7 +35,7 @@ it('should work for cross-process iframes', async ({ page, server }) => { it('should work for cross-frame evaluations', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => window.top.document.querySelector('#frame1')); expect(await elementHandle.contentFrame()).toBe(frame); @@ -45,7 +43,7 @@ it('should work for cross-frame evaluations', async ({ page, server }) => { it('should return null for non-iframes', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => document.body); expect(await elementHandle.contentFrame()).toBe(null); @@ -53,7 +51,7 @@ it('should return null for non-iframes', async ({ page, server }) => { it('should return null for document.documentElement', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => document.documentElement); expect(await elementHandle.contentFrame()).toBe(null); diff --git a/test/elementhandle-convenience.spec.ts b/test/elementhandle-convenience.spec.ts index bd18ea17b0..7d1c01a1b3 100644 --- a/test/elementhandle-convenience.spec.ts +++ b/test/elementhandle-convenience.spec.ts @@ -16,7 +16,6 @@ */ import { it, expect } from './playwright.fixtures'; -import utils from './utils'; it('should have a nice preview', async ({ page, server }) => { await page.goto(`${server.PREFIX}/dom.html`); @@ -86,7 +85,7 @@ it('textContent should be atomic', async ({ playwright, page }) => { return result; } }); - await utils.registerEngine(playwright, 'textContent', createDummySelector); + await playwright.selectors.register('textContent', createDummySelector); await page.setContent(`
Hello
`); const tc = await page.textContent('textContent=div'); expect(tc).toBe('Hello'); @@ -109,7 +108,7 @@ it('innerText should be atomic', async ({ playwright, page }) => { return result; } }); - await utils.registerEngine(playwright, 'innerText', createDummySelector); + await playwright.selectors.register('innerText', createDummySelector); await page.setContent(`
Hello
`); const tc = await page.innerText('innerText=div'); expect(tc).toBe('Hello'); @@ -132,7 +131,7 @@ it('innerHTML should be atomic', async ({ playwright, page }) => { return result; } }); - await utils.registerEngine(playwright, 'innerHTML', createDummySelector); + await playwright.selectors.register('innerHTML', createDummySelector); await page.setContent(`
Helloworld
`); const tc = await page.innerHTML('innerHTML=div'); expect(tc).toBe('Helloworld'); @@ -155,7 +154,7 @@ it('getAttribute should be atomic', async ({ playwright, page }) => { return result; } }); - await utils.registerEngine(playwright, 'getAttribute', createDummySelector); + await playwright.selectors.register('getAttribute', createDummySelector); await page.setContent(`
`); const tc = await page.getAttribute('getAttribute=div', 'foo'); expect(tc).toBe('hello'); diff --git a/test/elementhandle-owner-frame.spec.ts b/test/elementhandle-owner-frame.spec.ts index f07f3ffd05..31a7241f55 100644 --- a/test/elementhandle-owner-frame.spec.ts +++ b/test/elementhandle-owner-frame.spec.ts @@ -15,12 +15,11 @@ * limitations under the License. */ -import utils from './utils'; -import { it, expect, options } from './playwright.fixtures'; +import { it, expect, options, attachFrame } from './playwright.fixtures'; it('should work', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => document.body); expect(await elementHandle.ownerFrame()).toBe(frame); @@ -28,7 +27,7 @@ it('should work', async ({ page, server }) => { it('should work for cross-process iframes', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); + await attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => document.body); expect(await elementHandle.ownerFrame()).toBe(frame); @@ -38,7 +37,7 @@ it('should work for document', (test, parameters) => { test.flaky(options.WIN(parameters) && options.WEBKIT(parameters)); }, async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; const elementHandle = await frame.evaluateHandle(() => document); expect(await elementHandle.ownerFrame()).toBe(frame); @@ -46,7 +45,7 @@ it('should work for document', (test, parameters) => { it('should work for iframe elements', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.mainFrame(); const elementHandle = await frame.evaluateHandle(() => document.querySelector('#frame1')); expect(await elementHandle.ownerFrame()).toBe(frame); @@ -54,7 +53,7 @@ it('should work for iframe elements', async ({ page, server }) => { it('should work for cross-frame evaluations', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.mainFrame(); const elementHandle = await frame.evaluateHandle(() => document.querySelector('iframe').contentWindow.document.body); expect(await elementHandle.ownerFrame()).toBe(frame.childFrames()[0]); diff --git a/test/elementhandle-screenshot.spec.ts b/test/elementhandle-screenshot.spec.ts index f29324c22a..e516356ebc 100644 --- a/test/elementhandle-screenshot.spec.ts +++ b/test/elementhandle-screenshot.spec.ts @@ -15,9 +15,8 @@ * limitations under the License. */ -import { it, expect, describe, options } from './playwright.fixtures'; +import { it, expect, describe, options, verifyViewport } from './playwright.fixtures'; -import utils from './utils'; import {PNG} from 'pngjs'; import path from 'path'; import fs from 'fs'; @@ -77,7 +76,7 @@ describe('element screenshot', (suite, parameters) => { const screenshots = await Promise.all(promises); expect(screenshots[2]).toMatchImage(golden('screenshot-element-larger-than-viewport.png')); - await utils.verifyViewport(page, 500, 500); + await verifyViewport(page, 500, 500); }); it('should capture full element when larger than viewport', async ({page, golden}) => { @@ -104,7 +103,7 @@ describe('element screenshot', (suite, parameters) => { const screenshot = await elementHandle.screenshot(); expect(screenshot).toMatchImage(golden('screenshot-element-larger-than-viewport.png')); - await utils.verifyViewport(page, 500, 500); + await verifyViewport(page, 500, 500); }); it('should scroll element into view', async ({page, golden}) => { @@ -282,10 +281,10 @@ describe('element screenshot', (suite, parameters) => { it('should restore default viewport after fullPage screenshot', async ({ browser }) => { const context = await browser.newContext({ viewport: { width: 456, height: 789 } }); const page = await context.newPage(); - await utils.verifyViewport(page, 456, 789); + await verifyViewport(page, 456, 789); const screenshot = await page.screenshot({ fullPage: true }); expect(screenshot).toBeInstanceOf(Buffer); - await utils.verifyViewport(page, 456, 789); + await verifyViewport(page, 456, 789); await context.close(); }); @@ -298,7 +297,7 @@ describe('element screenshot', (suite, parameters) => { const __testHookBeforeScreenshot = () => { throw new Error('oh my'); }; const error = await page.screenshot({ fullPage: true, __testHookBeforeScreenshot } as any).catch(e => e); expect(error.message).toContain('oh my'); - await utils.verifyViewport(page, 350, 360); + await verifyViewport(page, 350, 360); await context.close(); }); @@ -311,10 +310,10 @@ describe('element screenshot', (suite, parameters) => { const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000)); const error = await page.screenshot({ fullPage: true, __testHookAfterScreenshot, timeout: 3000 } as any).catch(e => e); expect(error.message).toContain('page.screenshot: Timeout 3000ms exceeded'); - await utils.verifyViewport(page, 350, 360); + await verifyViewport(page, 350, 360); await page.setViewportSize({ width: 400, height: 400 }); await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport. - await utils.verifyViewport(page, 400, 400); + await verifyViewport(page, 400, 400); await context.close(); }); @@ -358,7 +357,7 @@ describe('element screenshot', (suite, parameters) => { const __testHookBeforeScreenshot = () => { throw new Error('oh my'); }; const error = await elementHandle.screenshot({ __testHookBeforeScreenshot } as any).catch(e => e); expect(error.message).toContain('oh my'); - await utils.verifyViewport(page, 350, 360); + await verifyViewport(page, 350, 360); await context.close(); }); @@ -384,12 +383,12 @@ describe('element screenshot', (suite, parameters) => { expect(screenshot).toBeInstanceOf(Buffer); }); - it('path option should create subdirectories', async ({page, server, golden, tmpDir}) => { + it('path option should create subdirectories', async ({page, server, golden, testOutputDir}) => { await page.setViewportSize({width: 500, height: 500}); await page.goto(server.PREFIX + '/grid.html'); await page.evaluate(() => window.scrollBy(50, 100)); const elementHandle = await page.$('.box:nth-of-type(3)'); - const outputPath = path.join(tmpDir, 'these', 'are', 'directories', 'screenshot.png'); + const outputPath = path.join(testOutputDir, 'these', 'are', 'directories', 'screenshot.png'); await elementHandle.screenshot({path: outputPath}); expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-element-bounding-box.png')); }); diff --git a/test/emulation-focus.spec.ts b/test/emulation-focus.spec.ts index b814c533d5..b3319e38d4 100644 --- a/test/emulation-focus.spec.ts +++ b/test/emulation-focus.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, options, attachFrame } from './playwright.fixtures'; it('should think that it is focused by default', async ({page}) => { expect(await page.evaluate('document.hasFocus()')).toBe(true); @@ -127,8 +126,8 @@ it('should not affect screenshots', (test, parameters) => { it('should change focused iframe', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); const [frame1, frame2] = await Promise.all([ - utils.attachFrame(page, 'frame1', server.PREFIX + '/input/textarea.html'), - utils.attachFrame(page, 'frame2', server.PREFIX + '/input/textarea.html'), + attachFrame(page, 'frame1', server.PREFIX + '/input/textarea.html'), + attachFrame(page, 'frame2', server.PREFIX + '/input/textarea.html'), ]); function logger() { self['_events'] = []; diff --git a/test/frame-evaluate.spec.ts b/test/frame-evaluate.spec.ts index 37f2ab22e4..a0d68130cc 100644 --- a/test/frame-evaluate.spec.ts +++ b/test/frame-evaluate.spec.ts @@ -15,14 +15,13 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; +import { it, expect, options, attachFrame, detachFrame } from './playwright.fixtures'; -import utils from './utils'; import type { Frame } from '../src/client/frame'; it('should have different execution contexts', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); expect(page.frames().length).toBe(2); await page.frames()[0].evaluate(() => window['FOO'] = 'foo'); await page.frames()[1].evaluate(() => window['FOO'] = 'bar'); @@ -99,15 +98,15 @@ it('should allow cross-frame element handles', async ({ page, server }) => { it('should not allow cross-frame element handles when frames do not script each other', async ({ page, server }) => { await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); + const frame = await attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); const bodyHandle = await frame.$('body'); const error = await page.evaluate(body => body.innerHTML, bodyHandle).catch(e => e); expect(error.message).toContain('Unable to adopt element handle from a different document'); }); it('should throw for detached frames', async ({page, server}) => { - const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await utils.detachFrame(page, 'frame1'); + const frame1 = await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await detachFrame(page, 'frame1'); let error = null; await frame1.evaluate(() => 7 * 8).catch(e => error = e); expect(error.message).toContain('Execution Context is not available in detached frame'); @@ -115,7 +114,7 @@ it('should throw for detached frames', async ({page, server}) => { it('should be isolated between frames', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); expect(page.frames().length).toBe(2); const [frame1, frame2] = page.frames(); expect(frame1 !== frame2).toBeTruthy(); diff --git a/test/frame-frame-element.spec.ts b/test/frame-frame-element.spec.ts index a237ebdc09..335451cc3d 100644 --- a/test/frame-frame-element.spec.ts +++ b/test/frame-frame-element.spec.ts @@ -15,15 +15,13 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should work', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); - const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); - const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE); + const frame1 = await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame2', server.EMPTY_PAGE); + const frame3 = await attachFrame(page, 'frame3', server.EMPTY_PAGE); const frame1handle1 = await page.$('#frame1'); const frame1handle2 = await frame1.frameElement(); const frame3handle1 = await page.$('#frame3'); @@ -35,7 +33,7 @@ it('should work', async ({page, server}) => { it('should work with contentFrame', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); - const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame = await attachFrame(page, 'frame1', server.EMPTY_PAGE); const handle = await frame.frameElement(); const contentFrame = await handle.contentFrame(); expect(contentFrame).toBe(frame); @@ -43,7 +41,7 @@ it('should work with contentFrame', async ({page, server}) => { it('should throw when detached', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); - const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame1 = await attachFrame(page, 'frame1', server.EMPTY_PAGE); await page.$eval('#frame1', e => e.remove()); const error = await frame1.frameElement().catch(e => e); expect(error.message).toContain('Frame has been detached.'); diff --git a/test/frame-goto.spec.ts b/test/frame-goto.spec.ts index d48a9b271d..530a3558ef 100644 --- a/test/frame-goto.spec.ts +++ b/test/frame-goto.spec.ts @@ -15,9 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should navigate subframes', async ({page, server}) => { await page.goto(server.PREFIX + '/frames/one-frame.html'); @@ -53,9 +51,9 @@ it('should return matching responses', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); // Attach three frames. const frames = [ - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE), - await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE), + await attachFrame(page, 'frame1', server.EMPTY_PAGE), + await attachFrame(page, 'frame2', server.EMPTY_PAGE), + await attachFrame(page, 'frame3', server.EMPTY_PAGE), ]; const serverResponses = []; server.setRoute('/0.html', (req, res) => serverResponses.push(res)); diff --git a/test/frame-hierarchy.spec.ts b/test/frame-hierarchy.spec.ts index f10434ccb0..cc0ccbf442 100644 --- a/test/frame-hierarchy.spec.ts +++ b/test/frame-hierarchy.spec.ts @@ -15,12 +15,28 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, options, attachFrame, detachFrame } from './playwright.fixtures'; +import type { Frame } from '../index'; + +function dumpFrames(frame: Frame, indentation: string = ''): string[] { + let description = frame.url().replace(/:\d{4}\//, ':/'); + if (frame.name()) + description += ' (' + frame.name() + ')'; + const result = [indentation + description]; + const childFrames = frame.childFrames(); + childFrames.sort((a, b) => { + if (a.url() !== b.url()) + return a.url() < b.url() ? -1 : 1; + return a.name() < b.name() ? -1 : 1; + }); + for (const child of childFrames) + result.push(...dumpFrames(child, ' ' + indentation)); + return result; +} it('should handle nested frames', async ({page, server}) => { await page.goto(server.PREFIX + '/frames/nested-frames.html'); - expect(utils.dumpFrames(page.mainFrame())).toEqual([ + expect(dumpFrames(page.mainFrame())).toEqual([ 'http://localhost:/frames/nested-frames.html', ' http://localhost:/frames/frame.html (aframe)', ' http://localhost:/frames/two-frames.html (2frames)', @@ -34,7 +50,7 @@ it('should send events when frames are manipulated dynamically', async ({page, s // validate frameattached events const attachedFrames = []; page.on('frameattached', frame => attachedFrames.push(frame)); - await utils.attachFrame(page, 'frame1', './assets/frame.html'); + await attachFrame(page, 'frame1', './assets/frame.html'); expect(attachedFrames.length).toBe(1); expect(attachedFrames[0].url()).toContain('/assets/frame.html'); @@ -52,7 +68,7 @@ it('should send events when frames are manipulated dynamically', async ({page, s // validate framedetached events const detachedFrames = []; page.on('framedetached', frame => detachedFrames.push(frame)); - await utils.detachFrame(page, 'frame1'); + await detachFrame(page, 'frame1'); expect(detachedFrames.length).toBe(1); expect(detachedFrames[0].isDetached()).toBe(true); }); @@ -136,7 +152,7 @@ it('should report frame from-inside shadow DOM', async ({page, server}) => { }); it('should report frame.name()', async ({page, server}) => { - await utils.attachFrame(page, 'theFrameId', server.EMPTY_PAGE); + await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); await page.evaluate(url => { const frame = document.createElement('iframe'); frame.name = 'theFrameName'; @@ -150,15 +166,15 @@ it('should report frame.name()', async ({page, server}) => { }); it('should report frame.parent()', async ({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame2', server.EMPTY_PAGE); expect(page.frames()[0].parentFrame()).toBe(null); expect(page.frames()[1].parentFrame()).toBe(page.mainFrame()); expect(page.frames()[2].parentFrame()).toBe(page.mainFrame()); }); it('should report different frame instance when frame re-attaches', async ({page, server}) => { - const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + const frame1 = await attachFrame(page, 'frame1', server.EMPTY_PAGE); await page.evaluate(() => { window['frame'] = document.querySelector('#frame1'); window['frame'].remove(); diff --git a/test/headful.spec.ts b/test/headful.spec.ts index 0180f28c13..b8b06ac05b 100644 --- a/test/headful.spec.ts +++ b/test/headful.spec.ts @@ -16,11 +16,8 @@ import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; -const { makeUserDataDir, removeUserDataDir } = utils; - -it('should have default url when launching browser', async ({browserType, defaultBrowserOptions, tmpDir}) => { - const browserContext = await browserType.launchPersistentContext(tmpDir, {...defaultBrowserOptions, headless: false }); +it('should have default url when launching browser', async ({browserType, defaultBrowserOptions, createUserDataDir}) => { + const browserContext = await browserType.launchPersistentContext(await createUserDataDir(), {...defaultBrowserOptions, headless: false }); const urls = browserContext.pages().map(page => page.url()); expect(urls).toEqual(['about:blank']); await browserContext.close(); @@ -30,9 +27,9 @@ it('headless should be able to read cookies written by headful', (test, paramete test.fail(options.WIN(parameters) && options.CHROMIUM(parameters)); test.flaky(options.FIREFOX(parameters)); test.slow(); -}, async ({browserType, defaultBrowserOptions, server}) => { +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir}) => { // see https://github.com/microsoft/playwright/issues/717 - const userDataDir = await makeUserDataDir(); + const userDataDir = await createUserDataDir(); // Write a cookie in headful chrome const headfulContext = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, headless: false}); const headfulPage = await headfulContext.newPage(); @@ -45,15 +42,13 @@ it('headless should be able to read cookies written by headful', (test, paramete await headlessPage.goto(server.EMPTY_PAGE); const cookie = await headlessPage.evaluate(() => document.cookie); await headlessContext.close(); - // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await removeUserDataDir(userDataDir); expect(cookie).toBe('foo=true'); }); it('should close browser with beforeunload page', (test, parameters) => { test.slow(); -}, async ({browserType, defaultBrowserOptions, server, tmpDir}) => { - const browserContext = await browserType.launchPersistentContext(tmpDir, {...defaultBrowserOptions, headless: false}); +}, async ({browserType, defaultBrowserOptions, server, createUserDataDir}) => { + const browserContext = await browserType.launchPersistentContext(await createUserDataDir(), {...defaultBrowserOptions, headless: false}); const page = await browserContext.newPage(); await page.goto(server.PREFIX + '/beforeunload.html'); // We have to interact with a page so that 'beforeunload' handlers diff --git a/test/keyboard.spec.ts b/test/keyboard.spec.ts index cd537fa099..9932c61e50 100644 --- a/test/keyboard.spec.ts +++ b/test/keyboard.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, options, attachFrame } from './playwright.fixtures'; it('should type into a textarea', async ({page}) => { await page.evaluate(() => { @@ -305,7 +304,7 @@ it('should type emoji', async ({page, server}) => { it('should type emoji into an iframe', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html'); + await attachFrame(page, 'emoji-test', server.PREFIX + '/input/textarea.html'); const frame = page.frames()[1]; const textarea = await frame.$('textarea'); await textarea.type('👹 Tokyo street Japan 🇯🇵'); diff --git a/test/launcher.spec.ts b/test/launcher.spec.ts index a640781bcb..115255afe1 100644 --- a/test/launcher.spec.ts +++ b/test/launcher.spec.ts @@ -16,16 +16,14 @@ */ import { it, expect } from './playwright.fixtures'; -import path from 'path'; -import utils from './utils'; it('should require top-level Errors', async ({}) => { - const Errors = require(path.join(utils.projectRoot(), '/lib/utils/errors.js')); + const Errors = require('../lib/utils/errors.js'); expect(String(Errors.TimeoutError)).toContain('TimeoutError'); }); it('should require top-level DeviceDescriptors', async ({playwright}) => { - const Devices = require(path.join(utils.projectRoot(), '/lib/server/deviceDescriptors.js')).DeviceDescriptors; + const Devices = require('../lib/server/deviceDescriptors.js').DeviceDescriptors; expect(Devices['iPhone 6']).toBeTruthy(); expect(Devices['iPhone 6']).toEqual(playwright.devices['iPhone 6']); }); diff --git a/test/network-request.spec.ts b/test/network-request.spec.ts index 0f3068000c..02147bfad0 100644 --- a/test/network-request.spec.ts +++ b/test/network-request.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, options, attachFrame } from './playwright.fixtures'; it('should work for main frame navigation request', async ({page, server}) => { const requests = []; @@ -30,7 +29,7 @@ it('should work for subframe navigation request', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); const requests = []; page.on('request', request => requests.push(request)); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); expect(requests.length).toBe(1); expect(requests[0].frame()).toBe(page.frames()[1]); }); diff --git a/test/page-emulate-media.spec.ts b/test/page-emulate-media.spec.ts index f706aa8072..96599b8870 100644 --- a/test/page-emulate-media.spec.ts +++ b/test/page-emulate-media.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should emulate type', async ({page, server}) => { expect(await page.evaluate(() => matchMedia('screen').matches)).toBe(true); @@ -110,7 +109,7 @@ it('should work in popup', async ({browser, server}) => { it('should work in cross-process iframe', async ({browser, server}) => { const page = await browser.newPage({ colorScheme: 'dark' }); await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); + await attachFrame(page, 'frame1', server.CROSS_PROCESS_PREFIX + '/empty.html'); const frame = page.frames()[1]; expect(await frame.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches)).toBe(true); await page.close(); diff --git a/test/page-event-request.spec.ts b/test/page-event-request.spec.ts index 08fed55224..06e728c88d 100644 --- a/test/page-event-request.spec.ts +++ b/test/page-event-request.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, attachFrame } from './playwright.fixtures'; it('should fire for navigation requests', async ({page, server}) => { const requests = []; @@ -29,7 +28,7 @@ it('should fire for iframes', async ({page, server}) => { const requests = []; page.on('request', request => requests.push(request)); await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); expect(requests.length).toBe(2); }); diff --git a/test/page-goto.spec.ts b/test/page-goto.spec.ts index 83e8d1637d..0cb38c97a5 100644 --- a/test/page-goto.spec.ts +++ b/test/page-goto.spec.ts @@ -17,7 +17,6 @@ import { it, expect } from './playwright.fixtures'; -import utils from './utils'; import path from 'path'; import url from 'url'; @@ -174,7 +173,7 @@ it('should fail when navigating to bad url', async ({page, isChromium, isWebKit} expect(error.message).toContain('Invalid url'); }); -it('should fail when navigating to bad SSL', async ({page, httpsServer, browserName}) => { +it('should fail when navigating to bad SSL', async ({page, httpsServer, expectedSSLError}) => { // Make sure that network events do not emit 'undefined'. // @see https://crbug.com/750469 page.on('request', request => expect(request).toBeTruthy()); @@ -182,15 +181,15 @@ it('should fail when navigating to bad SSL', async ({page, httpsServer, browserN page.on('requestfailed', request => expect(request).toBeTruthy()); let error = null; await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); - utils.expectSSLError(browserName, error.message); + expect(error.message).toContain(expectedSSLError); }); -it('should fail when navigating to bad SSL after redirects', async ({page, server, httpsServer, browserName}) => { +it('should fail when navigating to bad SSL after redirects', async ({page, server, httpsServer, expectedSSLError}) => { server.setRedirect('/redirect/1.html', '/redirect/2.html'); server.setRedirect('/redirect/2.html', '/empty.html'); let error = null; await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); - utils.expectSSLError(browserName, error.message); + expect(error.message).toContain(expectedSSLError); }); it('should not crash when navigating to bad SSL after a cross origin navigation', async ({page, server, httpsServer}) => { diff --git a/test/page-screenshot.spec.ts b/test/page-screenshot.spec.ts index ded991cd53..8f38f7e166 100644 --- a/test/page-screenshot.spec.ts +++ b/test/page-screenshot.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect, describe, options } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, describe, options, verifyViewport } from './playwright.fixtures'; import path from 'path'; import fs from 'fs'; @@ -122,7 +121,7 @@ describe('page screenshot', (suite, parameters) => { await page.goto(server.PREFIX + '/grid.html'); const screenshot = await page.screenshot({ fullPage: true }); expect(screenshot).toBeInstanceOf(Buffer); - await utils.verifyViewport(page, 500, 500); + await verifyViewport(page, 500, 500); }); it('should run in parallel in multiple pages', async ({server, context, golden}) => { @@ -262,32 +261,32 @@ describe('page screenshot', (suite, parameters) => { expect(await page.screenshot()).toMatchImage(golden('screenshot-iframe.png')); }); - it('path option should work', async ({page, server, golden, tmpDir}) => { + it('path option should work', async ({page, server, golden, testOutputDir}) => { await page.setViewportSize({width: 500, height: 500}); await page.goto(server.PREFIX + '/grid.html'); - const outputPath = path.join(tmpDir, 'screenshot.png'); + const outputPath = path.join(testOutputDir, 'screenshot.png'); await page.screenshot({path: outputPath}); expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png')); }); - it('path option should create subdirectories', async ({page, server, golden, tmpDir}) => { + it('path option should create subdirectories', async ({page, server, golden, testOutputDir}) => { await page.setViewportSize({width: 500, height: 500}); await page.goto(server.PREFIX + '/grid.html'); - const outputPath = path.join(tmpDir, 'these', 'are', 'directories', 'screenshot.png'); + const outputPath = path.join(testOutputDir, 'these', 'are', 'directories', 'screenshot.png'); await page.screenshot({path: outputPath}); expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('screenshot-sanity.png')); }); - it('path option should detect jpeg', async ({page, server, golden, tmpDir}) => { + it('path option should detect jpeg', async ({page, server, golden, testOutputDir}) => { await page.setViewportSize({ width: 100, height: 100 }); await page.goto(server.EMPTY_PAGE); - const outputPath = path.join(tmpDir, 'screenshot.jpg'); + const outputPath = path.join(testOutputDir, 'screenshot.jpg'); const screenshot = await page.screenshot({omitBackground: true, path: outputPath}); expect(await fs.promises.readFile(outputPath)).toMatchImage(golden('white.jpg')); expect(screenshot).toMatchImage(golden('white.jpg')); }); - it('path option should throw for unsupported mime type', async ({page, server, golden, tmpDir}) => { + it('path option should throw for unsupported mime type', async ({page}) => { const error = await page.screenshot({ path: 'file.txt' }).catch(e => e); expect(error.message).toContain('path: unsupported mime type "text/plain"'); }); diff --git a/test/page-wait-for-navigation.spec.ts b/test/page-wait-for-navigation.spec.ts index 1f9eca9abf..a7abd2c717 100644 --- a/test/page-wait-for-navigation.spec.ts +++ b/test/page-wait-for-navigation.spec.ts @@ -16,9 +16,7 @@ */ import { it, expect } from './playwright.fixtures'; - -import utils from './utils'; -import type { Frame } from '..'; +import type { Frame } from '../index'; it('should work', async ({page, server}) => { await page.goto(server.EMPTY_PAGE); @@ -72,14 +70,14 @@ it('should work with clicking on anchor links', async ({page, server}) => { expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar'); }); -it('should work with clicking on links which do not commit navigation', async ({page, server, httpsServer, browserName}) => { +it('should work with clicking on links which do not commit navigation', async ({page, server, httpsServer, expectedSSLError}) => { await page.goto(server.EMPTY_PAGE); await page.setContent(`foobar`); const [error] = await Promise.all([ page.waitForNavigation().catch(e => e), page.click('a'), ]); - utils.expectSSLError(browserName, error.message); + expect(error.message).toContain(expectedSSLError); }); it('should work with history.pushState()', async ({page, server}) => { diff --git a/test/pdf.spec.ts b/test/pdf.spec.ts index e2081c0254..fc267084c9 100644 --- a/test/pdf.spec.ts +++ b/test/pdf.spec.ts @@ -22,11 +22,10 @@ import path from 'path'; it('should be able to save file', (test, parameters) => { test.skip(!(options.HEADLESS && options.CHROMIUM(parameters)), 'Printing to pdf is currently only supported in headless chromium.'); -}, async ({page, tmpDir}) => { - const outputFile = path.join(tmpDir, 'output.pdf'); +}, async ({page, testOutputDir}) => { + const outputFile = path.join(testOutputDir, 'output.pdf'); await page.pdf({path: outputFile}); expect(fs.readFileSync(outputFile).byteLength).toBeGreaterThan(0); - fs.unlinkSync(outputFile); }); it('should only have pdf in chromium', (test, parameters) => { diff --git a/test/playwright.fixtures.ts b/test/playwright.fixtures.ts index 952b2fbf81..1891217e17 100644 --- a/test/playwright.fixtures.ts +++ b/test/playwright.fixtures.ts @@ -17,16 +17,19 @@ import fs from 'fs'; import path from 'path'; import os from 'os'; +import util from 'util'; import childProcess from 'child_process'; -import type { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServer, BrowserContextOptions } from '../index'; +import type { LaunchOptions, BrowserType, Browser, BrowserContext, Page, Frame, BrowserServer, BrowserContextOptions } from '../index'; import { TestServer } from '../utils/testserver'; import { Connection } from '../lib/client/connection'; import { Transport } from '../lib/protocol/transport'; import { installCoverageHooks } from './coverage'; -import { mkdtempAsync, removeFolderAsync } from './utils'; import { fixtures as baseFixtures } from '@playwright/test-runner'; import assert from 'assert'; +const mkdtempAsync = util.promisify(fs.mkdtemp); +const removeFolderAsync = util.promisify(require('rimraf')); + type PlaywrightParameters = { platform: 'win32' | 'linux' | 'darwin' browserName: string; @@ -48,21 +51,24 @@ type PlaywrightWorkerFixtures = { isWindows: boolean; isMac: boolean; isLinux: boolean; + expectedSSLError: string; }; -type PlaywrightFixtures = { +type PlaywrightTestFixtures = { context: BrowserContext; server: TestServer; page: Page; httpsServer: TestServer; browserServer: BrowserServer; + testOutputDir: string; + createUserDataDir: () => Promise; launchPersistent: (options?: Parameters['launchPersistentContext']>[1]) => Promise<{context: BrowserContext, page: Page}>; }; const fixtures = baseFixtures .declareParameters() .declareWorkerFixtures() - .declareTestFixtures(); + .declareTestFixtures(); const { defineTestFixture, defineWorkerFixture, defineParameter, generateParametrizedTests } = fixtures; export const playwrightFixtures = fixtures; @@ -236,12 +242,42 @@ defineWorkerFixture('golden', async ({browserName}, test) => { await test(p => path.join(browserName, p)); }); -defineTestFixture('context', async ({browser}, runTest, info) => { +defineWorkerFixture('expectedSSLError', async ({browserName, platform}, runTest) => { + let expectedSSLError: string; + if (browserName === 'chromium') { + expectedSSLError = 'net::ERR_CERT_AUTHORITY_INVALID'; + } else if (browserName === 'webkit') { + if (platform === 'darwin') + expectedSSLError = 'The certificate for this server is invalid'; + else if (platform === 'win32') + expectedSSLError = 'SSL peer certificate or SSH remote key was not OK'; + else + expectedSSLError = 'Unacceptable TLS certificate'; + } else { + expectedSSLError = 'SSL_ERROR_UNKNOWN'; + } + await runTest(expectedSSLError); +}); + +defineTestFixture('testOutputDir', async ({}, runTest, info) => { const { test, config } = info; const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, ''); const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_'); + const testOutputDir = path.join(config.outputDir, relativePath, sanitizedTitle); + await fs.promises.mkdir(testOutputDir, { recursive: true }); + await runTest(testOutputDir); + const files = await fs.promises.readdir(testOutputDir); + if (!files.length) { + // Do not leave an empty useless directory. + // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 + await removeFolderAsync(testOutputDir).catch(e => {}); + } +}); + +defineTestFixture('context', async ({browser, testOutputDir}, runTest, info) => { + const { config } = info; const contextOptions: BrowserContextOptions = { - relativeArtifactsPath: path.join(relativePath, sanitizedTitle), + relativeArtifactsPath: path.relative(config.outputDir, testOutputDir), recordTrace: !!options.TRACING, }; const context = await browser.newContext(contextOptions); @@ -249,24 +285,37 @@ defineTestFixture('context', async ({browser}, runTest, info) => { await context.close(); }); -defineTestFixture('page', async ({context}, runTest, info) => { +defineTestFixture('page', async ({context, testOutputDir}, runTest, info) => { const page = await context.newPage(); await runTest(page); - const { test, config, result } = info; - if (result.status === 'failed' || result.status === 'timedOut') { - const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, ''); - const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_'); - const assetPath = path.join(config.outputDir, relativePath, sanitizedTitle) + '-failed.png'; - await page.screenshot({ timeout: 5000, path: assetPath }); - } + const { result } = info; + if (result.status === 'failed' || result.status === 'timedOut') + await page.screenshot({ timeout: 5000, path: path.join(testOutputDir, 'test-failed.png') }); }); -defineTestFixture('launchPersistent', async ({tmpDir, defaultBrowserOptions, browserType}, test) => { +defineTestFixture('createUserDataDir', async ({testOutputDir}, runTest, info) => { + let counter = 0; + const dirs: string[] = []; + async function createUserDataDir() { + const dir = path.join(testOutputDir, `user-data-dir-${counter++}`); + dirs.push(dir); + await fs.promises.mkdir(dir, { recursive: true }); + return dir; + } + await runTest(createUserDataDir); + // Remove user data dirs, because we cannot upload them as test result artifacts. + // - Firefox removes lock file later, repsumably from another watchdog process? + // - WebKit has circular symlinks that makes CI go crazy. + await Promise.all(dirs.map(dir => removeFolderAsync(dir).catch(e => {}))); +}); + +defineTestFixture('launchPersistent', async ({createUserDataDir, defaultBrowserOptions, browserType}, test) => { let context; async function launchPersistent(options) { if (context) throw new Error('can only launch one persitent context'); - context = await browserType.launchPersistentContext(tmpDir, {...defaultBrowserOptions, ...options}); + const userDataDir = await createUserDataDir(); + context = await browserType.launchPersistentContext(userDataDir, {...defaultBrowserOptions, ...options}); const page = context.pages()[0]; return {context, page}; } @@ -296,3 +345,28 @@ function valueFromEnv(name, defaultValue) { return defaultValue; return JSON.parse(process.env[name]); } + +export async function attachFrame(page: Page, frameId: string, url: string): Promise { + const handle = await page.evaluateHandle(async ({ frameId, url }) => { + const frame = document.createElement('iframe'); + frame.src = url; + frame.id = frameId; + document.body.appendChild(frame); + await new Promise(x => frame.onload = x); + return frame; + }, { frameId, url }); + return handle.asElement().contentFrame(); +} + +export async function detachFrame(page: Page, frameId: string) { + await page.evaluate(frameId => { + document.getElementById(frameId).remove(); + }, frameId); +} + +export async function verifyViewport(page: Page, width: number, height: number) { + expect(page.viewportSize().width).toBe(width); + expect(page.viewportSize().height).toBe(height); + expect(await page.evaluate('window.innerWidth')).toBe(width); + expect(await page.evaluate('window.innerHeight')).toBe(height); +} diff --git a/test/selectors-register.spec.ts b/test/selectors-register.spec.ts index 2062d6d5ba..5ab9ee7bee 100644 --- a/test/selectors-register.spec.ts +++ b/test/selectors-register.spec.ts @@ -18,7 +18,6 @@ import { it, expect } from './playwright.fixtures'; import path from 'path'; -import utils from './utils'; it('should work', async ({playwright, browser}) => { const createTagSelector = () => ({ @@ -33,11 +32,11 @@ it('should work', async ({playwright, browser}) => { } }); // Register one engine before creating context. - await utils.registerEngine(playwright, 'tag', `(${createTagSelector.toString()})()`); + await playwright.selectors.register('tag', `(${createTagSelector.toString()})()`); const context = await browser.newContext(); // Register another engine after creating context. - await utils.registerEngine(playwright, 'tag2', `(${createTagSelector.toString()})()`); + await playwright.selectors.register('tag2', `(${createTagSelector.toString()})()`); const page = await context.newPage(); await page.setContent('
'); @@ -60,7 +59,7 @@ it('should work', async ({playwright, browser}) => { }); it('should work with path', async ({playwright, page}) => { - await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); + await playwright.selectors.register('foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); await page.setContent('
'); expect(await page.$eval('foo=whatever', e => e.nodeName)).toBe('SECTION'); }); @@ -75,8 +74,8 @@ it('should work in main and isolated world', async ({playwright, page}) => { return [document.body, document.documentElement, window['__answer']]; } }); - await utils.registerEngine(playwright, 'main', createDummySelector); - await utils.registerEngine(playwright, 'isolated', createDummySelector, { contentScript: true }); + await playwright.selectors.register('main', createDummySelector); + await playwright.selectors.register('isolated', createDummySelector, { contentScript: true }); await page.setContent('
'); await page.evaluate(() => window['__answer'] = document.querySelector('span')); // Works in main if asked. @@ -117,8 +116,8 @@ it('should handle errors', async ({playwright, page}) => { expect(error.message).toBe('Selector engine name may only contain [a-zA-Z0-9_] characters'); // Selector names are case-sensitive. - await utils.registerEngine(playwright, 'dummy', createDummySelector); - await utils.registerEngine(playwright, 'duMMy', createDummySelector); + await playwright.selectors.register('dummy', createDummySelector); + await playwright.selectors.register('duMMy', createDummySelector); error = await playwright.selectors.register('dummy', createDummySelector).catch(e => e); expect(error.message).toBe('"dummy" selector engine has been already registered'); diff --git a/test/slowmo.spec.ts b/test/slowmo.spec.ts index ceaeb92551..2300529a70 100644 --- a/test/slowmo.spec.ts +++ b/test/slowmo.spec.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { it, expect, describe, options } from './playwright.fixtures'; -import { attachFrame } from './utils'; +import { it, expect, describe, options, attachFrame } from './playwright.fixtures'; async function checkSlowMo(toImpl, page, task) { let didSlowMo = false; diff --git a/test/utils.js b/test/utils.js deleted file mode 100644 index cdc6a9c19b..0000000000 --- a/test/utils.js +++ /dev/null @@ -1,245 +0,0 @@ -/** - * Copyright 2017 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const os = require('os'); -const removeFolder = require('rimraf'); -const { fixtures } = require('@playwright/test-runner'); -const { expect } = fixtures; - -const {FlakinessDashboard} = require('../utils/flakiness-dashboard'); -const PROJECT_ROOT = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..'); - -let platform = os.platform(); - -const utils = module.exports = { - mkdtempAsync: util.promisify(fs.mkdtemp), - - removeFolderAsync: util.promisify(removeFolder), - - /** - * @return {string} - */ - projectRoot: function() { - return PROJECT_ROOT; - }, - - /** - * @param {!Page} page - * @param {string} frameId - * @param {string} url - * @return {!Playwright.Frame} - */ - attachFrame: async function(page, frameId, url) { - const handle = await page.evaluateHandle(async ({ frameId, url }) => { - const frame = document.createElement('iframe'); - frame.src = url; - frame.id = frameId; - document.body.appendChild(frame); - await new Promise(x => frame.onload = x); - return frame; - }, { frameId, url }); - return handle.asElement().contentFrame(); - }, - - /** - * @param {!Page} page - * @param {string} frameId - */ - detachFrame: async function(page, frameId) { - await page.evaluate(frameId => { - document.getElementById(frameId).remove(); - }, frameId); - }, - - /** - * @param {!Frame} frame - * @param {string=} indentation - * @return {Array} - */ - dumpFrames: function(frame, indentation) { - indentation = indentation || ''; - let description = frame.url().replace(/:\d{4}\//, ':/'); - if (frame.name()) - description += ' (' + frame.name() + ')'; - const result = [indentation + description]; - const childFrames = frame.childFrames(); - childFrames.sort((a, b) => { - if (a.url() !== b.url()) - return a.url() < b.url() ? -1 : 1; - return a.name() < b.name() ? -1 : 1; - }); - for (const child of childFrames) - result.push(...utils.dumpFrames(child, ' ' + indentation)); - return result; - }, - - verifyViewport: async (page, width, height) => { - expect(page.viewportSize().width).toBe(width); - expect(page.viewportSize().height).toBe(height); - expect(await page.evaluate('window.innerWidth')).toBe(width); - expect(await page.evaluate('window.innerHeight')).toBe(height); - }, - - registerEngine: async (playwright, name, script, options) => { - try { - await playwright.selectors.register(name, script, options); - } catch (e) { - if (!e.message.includes('has been already registered')) - throw e; - } - }, - - initializeFlakinessDashboardIfNeeded: async function(testRunner) { - // Generate testIDs for all tests and verify they don't clash. - // This will add |test.testId| for every test. - // - // NOTE: we do this on CI's so that problems arise on PR trybots. - if (process.env.CI) - generateTestIDs(testRunner); - // FLAKINESS_DASHBOARD_PASSWORD is an encrypted/secured variable. - // Encrypted variables get a special treatment in CI's when handling PRs so that - // secrets are not leaked to untrusted code. - // - AppVeyor DOES NOT decrypt secured variables for PRs - // - Travis DOES NOT decrypt encrypted variables for PRs - // - Cirrus CI DOES NOT decrypt encrypted variables for PRs *unless* PR is sent - // from someone who has WRITE ACCESS to the repo. - // - // Since we don't want to run flakiness dashboard for PRs on all CIs, we - // check existence of FLAKINESS_DASHBOARD_PASSWORD and absence of - // CIRRUS_BASE_SHA env variables. - if (!process.env.FLAKINESS_DASHBOARD_PASSWORD || process.env.CIRRUS_BASE_SHA) - return; - const {sha, timestamp} = await FlakinessDashboard.getCommitDetails(__dirname, 'HEAD'); - const dashboard = new FlakinessDashboard({ - commit: { - sha, - timestamp, - url: `https://github.com/Microsoft/playwright/commit/${sha}`, - }, - build: { - url: process.env.FLAKINESS_DASHBOARD_BUILD_URL, - }, - dashboardRepo: { - url: 'https://github.com/aslushnikov/playwright-flakiness-dashboard.git', - username: 'playwright-flakiness', - email: 'aslushnikov+playwrightflakiness@gmail.com', - password: process.env.FLAKINESS_DASHBOARD_PASSWORD, - branch: process.env.FLAKINESS_DASHBOARD_NAME, - }, - }); - - testRunner.on('testfinished', (test, parameters) => { - // Do not report tests from COVERAGE testsuite. - // They don't bring much value to us. - if (test.fullName.includes('**API COVERAGE**')) - return; - const testpath = test.location.filePath.substring(utils.projectRoot().length); - const url = `https://github.com/Microsoft/playwright/blob/${sha}/${testpath}#L${test.location.lineNumber}`; - dashboard.reportTestResult({ - testId: test.testId, - name: test.location().toString(), - description: test.fullName(), - url, - result: test.result, - }); - }); - testRunner.on('finished', async({result}) => { - dashboard.setBuildResult(result); - await dashboard.uploadAndCleanup(); - }); - - function generateTestIDs(testRunner) { - const testIds = new Map(); - for (const test of testRunner.tests()) { - const testIdComponents = [test.name]; - for (let suite = test.suite; !!suite.parentSuite; suite = suite.parentSuite) - testIdComponents.push(suite.name); - testIdComponents.reverse(); - const testId = testIdComponents.join('>'); - const clashingTest = testIds.get(testId); - if (clashingTest) - throw new Error(`Two tests with clashing IDs: ${test.location()} and ${clashingTest.location()}`); - testIds.set(testId, test); - test.testId = testId; - } - } - }, - - makeUserDataDir: async function() { - return await utils.mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_profile-')); - }, - - removeUserDataDir: async function(dir) { - await utils.removeFolderAsync(dir).catch(e => {}); - }, - - setPlatform(p) { - // To support isplaywrightready. - platform = p; - }, - - createTestLogger(dumpLogOnFailure = true, testRun = null, prefix = '') { - const colors = [31, 32, 33, 34, 35, 36, 37]; - let colorIndex = 0; - for (let i = 0; i < prefix.length; i++) - colorIndex += prefix.charCodeAt(i); - const color = colors[colorIndex % colors.length]; - prefix = prefix ? `\x1b[${color}m[${prefix}]\x1b[0m ` : ''; - - const logger = { - isEnabled: (name, severity) => { - return name.startsWith('browser') || dumpLogOnFailure; - }, - log: (name, severity, message, args) => { - if (!testRun) - return; - if (name.startsWith('browser')) { - if (severity === 'warning') - testRun.log(`${prefix}\x1b[31m[browser]\x1b[0m ${message}`) - else - testRun.log(`${prefix}\x1b[33m[browser]\x1b[0m ${message}`) - } else if (dumpLogOnFailure) { - testRun.log(`${prefix}\x1b[32m[${name}]\x1b[0m ${message}`) - } - }, - setTestRun(tr) { - if (testRun && testRun.ok()) - testRun.output().splice(0); - testRun = tr; - }, - }; - return logger; - }, - - expectSSLError(browserName, errorMessage) { - if (browserName === 'chromium') { - expect(errorMessage).toContain('net::ERR_CERT_AUTHORITY_INVALID'); - } else if (browserName === 'webkit') { - if (platform === 'darwin') - expect(errorMessage).toContain('The certificate for this server is invalid'); - else if (platform === 'win32') - expect(errorMessage).toContain('SSL peer certificate or SSH remote key was not OK'); - else - expect(errorMessage).toContain('Unacceptable TLS certificate'); - } else { - expect(errorMessage).toContain('SSL_ERROR_UNKNOWN'); - } - }, -}; diff --git a/test/wait-for-selector-1.spec.ts b/test/wait-for-selector-1.spec.ts index ffd5395b6d..ff0dc2a966 100644 --- a/test/wait-for-selector-1.spec.ts +++ b/test/wait-for-selector-1.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, attachFrame, detachFrame } from './playwright.fixtures'; async function giveItTimeToLog(frame) { await frame.evaluate(() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))); @@ -197,7 +196,7 @@ it('should work when node is added through innerHTML', async ({page, server}) => 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); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const otherFrame = page.frames()[1]; const watchdog = page.waitForSelector('div', { state: 'attached' }); await otherFrame.evaluate(addElement, 'div'); @@ -207,8 +206,8 @@ it('page.waitForSelector is shortcut for main frame', async ({page, server}) => }); 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); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame2', server.EMPTY_PAGE); const frame1 = page.frames()[1]; const frame2 = page.frames()[2]; const waitForSelectorPromise = frame2.waitForSelector('div', { state: 'attached' }); @@ -219,11 +218,11 @@ it('should run in specified frame', async ({page, server}) => { }); it('should throw when frame is detached', async ({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await 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 detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); expect(waitError.message).toContain('waitForFunction failed: frame got detached.'); diff --git a/test/wait-for-selector-2.spec.ts b/test/wait-for-selector-2.spec.ts index 52cc10baa4..8457998027 100644 --- a/test/wait-for-selector-2.spec.ts +++ b/test/wait-for-selector-2.spec.ts @@ -15,8 +15,7 @@ * limitations under the License. */ -import { it, expect } from './playwright.fixtures'; -import utils from './utils'; +import { it, expect, attachFrame, detachFrame } from './playwright.fixtures'; const addElement = tag => document.body.appendChild(document.createElement(tag)); @@ -217,8 +216,8 @@ it('should respect timeout xpath', async ({page, playwright}) => { }); it('should run in specified frame xpath', async ({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame2', server.EMPTY_PAGE); const frame1 = page.frames()[1]; const frame2 = page.frames()[2]; const waitForXPathPromise = frame2.waitForSelector('//div', { state: 'attached' }); @@ -229,11 +228,11 @@ it('should run in specified frame xpath', async ({page, server}) => { }); it('should throw when frame is detached xpath', async ({page, server}) => { - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + await attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame = page.frames()[1]; let waitError = null; const waitPromise = frame.waitForSelector('//*[@class="box"]').catch(e => waitError = e); - await utils.detachFrame(page, 'frame1'); + await detachFrame(page, 'frame1'); await waitPromise; expect(waitError).toBeTruthy(); expect(waitError.message).toContain('waitForFunction failed: frame got detached.');