feat(launch): introduce client, server & persistent launch modes (3) (#854)

This commit is contained in:
Pavel Feldman 2020-02-05 16:36:36 -08:00 committed by GitHub
parent 28c4a1697c
commit 55b6fe241e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 164 additions and 159 deletions

View File

@ -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/).

View File

@ -42,8 +42,7 @@ export interface BrowserType {
name(): string;
launch(options?: LaunchOptions & { slowMo?: number }): Promise<Browser>;
launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer>;
launchPersistent(options?: LaunchOptions & { userDataDir: string }): Promise<BrowserContext>;
defaultArgs(options?: BrowserArgOptions): string[];
launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext>;
connect(options: ConnectOptions): Promise<Browser>;
devices: types.Devices;
errors: { TimeoutError: typeof TimeoutError };

View File

@ -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<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext> {
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;
}

View File

@ -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<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext> {
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<string> {
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;
}

View File

@ -53,7 +53,7 @@ export class WebKit implements BrowserType {
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<WKBrowser> {
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<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
async launchPersistent(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext> {
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;
}

View File

@ -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);

View File

@ -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,<script>console.error('message from dumpio')</script>`);
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 () => {

21
test/fixtures/dumpio.js vendored Normal file
View File

@ -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) {
}
})();

View File

@ -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);
});
});
};

View File

@ -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);
});
});
};

View File

@ -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 => {});
}
};

View File

@ -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)}`);
}