diff --git a/docs/api.md b/docs/api.md index 9852adcd43..787c99101e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3470,15 +3470,6 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. This methods attaches Playwright to an existing browser instance. -#### browserType.defaultArgs([options]) -- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - - `headless` <[boolean]> Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the `devtools` option is `true`. - - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). - - `devtools` <[boolean]> **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be set `false`. -- returns: <[Array]<[string]>> - -The default flags that browser will be launched with. - #### browserType.devices - returns: <[Object]> @@ -3557,9 +3548,9 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'. > > See [`this article`](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) for a description of the differences between Chromium and Chrome. [`This article`](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) describes some differences for Linux users. -#### browserType.launchPersistent([options]) +#### browserType.launchPersistent(userDataDir, [options]) + - `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile). - `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - - `userDataDir` <[string]> Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for [Chromium](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile). - `headless` <[boolean]> Whether to run browser in headless mode. More details for [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the `devtools` option is `true`. - `executablePath` <[string]> Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to [current working directory](https://nodejs.org/api/process.html#process_process_cwd). **BEWARE**: Playwright is only [guaranteed to work](https://github.com/Microsoft/playwright/#q-why-doesnt-playwright-vxxx-work-with-chromium-vyyy) with the bundled Chromium, Firefox or WebKit, use at your own risk. - `args` <[Array]<[string]>> Additional arguments to pass to the browser instance. The list of Chromium flags can be found [here](http://peter.sh/experiments/chromium-command-line-switches/). diff --git a/src/server/browserType.ts b/src/server/browserType.ts index d530575169..b7a15387c2 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -42,8 +42,7 @@ export interface BrowserType { name(): string; launch(options?: LaunchOptions & { slowMo?: number }): Promise; launchServer(options?: LaunchOptions & { port?: number }): Promise; - launchPersistent(options?: LaunchOptions & { userDataDir: string }): Promise; - defaultArgs(options?: BrowserArgOptions): string[]; + launchPersistent(userDataDir: string, options?: LaunchOptions): Promise; connect(options: ConnectOptions): Promise; devices: types.Devices; errors: { TimeoutError: typeof TimeoutError }; diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 7cfdd2d4e1..84a8a02720 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -62,8 +62,8 @@ export class Chromium implements BrowserType { return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer; } - async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise { - const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir); + async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise { + const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir); const browser = await CRBrowser.connect(transport!); // Hack: for typical launch scenario, ensure that close waits for actual process termination. const browserContext = browser._defaultContext; @@ -84,27 +84,19 @@ export class Chromium implements BrowserType { timeout = 30000 } = options; - const chromeArguments = []; - if (!ignoreDefaultArgs) - chromeArguments.push(...this.defaultArgs(options)); - else if (Array.isArray(ignoreDefaultArgs)) - chromeArguments.push(...this.defaultArgs(options).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); - else - chromeArguments.push(...args); - - const userDataDirArg = chromeArguments.find(arg => arg.startsWith('--user-data-dir=')); - if (userDataDirArg) - throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument'); - if (chromeArguments.find(arg => arg.startsWith('--remote-debugging-'))) - throw new Error('Can\' use --remote-debugging-* args. Playwright manages remote debugging connection itself'); - let temporaryUserDataDir: string | null = null; if (!userDataDir) { userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH); - temporaryUserDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH); + temporaryUserDataDir = userDataDir!; } - chromeArguments.push(`--user-data-dir=${userDataDir}`); - chromeArguments.push(launchType === 'server' ? `--remote-debugging-port=${port || 0}` : '--remote-debugging-pipe'); + + const chromeArguments = []; + if (!ignoreDefaultArgs) + chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port || 0)); + else if (Array.isArray(ignoreDefaultArgs)) + chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir!, port || 0).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); + else + chromeArguments.push(...args); let chromeExecutable = executablePath; if (!executablePath) { @@ -113,7 +105,6 @@ export class Chromium implements BrowserType { throw new Error(missingText); chromeExecutable = executablePath; } - let browserServer: BrowserServer | undefined = undefined; const { launchedProcess, gracefullyClose } = await launchProcess({ executablePath: chromeExecutable!, @@ -172,13 +163,21 @@ export class Chromium implements BrowserType { return { TimeoutError }; } - defaultArgs(options: BrowserArgOptions = {}): string[] { + private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] { const { devtools = false, headless = !devtools, args = [], } = options; + const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); + if (userDataDirArg) + throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument'); + if (args.find(arg => arg.startsWith('--remote-debugging-'))) + throw new Error('Playwright manages remote debugging connection itself.'); + const chromeArguments = [...DEFAULT_ARGS]; + chromeArguments.push(`--user-data-dir=${userDataDir}`); + chromeArguments.push(launchType === 'server' ? `--remote-debugging-port=${port || 0}` : '--remote-debugging-pipe'); if (devtools) chromeArguments.push('--auto-open-devtools-for-tabs'); if (headless) { @@ -188,9 +187,10 @@ export class Chromium implements BrowserType { '--mute-audio' ); } + chromeArguments.push(...args); if (args.every(arg => arg.startsWith('-'))) chromeArguments.push('about:blank'); - chromeArguments.push(...args); + return chromeArguments; } diff --git a/src/server/firefox.ts b/src/server/firefox.ts index 6c43607d1f..532c553a8a 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -61,8 +61,8 @@ export class Firefox implements BrowserType { return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer; } - async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise { - const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir); + async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise { + const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir); const browser = await FFBrowser.connect(transport!); // Hack: for typical launch scenario, ensure that close waits for actual process termination. const browserContext = browser._defaultContext; @@ -84,27 +84,20 @@ export class Firefox implements BrowserType { } = options; const firefoxArguments = []; - if (!ignoreDefaultArgs) - firefoxArguments.push(...this.defaultArgs(options)); - else if (Array.isArray(ignoreDefaultArgs)) - firefoxArguments.push(...this.defaultArgs(options).filter(arg => !ignoreDefaultArgs.includes(arg))); - else - firefoxArguments.push(...args); - - const userDataDirArg = firefoxArguments.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); - if (userDataDirArg) - throw new Error('Pass userDataDir parameter instead of specifying -profile argument'); let temporaryProfileDir = null; if (!userDataDir) { - userDataDir = await createProfile(); - temporaryProfileDir = userDataDirArg; + userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); + temporaryProfileDir = userDataDir; } - firefoxArguments.unshift(`-profile`, userDataDir); + populateProfile(userDataDir!); - if (firefoxArguments.find(arg => arg.startsWith('-juggler'))) - throw new Error('Use the port parameter instead of -juggler argument'); - firefoxArguments.unshift('-juggler', String(port || 0)); + if (!ignoreDefaultArgs) + firefoxArguments.push(...this._defaultArgs(options, userDataDir!, port || 0)); + else if (Array.isArray(ignoreDefaultArgs)) + firefoxArguments.push(...this._defaultArgs(options, userDataDir!, port || 0).filter(arg => !ignoreDefaultArgs.includes(arg))); + else + firefoxArguments.push(...args); let firefoxExecutable = executablePath; if (!firefoxExecutable) { @@ -169,7 +162,7 @@ export class Firefox implements BrowserType { return { TimeoutError }; } - defaultArgs(options: BrowserArgOptions = {}): string[] { + private _defaultArgs(options: BrowserArgOptions = {}, userDataDir: string, port: number): string[] { const { devtools = false, headless = !devtools, @@ -177,12 +170,22 @@ export class Firefox implements BrowserType { } = options; if (devtools) throw new Error('Option "devtools" is not supported by Firefox'); - const firefoxArguments = [...DEFAULT_ARGS]; + const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); + if (userDataDirArg) + throw new Error('Pass userDataDir parameter instead of specifying -profile argument'); + if (args.find(arg => arg.startsWith('-juggler'))) + throw new Error('Use the port parameter instead of -juggler argument'); + + const firefoxArguments = ['-no-remote']; if (headless) firefoxArguments.push('-headless'); else firefoxArguments.push('-wait-for-browser'); + + firefoxArguments.push(`-profile`, userDataDir); + firefoxArguments.push('-juggler', String(port)); firefoxArguments.push(...args); + if (args.every(arg => arg.startsWith('-'))) firefoxArguments.push('about:blank'); return firefoxArguments; @@ -242,10 +245,6 @@ export class Firefox implements BrowserType { const mkdtempAsync = platform.promisify(fs.mkdtemp); const writeFileAsync = platform.promisify(fs.writeFile); -const DEFAULT_ARGS = [ - '-no-remote', -]; - const DUMMY_UMA_SERVER = 'dummy.test'; const DEFAULT_PREFERENCES = { // Make sure Shield doesn't hit the network. @@ -451,16 +450,13 @@ const DEFAULT_PREFERENCES = { 'toolkit.startup.max_resumed_crashes': -1, }; -async function createProfile(extraPrefs?: object): Promise { - const profilePath = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); +async function populateProfile(profilePath: string) { const prefsJS: string[] = []; const userJS: string[] = []; - const prefs = { ...DEFAULT_PREFERENCES, ...extraPrefs }; - for (const [key, value] of Object.entries(prefs)) + for (const [key, value] of Object.entries(DEFAULT_PREFERENCES)) userJS.push(`user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`); await writeFileAsync(path.join(profilePath, 'user.js'), userJS.join('\n')); await writeFileAsync(path.join(profilePath, 'prefs.js'), prefsJS.join('\n')); - return profilePath; } diff --git a/src/server/webkit.ts b/src/server/webkit.ts index dbb4ef5e54..dc4dfc3e77 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -53,7 +53,7 @@ export class WebKit implements BrowserType { } async launch(options?: LaunchOptions & { slowMo?: number }): Promise { - const { browserServer, transport } = await this._launchServer(options, 'local', null); + const { browserServer, transport } = await this._launchServer(options, 'local'); const browser = await WKBrowser.connect(transport!, options && options.slowMo); // Hack: for typical launch scenario, ensure that close waits for actual process termination. browser.close = () => browserServer.close(); @@ -65,8 +65,8 @@ export class WebKit implements BrowserType { return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer; } - async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise { - const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir); + async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise { + const { browserServer, transport } = await this._launchServer(options, 'persistent', userDataDir); const browser = await WKBrowser.connect(transport!); // Hack: for typical launch scenario, ensure that close waits for actual process termination. const browserContext = browser._defaultContext; @@ -86,24 +86,19 @@ export class WebKit implements BrowserType { handleSIGHUP = true, } = options; - const webkitArguments = []; - if (!ignoreDefaultArgs) - webkitArguments.push(...this.defaultArgs(options)); - else if (Array.isArray(ignoreDefaultArgs)) - webkitArguments.push(...this.defaultArgs(options).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); - else - webkitArguments.push(...args); - - const userDataDirArg = webkitArguments.find(arg => arg.startsWith('--user-data-dir=')); - if (userDataDirArg) - throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument'); - let temporaryUserDataDir: string | null = null; if (!userDataDir) { userDataDir = await mkdtempAsync(WEBKIT_PROFILE_PATH); temporaryUserDataDir = userDataDir!; } - webkitArguments.push(`--user-data-dir=${userDataDir}`); + + const webkitArguments = []; + if (!ignoreDefaultArgs) + webkitArguments.push(...this._defaultArgs(options, userDataDir!, port || 0)); + else if (Array.isArray(ignoreDefaultArgs)) + webkitArguments.push(...this._defaultArgs(options, userDataDir!, port || 0).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1)); + else + webkitArguments.push(...args); let webkitExecutable = executablePath; if (!executablePath) { @@ -162,7 +157,7 @@ export class WebKit implements BrowserType { return { TimeoutError }; } - defaultArgs(options: BrowserArgOptions = {}): string[] { + _defaultArgs(options: BrowserArgOptions = {}, userDataDir: string, port: number): string[] { const { devtools = false, headless = !devtools, @@ -170,9 +165,13 @@ export class WebKit implements BrowserType { } = options; if (devtools) throw new Error('Option "devtools" is not supported by WebKit'); + const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir=')); + if (userDataDirArg) + throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument'); const webkitArguments = ['--inspector-pipe']; if (headless) webkitArguments.push('--headless'); + webkitArguments.push(`--user-data-dir=${userDataDir}`); webkitArguments.push(...args); return webkitArguments; } diff --git a/test/defaultbrowsercontext.spec.js b/test/defaultbrowsercontext.spec.js index 61f139ec29..6c2bde3ef4 100644 --- a/test/defaultbrowsercontext.spec.js +++ b/test/defaultbrowsercontext.spec.js @@ -15,6 +15,8 @@ * limitations under the License. */ +const { makeUserDataDir, removeUserDataDir } = require('./utils'); + module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions, playwright }) { const {describe, xdescribe, fdescribe} = testRunner; const {it, fit, xit, dit} = testRunner; @@ -22,13 +24,15 @@ module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions, describe('launchPersistent()', function() { beforeEach(async state => { - state.browserContext = await playwright.launchPersistent(defaultBrowserOptions); + state.userDataDir = await makeUserDataDir(); + state.browserContext = await playwright.launchPersistent(state.userDataDir, defaultBrowserOptions); state.page = await state.browserContext.newPage(); }); afterEach(async state => { await state.browserContext.close(); delete state.browserContext; delete state.page; + await removeUserDataDir(state.userDataDir); }); it('context.cookies() should work', async({page, server}) => { await page.goto(server.EMPTY_PAGE); diff --git a/test/fixtures.spec.js b/test/fixtures.spec.js index f305a6a0f3..c2433bcca3 100644 --- a/test/fixtures.spec.js +++ b/test/fixtures.spec.js @@ -66,17 +66,18 @@ module.exports.describe = function({testRunner, expect, product, playwright, pla } describe('Fixtures', function() { - xit('should dump browser process stderr', async({server}) => { - const browserServer = await playwright.launchServer({ dumpio: true }); + it('should dump browser process stderr', async({server}) => { let dumpioData = ''; - browserServer.process().stdout.on('data', data => dumpioData += data.toString('utf8')); - browserServer.process().stderr.on('data', data => dumpioData += data.toString('utf8')); - const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() }); - const page = await browser.newPage(); - await page.goto(`data:text/html,`); - await new Promise(f => setTimeout(f, 1000)); - await page.close(); - await browserServer.close(); + const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product, 'usewebsocket']); + res.stderr.on('data', data => dumpioData += data.toString('utf8')); + await new Promise(resolve => res.on('close', resolve)); + expect(dumpioData).toContain('message from dumpio'); + }); + it('should dump browser process stderr', async({server}) => { + let dumpioData = ''; + const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product]); + res.stderr.on('data', data => dumpioData += data.toString('utf8')); + await new Promise(resolve => res.on('close', resolve)); expect(dumpioData).toContain('message from dumpio'); }); it('should close the browser when the node process closes', async () => { diff --git a/test/fixtures/dumpio.js b/test/fixtures/dumpio.js new file mode 100644 index 0000000000..1bb380233b --- /dev/null +++ b/test/fixtures/dumpio.js @@ -0,0 +1,21 @@ +(async() => { + process.on('unhandledRejection', error => { + // Catch various errors as we launch non-browser binary. + console.log('unhandledRejection', error.message); + }); + + const [, , playwrightRoot, product, useWebSocket] = process.argv; + const options = { + ignoreDefaultArgs: true, + dumpio: true, + timeout: 1, + executablePath: 'node', + args: ['-e', 'console.error("message from dumpio")', '--'] + } + console.error('using web socket: ' + options.webSocket); + try { + await require(playwrightRoot)[product.toLowerCase()].launchServer(options); + console.error('Browser launch unexpectedly succeeded.'); + } catch (e) { + } +})(); diff --git a/test/headful.spec.js b/test/headful.spec.js index b3d08fc66d..9bbc9c1f13 100644 --- a/test/headful.spec.js +++ b/test/headful.spec.js @@ -14,15 +14,7 @@ * limitations under the License. */ -const path = require('path'); -const os = require('os'); -const fs = require('fs'); -const util = require('util'); - -const rmAsync = util.promisify(require('rimraf')); -const mkdtempAsync = util.promisify(fs.mkdtemp); - -const TMP_FOLDER = path.join(os.tmpdir(), 'pw_tmp_folder-'); +const { makeUserDataDir, removeUserDataDir } = require('./utils'); module.exports.describe = function({testRunner, expect, playwright, defaultBrowserOptions, FFOX, CHROMIUM, WEBKIT, WIN}) { const {describe, xdescribe, fdescribe} = testRunner; @@ -38,38 +30,42 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows describe('Headful', function() { it('should have default url when launching browser', async function() { - const browserContext = await playwright.launchPersistent(headfulOptions); + const userDataDir = await makeUserDataDir(); + const browserContext = await playwright.launchPersistent(userDataDir, headfulOptions); const pages = (await browserContext.pages()).map(page => page.url()); expect(pages).toEqual(['about:blank']); await browserContext.close(); + await removeUserDataDir(userDataDir); }); // see https://github.com/microsoft/playwright/issues/717 it.skip((WIN && CHROMIUM) || FFOX)('headless should be able to read cookies written by headful', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); + const userDataDir = await makeUserDataDir(); // Write a cookie in headful chrome - const headfulContext = await playwright.launchPersistent(Object.assign({userDataDir}, headfulOptions)); + const headfulContext = await playwright.launchPersistent(userDataDir, headfulOptions); const headfulPage = await headfulContext.newPage(); await headfulPage.goto(server.EMPTY_PAGE); await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); await headfulContext.close(); // Read the cookie from headless chrome - const headlessContext = await playwright.launchPersistent(Object.assign({userDataDir}, headlessOptions)); + const headlessContext = await playwright.launchPersistent(userDataDir, headlessOptions); const headlessPage = await headlessContext.newPage(); 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 rmAsync(userDataDir).catch(e => {}); + await removeUserDataDir(userDataDir); expect(cookie).toBe('foo=true'); }); it.skip(FFOX)('should close browser with beforeunload page', async({server}) => { - const browserContext = await playwright.launchPersistent(headfulOptions); + const userDataDir = await makeUserDataDir(); + const browserContext = await playwright.launchPersistent(userDataDir, headfulOptions); const page = await browserContext.newPage(); await page.goto(server.PREFIX + '/beforeunload.html'); // We have to interact with a page so that 'beforeunload' handlers // fire. await page.click('body'); await browserContext.close(); + await removeUserDataDir(userDataDir); }); }); }; diff --git a/test/launcher.spec.js b/test/launcher.spec.js index baa79306bb..909404334c 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -14,16 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const fs = require('fs'); -const os = require('os'); + const path = require('path'); -const util = require('util'); - +const fs = require('fs'); const utils = require('./utils'); -const rmAsync = util.promisify(require('rimraf')); -const mkdtempAsync = util.promisify(fs.mkdtemp); - -const TMP_FOLDER = path.join(os.tmpdir(), 'pw_tmp_folder-'); +const { makeUserDataDir, removeUserDataDir } = require('./utils'); module.exports.describe = function({testRunner, expect, defaultBrowserOptions, playwright, playwrightPath, product, CHROMIUM, FFOX, WEBKIT, WIN}) { const {describe, xdescribe, fdescribe} = testRunner; @@ -47,16 +42,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p await playwright.launch(options).catch(e => waitError = e); expect(waitError.message).toContain('Failed to launch'); }); - it('should have default URL when launching browser', async function() { - const browserContext = await playwright.launchPersistent(defaultBrowserOptions); + if('should have default URL when launching browser', async function() { + const userDataDir = await makeUserDataDir(); + const browserContext = await playwright.launchPersistent(userDataDir, defaultBrowserOptions); const pages = (await browserContext.pages()).map(page => page.url()); expect(pages).toEqual(['about:blank']); await browserContext.close(); + await removeUserDataDir(userDataDir); }); it('should have custom URL when launching browser', async function({server}) { + const userDataDir = await makeUserDataDir(); const options = Object.assign({}, defaultBrowserOptions); options.args = [server.EMPTY_PAGE].concat(options.args || []); - const browserContext = await playwright.launchPersistent(options); + const browserContext = await playwright.launchPersistent(userDataDir, options); const pages = await browserContext.pages(); expect(pages.length).toBe(1); const page = pages[0]; @@ -65,11 +63,14 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p } expect(page.url()).toBe(server.EMPTY_PAGE); await browserContext.close(); + await removeUserDataDir(userDataDir); }); it('should return child_process instance', async () => { - const browserServer = await playwright.launchServer(defaultBrowserOptions); + const userDataDir = await makeUserDataDir(); + const browserServer = await playwright.launchServer(userDataDir, defaultBrowserOptions); expect(browserServer.process().pid).toBeGreaterThan(0); await browserServer.close(); + await removeUserDataDir(userDataDir); }); }); @@ -93,27 +94,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p throw new Error('Unknown browser'); }); }); - - describe('Playwright.defaultArguments', () => { - it('should return the default arguments', async() => { - if (CHROMIUM) - expect(playwright.defaultArgs()).toContain('--no-first-run'); - expect(playwright.defaultArgs()).toContain(FFOX ? '-headless' : '--headless'); - expect(playwright.defaultArgs({headless: false})).not.toContain(FFOX ? '-headless' : '--headless'); - }); - it('should filter out ignored default arguments', async() => { - const defaultArgsWithoutUserDataDir = playwright.defaultArgs(defaultBrowserOptions); - const defaultArgsWithUserDataDir = playwright.defaultArgs({...defaultBrowserOptions, userDataDir: 'fake-profile'}); - const browserServer = await playwright.launchServer(Object.assign({}, defaultBrowserOptions, { - userDataDir: 'fake-profile', - // Filter out any of the args added by the fake profile - ignoreDefaultArgs: defaultArgsWithUserDataDir.filter(x => !defaultArgsWithoutUserDataDir.includes(x)) - })); - const spawnargs = browserServer.process().spawnargs; - expect(spawnargs.some(x => x.includes('fake-profile'))).toBe(false); - await browserServer.close(); - }); - }); }); describe('Top-level requires', function() { @@ -264,65 +244,67 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p describe('Playwright.launchPersistent', function() { it('userDataDir option', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browserContext = await playwright.launchPersistent(options); + const userDataDir = await makeUserDataDir(); + const options = Object.assign(defaultBrowserOptions); + const browserContext = await playwright.launchPersistent(userDataDir, options); // Open a page to make sure its functional. await browserContext.newPage(); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); await browserContext.close(); expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0); // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); + await removeUserDataDir(userDataDir); }); it.skip(FFOX)('userDataDir option should restore state', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browserContext = await playwright.launchPersistent(options); + const userDataDir = await makeUserDataDir(); + const browserContext = await playwright.launchPersistent(userDataDir, defaultBrowserOptions); const page = await browserContext.newPage(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => localStorage.hey = 'hello'); await browserContext.close(); - const browserContext2 = await playwright.launchPersistent(options); + const browserContext2 = await playwright.launchPersistent(userDataDir, defaultBrowserOptions); const page2 = await browserContext2.newPage(); await page2.goto(server.EMPTY_PAGE); expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); await browserContext2.close(); - const browserContext3 = await playwright.launchPersistent(defaultBrowserOptions); + const userDataDir2 = await makeUserDataDir(); + const browserContext3 = await playwright.launchPersistent(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 rmAsync(userDataDir).catch(e => {}); + await removeUserDataDir(userDataDir); + await removeUserDataDir(userDataDir2); }); // See https://github.com/microsoft/playwright/issues/717 it.skip(FFOX || (WIN && CHROMIUM))('userDataDir option should restore cookies', async({server}) => { - const userDataDir = await mkdtempAsync(TMP_FOLDER); - const options = Object.assign({userDataDir}, defaultBrowserOptions); - const browserContext = await playwright.launchPersistent(options); + const userDataDir = await makeUserDataDir(); + const browserContext = await playwright.launchPersistent(userDataDir, defaultBrowserOptions); const page = await browserContext.newPage(); await page.goto(server.EMPTY_PAGE); await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); await browserContext.close(); - const browserContext2 = await playwright.launchPersistent(options); + const browserContext2 = await playwright.launchPersistent(userDataDir, defaultBrowserOptions); const page2 = await browserContext2.newPage(); await page2.goto(server.EMPTY_PAGE); expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); await browserContext2.close(); - const browserContext3 = await playwright.launchPersistent(defaultBrowserOptions); + const userDataDir2 = await makeUserDataDir(); + const browserContext3 = await playwright.launchPersistent(userDataDir2, defaultBrowserOptions); const page3 = await browserContext3.newPage(); await page3.goto(server.EMPTY_PAGE); expect(await page3.evaluate(() => localStorage.hey)).not.toBe('doSomethingOnlyOnce=true'); await browserContext3.close(); // This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778 - await rmAsync(userDataDir).catch(e => {}); + await removeUserDataDir(userDataDir); + await removeUserDataDir(userDataDir2); }); }); }; diff --git a/test/utils.js b/test/utils.js index 8103a1b637..13e5ad0141 100644 --- a/test/utils.js +++ b/test/utils.js @@ -17,9 +17,16 @@ const fs = require('fs'); const path = require('path'); +const util = require('util'); +const os = require('os'); +const removeFolder = require('rimraf'); + const {FlakinessDashboard} = require('../utils/flakiness-dashboard'); const PROJECT_ROOT = fs.existsSync(path.join(__dirname, '..', 'package.json')) ? path.join(__dirname, '..') : path.join(__dirname, '..', '..'); +const mkdtempAsync = util.promisify(require('fs').mkdtemp); +const removeFolderAsync = util.promisify(removeFolder); + const COVERAGE_TESTSUITE_NAME = '**API COVERAGE**'; /** @@ -268,4 +275,13 @@ const utils = module.exports = { } } }, + + makeUserDataDir: async function() { + return await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_profile-')); + }, + + removeUserDataDir: async function(dir) { + await removeFolderAsync(dir).catch(e => {}); + } + }; diff --git a/utils/protocol-types-generator/index.js b/utils/protocol-types-generator/index.js index 46a765acab..4ff2d38440 100644 --- a/utils/protocol-types-generator/index.js +++ b/utils/protocol-types-generator/index.js @@ -10,11 +10,11 @@ async function generateChromiunProtocol(revision) { if (revision.local && fs.existsSync(outputPath)) return; const playwright = await require('../../index').chromium; - const browserContext = await playwright.launchPersistent({ executablePath: revision.executablePath }); - const page = await browserContext.newPage(); + const browser = await playwright.launch({ executablePath: revision.executablePath }); + const page = await browser.newPage(); await page.goto(`http://${origin}/json/protocol`); const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText)); - await browserContext.close(); + await browser.close(); fs.writeFileSync(outputPath, jsonToTS(json)); console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`); }