mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 10:15:12 +03:00
feat(launch): introduce client, server & persistent launch modes (3) (#854)
This commit is contained in:
parent
28c4a1697c
commit
55b6fe241e
13
docs/api.md
13
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/).
|
||||
|
@ -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 };
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
21
test/fixtures/dumpio.js
vendored
Normal 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) {
|
||||
}
|
||||
})();
|
@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -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 => {});
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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)}`);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user