feat(launch): introduce client, server & persistent launch modes (2) (#840)

This commit is contained in:
Pavel Feldman 2020-02-05 12:41:55 -08:00 коммит произвёл GitHub
Родитель 0f1a42a5d3
Коммит a2ab645e63
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
31 изменённых файлов: 479 добавлений и 422 удалений

Просмотреть файл

@ -40,9 +40,7 @@ const playwright = require('playwright');
(async () => {
for (const browserType of ['chromium', 'firefox', 'webkit']) {
const browser = await playwright[browserType].launch();
const context = await browser.newContext();
const page = await context.newPage('http://whatsmyuseragent.org/');
const page = await browser.newPage('http://whatsmyuseragent.org/');
await page.screenshot({ path: `example-${browserType}.png` });
await browser.close();
}
@ -59,14 +57,13 @@ const iPhone11 = devices['iPhone 11 Pro'];
(async () => {
const browser = await webkit.launch();
const context = await browser.newContext({
const page = await browser.newPage('https://maps.google.com', {
viewport: iPhone11.viewport,
userAgent: iPhone11.userAgent,
geolocation: { longitude: 12.492507, latitude: 41.889938 },
permissions: { 'https://www.google.com': ['geolocation'] }
});
const page = await context.newPage('https://maps.google.com');
await page.click('text="Your location"');
await page.waitForRequest(/.*preview\/pwa/);
await page.screenshot({ path: 'colosseum-iphone.png' });
@ -82,14 +79,12 @@ const pixel2 = devices['Pixel 2'];
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext({
const page = await browser.newPage('https://maps.google.com', {
viewport: pixel2.viewport,
userAgent: pixel2.userAgent,
geolocation: { longitude: 12.492507, latitude: 41.889938 },
permissions: { 'https://www.google.com': ['geolocation'] }
});
const page = await context.newPage('https://maps.google.com');
await page.click('text="Your location"');
await page.waitForRequest(/.*pwa\/net.js.*/);
await page.screenshot({ path: 'colosseum-android.png' });
@ -106,10 +101,7 @@ const { firefox } = require('playwright');
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://www.example.com/');
const page = await browser.newPage('https://www.example.com/');
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,

Просмотреть файл

@ -24,7 +24,7 @@
- [class: Accessibility](#class-accessibility)
- [class: Coverage](#class-coverage)
- [class: Worker](#class-worker)
- [class: BrowserApp](#class-browserapp)
- [class: BrowserServer](#class-browserserver)
- [class: BrowserType](#class-browsertype)
- [class: ChromiumBrowser](#class-chromiumbrowser)
- [class: ChromiumSession](#class-chromiumsession)
@ -44,8 +44,7 @@ const { chromium, firefox, webkit } = require('playwright');
(async () => {
const browser = await chromium.launch(); // Or 'firefox' or 'webkit'.
const context = await browser.newContext();
const page = await context.newPage('http://example.com');
const page = await browser.newPage('http://example.com');
// other actions...
await browser.close();
})();
@ -70,8 +69,7 @@ This object can be used to launch or connect to Chromium, returning instances of
#### playwright.devices
- returns: <[Object]>
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions). Actual list of
devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions) or [`browser.newPage(options)`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
```js
const { webkit, devices } = require('playwright');
@ -138,36 +136,22 @@ const { firefox } = require('playwright'); // Or 'chromium' or 'webkit'.
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage('https://example.com');
const page = await browser.newPage('https://example.com');
await browser.close();
})();
```
An example of launching a browser executable and connecting to a [Browser] later:
```js
const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
(async () => {
const browserApp = await webkit.launchBrowserApp();
const wsEndpoint = browserApp.wsEndpoint();
// Use web socket endpoint later to establish a connection.
const browser = await webkit.connect({ wsEndpoint });
// Close browser instance.
await browserApp.close();
})();
```
See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific features. Note that [browserType.connect(options)](#browsertypeconnectoptions) and [browserType.launch(options)](#browsertypelaunchoptions) always return a specific browser instance, based on the browser being connected to or launched.
<!-- GEN:toc -->
- [event: 'disconnected'](#event-disconnected)
- [browser.browserContexts()](#browserbrowsercontexts)
- [browser.close()](#browserclose)
- [browser.defaultContext()](#browserdefaultcontext)
- [browser.disconnect()](#browserdisconnect)
- [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage(url, [options])](#browsernewpageurl-options)
- [browser.pages()](#browserpages)
<!-- GEN:stop -->
#### event: 'disconnected'
@ -186,11 +170,6 @@ a single instance of [BrowserContext].
Closes browser and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
#### browser.defaultContext()
- returns: <[BrowserContext]>
Returns the default browser context. The default browser context can not be closed.
#### browser.disconnect()
- returns: <[Promise]>
@ -233,6 +212,33 @@ Creates a new browser context. It won't share cookies/cache with other browser c
})();
```
#### browser.newPage(url, [options])
- `url` <?[string]> Optional url to navigate the page to.
- `options` <[Object]>
- `ignoreHTTPSErrors` <?[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `bypassCSP` <?[boolean]> Toggles bypassing page's Content-Security-Policy.
- `viewport` <?[Object]> Sets a consistent viewport for each page. Defaults to an 800x600 viewport. `null` disables the default viewport.
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be thought of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `userAgent` <?[string]> Specific user agent to use in this context.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the context. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the context. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs.
- `geolocation` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details.
- returns: <[Promise]<[Page]>>
Creates a new page in a new browser context and optionally navigates it to the specified URL.
#### browser.pages()
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages.
An array of all the pages inside all the browser contexts.
### class: BrowserContext
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
@ -277,7 +283,7 @@ Clears context bookies.
Clears all permission overrides for the browser context.
```js
const context = browser.defaultContext();
const context = await browser.newContext();
context.setPermissions('https://example.com', ['clipboard-read']);
// do stuff ..
context.clearPermissions();
@ -376,7 +382,7 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});
```js
const context = browser.defaultContext();
const context = await browser.newContext();
await context.setPermissions('https://html5demos.com', ['geolocation']);
```
@ -951,8 +957,7 @@ const crypto = require('crypto');
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text()));
await page.exposeFunction('md5', text =>
crypto.createHash('md5').update(text).digest('hex')
@ -975,8 +980,7 @@ const fs = require('fs');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text()));
await page.exposeFunction('readfile', async filePath => {
return new Promise((resolve, reject) => {
@ -1491,8 +1495,7 @@ const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
(async () => {
const browser = await webkit.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
const watchDog = page.waitForFunction('window.innerWidth < 100');
await page.setViewport({width: 50, height: 50});
await watchDog;
@ -1599,8 +1602,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
let currentURL;
page
.waitForSelector('img')
@ -1636,8 +1638,7 @@ const { firefox } = require('playwright'); // Or 'chromium' or 'webkit'.
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage('https://www.google.com/chrome/browser/canary.html');
const page = await browser.newPage('https://www.google.com/chrome/browser/canary.html');
dumpFrameTree(page.mainFrame(), '');
await browser.close();
@ -2117,8 +2118,7 @@ const { firefox } = require('playwright'); // Or 'chromium' or 'webkit'.
(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
const watchDog = page.mainFrame().waitForFunction('window.innerWidth < 100');
page.setViewport({width: 50, height: 50});
await watchDog;
@ -2192,8 +2192,7 @@ const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.
(async () => {
const browser = await webkit.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
let currentURL;
page.mainFrame()
.waitForSelector('img')
@ -2216,8 +2215,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage('https://example.com');
const page = await browser.newPage('https://example.com');
const hrefElement = await page.$('a');
await hrefElement.click();
// ...
@ -2633,8 +2631,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const page = await browser.newPage();
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.dismiss();
@ -3137,8 +3134,7 @@ const { selectors, firefox } = require('playwright'); // Or 'chromium' or 'webk
await selectors.register(createTagNameEngine);
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage('http://example.com');
const page = await browser.newPage('http://example.com');
// Use the selector prefixed with its name.
const button = await page.$('tag=button');
@ -3408,33 +3404,33 @@ If the function passed to the `worker.evaluateHandle` returns a [Promise], then
- returns: <[string]>
### class: BrowserApp
### class: BrowserServer
<!-- GEN:toc -->
- [event: 'close'](#event-close-2)
- [browserApp.close()](#browserappclose)
- [browserApp.kill()](#browserappkill)
- [browserApp.process()](#browserappprocess)
- [browserApp.wsEndpoint()](#browserappwsendpoint)
- [browserServer.close()](#browserserverclose)
- [browserServer.kill()](#browserserverkill)
- [browserServer.process()](#browserserverprocess)
- [browserServer.wsEndpoint()](#browserserverwsendpoint)
<!-- GEN:stop -->
#### event: 'close'
Emitted when the browser app closes.
#### browserApp.close()
#### browserServer.close()
- returns: <[Promise]>
Closes the browser gracefully and makes sure the process is terminated.
#### browserApp.kill()
#### browserServer.kill()
Kills the browser process.
#### browserApp.process()
#### browserServer.process()
- returns: <?[ChildProcess]> Spawned browser application process.
#### browserApp.wsEndpoint()
#### browserServer.wsEndpoint()
- returns: <[string]> Browser websocket url.
Browser websocket endpoint which can be used as an argument to [browserType.connect(options)](#browsertypeconnectoptions) to establish connection to the browser.
@ -3448,8 +3444,7 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage('http://example.com');
const page = await browser.newPage('http://example.com');
// other actions...
await browser.close();
})();
@ -3462,7 +3457,8 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
- [browserType.errors](#browsertypeerrors)
- [browserType.executablePath()](#browsertypeexecutablepath)
- [browserType.launch([options])](#browsertypelaunchoptions)
- [browserType.launchBrowserApp([options])](#browsertypelaunchbrowserappoptions)
- [browserType.launchPersistent([options])](#browsertypelaunchpersistentoptions)
- [browserType.launchServer([options])](#browsertypelaunchserveroptions)
- [browserType.name()](#browsertypename)
<!-- GEN:stop -->
@ -3478,7 +3474,6 @@ This methods attaches Playwright to an existing browser instance.
- `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/).
- `userDataDir` <[string]> Path to a [User Data Directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md).
- `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]>>
@ -3487,8 +3482,7 @@ The default flags that browser will be launched with.
#### browserType.devices
- returns: <[Object]>
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions). Actual list of
devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
Returns a list of devices to be used with [`browser.newContext(options)`](#browsernewcontextoptions) and [`browser.newPage(options)`](#browsernewpageoptions). Actual list of devices can be found in [src/deviceDescriptors.ts](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts).
```js
const { webkit } = require('playwright');
@ -3542,9 +3536,9 @@ try {
- `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`.
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `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).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `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`.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- returns: <[Promise]<[Browser]>> Promise which resolves to browser instance.
@ -3563,11 +3557,11 @@ 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.launchBrowserApp([options])
#### browserType.launchPersistent([options])
- `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.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `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/).
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`browserType.defaultArgs()`](#browsertypedefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
@ -3575,10 +3569,43 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
- `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`.
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `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).
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `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: <[Promise]<[BrowserApp]>> Promise which resolves to the browser app instance.
- returns: <[Promise]<[BrowserServer]>> Promise which resolves to the browser app instance.
Launches browser instance that uses persistent storage located at `userDataDir`. If `userDataDir` is not specified, temporary folder is created for the persistent storage. That folder is deleted when browser closes.
#### browserType.launchServer([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`.
- `port` <[number]> Port to use for the web socket. Defaults to 0 that picks any available port.
- `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/).
- `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`browserType.defaultArgs()`](#browsertypedefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.
- `handleSIGHUP` <[boolean]> Close the browser process on SIGHUP. Defaults to `true`.
- `timeout` <[number]> Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout.
- `dumpio` <[boolean]> Whether to pipe the browser process stdout and stderr into `process.stdout` and `process.stderr`. Defaults to `false`.
- `env` <[Object]> Specify environment variables that will be visible to the browser. Defaults to `process.env`.
- `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: <[Promise]<[BrowserServer]>> Promise which resolves to the browser app instance.
Launches browser server that client can connect to. An example of launching a browser executable and connecting to it later:
```js
const { chromium } = require('playwright'); // Or 'webkit' or 'firefox'.
(async () => {
const browserServer = await chromium.launchServer();
const wsEndpoint = browserServer.wsEndpoint();
// Use web socket endpoint later to establish a connection.
const browser = await chromium.connect({ wsEndpoint });
// Close browser instance.
await browserServer.close();
})();
```
#### browserType.name()
- returns: <[string]>
@ -3614,10 +3641,11 @@ await browser.stopTracing();
- [event: 'disconnected'](#event-disconnected)
- [browser.browserContexts()](#browserbrowsercontexts)
- [browser.close()](#browserclose)
- [browser.defaultContext()](#browserdefaultcontext)
- [browser.disconnect()](#browserdisconnect)
- [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage(url, [options])](#browsernewpageurl-options)
- [browser.pages()](#browserpages)
<!-- GEN:stop -->
#### event: 'targetchanged'
@ -3781,10 +3809,11 @@ Firefox browser instance does not expose Firefox-specific features.
- [event: 'disconnected'](#event-disconnected)
- [browser.browserContexts()](#browserbrowsercontexts)
- [browser.close()](#browserclose)
- [browser.defaultContext()](#browserdefaultcontext)
- [browser.disconnect()](#browserdisconnect)
- [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage(url, [options])](#browsernewpageurl-options)
- [browser.pages()](#browserpages)
<!-- GEN:stop -->
### class: WebKitBrowser
@ -3797,10 +3826,11 @@ WebKit browser instance does not expose WebKit-specific features.
- [event: 'disconnected'](#event-disconnected)
- [browser.browserContexts()](#browserbrowsercontexts)
- [browser.close()](#browserclose)
- [browser.defaultContext()](#browserdefaultcontext)
- [browser.disconnect()](#browserdisconnect)
- [browser.isConnected()](#browserisconnected)
- [browser.newContext(options)](#browsernewcontextoptions)
- [browser.newPage(url, [options])](#browsernewpageurl-options)
- [browser.pages()](#browserpages)
<!-- GEN:stop -->
### Working with selectors
@ -3874,7 +3904,7 @@ const { chromium } = require('playwright');
[Accessibility]: #class-accessibility "Accessibility"
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
[Body]: #class-body "Body"
[BrowserApp]: #class-browserapp "BrowserApp"
[BrowserServer]: #class-browserapp "BrowserServer"
[BrowserContext]: #class-browsercontext "BrowserContext"
[BrowserType]: #class-browsertype "BrowserType"
[Browser]: #class-browser "Browser"

Просмотреть файл

@ -37,4 +37,4 @@ export { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';
export { WKBrowser as WebKitBrowser } from './webkit/wkBrowser';
export { BrowserType } from './server/browserType';
export { BrowserApp } from './server/browserApp';
export { BrowserServer } from './server/browserServer';

Просмотреть файл

@ -16,12 +16,13 @@
import { BrowserContext, BrowserContextOptions } from './browserContext';
import * as platform from './platform';
import { Page } from './page';
export interface Browser extends platform.EventEmitterType {
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
browserContexts(): BrowserContext[];
defaultContext(): BrowserContext;
pages(): Promise<Page[]>;
newPage(url?: string, options?: BrowserContextOptions): Promise<Page>;
disconnect(): Promise<void>;
isConnected(): boolean;
close(): Promise<void>;
@ -31,3 +32,15 @@ export type ConnectOptions = {
slowMo?: number,
wsEndpoint: string
};
export async function collectPages(browser: Browser): Promise<Page[]> {
const result: Promise<Page[]>[] = [];
for (const browserContext of browser.browserContexts())
result.push(browserContext.pages());
const pages: Page[] = [];
for (const group of await Promise.all(result))
pages.push(...group);
return pages;
}
export type LaunchType = 'local' | 'server' | 'persistent';

Просмотреть файл

@ -24,7 +24,7 @@ import { Page, Worker } from '../page';
import { CRTarget } from './crTarget';
import { Protocol } from './protocol';
import { CRPage } from './crPage';
import { Browser } from '../browser';
import { Browser, collectPages } from '../browser';
import * as network from '../network';
import * as types from '../types';
import * as platform from '../platform';
@ -34,7 +34,7 @@ import { ConnectionTransport, SlowMoTransport } from '../transport';
export class CRBrowser extends platform.EventEmitter implements Browser {
_connection: CRConnection;
_client: CRSession;
private _defaultContext: BrowserContext;
readonly _defaultContext: BrowserContext;
private _contexts = new Map<string, BrowserContext>();
_targets = new Map<string, CRTarget>();
@ -165,11 +165,16 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
}
browserContexts(): BrowserContext[] {
return [this._defaultContext, ...Array.from(this._contexts.values())];
return Array.from(this._contexts.values());
}
defaultContext(): BrowserContext {
return this._defaultContext;
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(url?: string, options?: BrowserContextOptions): Promise<Page> {
const browserContext = await this.newContext(options);
return browserContext.newPage(url);
}
async _targetCreated(event: Protocol.Target.targetCreatedPayload) {

Просмотреть файл

@ -20,7 +20,7 @@ export const Events = {
Disconnected: 'disconnected'
},
BrowserApp: {
BrowserServer: {
Close: 'close',
},

Просмотреть файл

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Browser } from '../browser';
import { Browser, collectPages } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
@ -31,7 +31,7 @@ import { ConnectionTransport, SlowMoTransport } from '../transport';
export class FFBrowser extends platform.EventEmitter implements Browser {
_connection: FFConnection;
_targets: Map<string, Target>;
private _defaultContext: BrowserContext;
readonly _defaultContext: BrowserContext;
private _contexts: Map<string, BrowserContext>;
private _eventListeners: RegisteredListener[];
@ -84,12 +84,17 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
return context;
}
browserContexts(): Array<BrowserContext> {
return [this._defaultContext, ...Array.from(this._contexts.values())];
browserContexts(): BrowserContext[] {
return Array.from(this._contexts.values());
}
defaultContext() {
return this._defaultContext;
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(url?: string, options?: BrowserContextOptions): Promise<Page> {
const browserContext = await this.newContext(options);
return browserContext.newPage(url);
}
async _waitForTarget(predicate: (target: Target) => boolean, options: { timeout?: number; } = {}): Promise<Target> {

Просмотреть файл

@ -17,7 +17,7 @@
import { ChildProcess, execSync } from 'child_process';
import * as platform from '../platform';
export class BrowserApp extends platform.EventEmitter {
export class BrowserServer extends platform.EventEmitter {
private _process: ChildProcess;
private _gracefullyClose: () => Promise<void>;
private _browserWSEndpoint: string | null = null;

Просмотреть файл

@ -17,12 +17,12 @@
import * as types from '../types';
import { TimeoutError } from '../errors';
import { Browser, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
import { BrowserContext } from '../browserContext';
import { BrowserServer } from './browserServer';
export type BrowserArgOptions = {
headless?: boolean,
args?: string[],
userDataDir?: string,
devtools?: boolean,
};
@ -40,8 +40,9 @@ export type LaunchOptions = BrowserArgOptions & {
export interface BrowserType {
executablePath(): string;
name(): string;
launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;
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[];
connect(options: ConnectOptions): Promise<Browser>;
devices: types.Devices;

Просмотреть файл

@ -30,10 +30,11 @@ import { launchProcess, waitForLine } from '../server/processLauncher';
import { kBrowserCloseMessageId } from '../chromium/crConnection';
import { PipeTransport } from './pipeTransport';
import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
import { ConnectOptions, LaunchType } from '../browser';
import { BrowserServer } from './browserServer';
import { Events } from '../events';
import { ConnectionTransport } from '../transport';
import { BrowserContext } from '../browserContext';
export class Chromium implements BrowserType {
private _projectRoot: string;
@ -49,19 +50,28 @@ export class Chromium implements BrowserType {
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<CRBrowser> {
const { browserApp, transport } = await this._launchBrowserApp(options, false);
const { browserServer, transport } = await this._launchServer(options, 'local');
const browser = await CRBrowser.connect(transport!, options && options.slowMo);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => browserApp.close();
(browser as any)['__app__'] = browserApp;
browser.close = () => browserServer.close();
(browser as any)['__server__'] = browserServer;
return browser;
}
async launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp> {
return (await this._launchBrowserApp(options, true)).browserApp;
async launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer> {
return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer;
}
async _launchBrowserApp(options: LaunchOptions = {}, isServer: boolean): Promise<{ browserApp: BrowserApp, transport?: ConnectionTransport }> {
async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
const browser = await CRBrowser.connect(transport!);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext;
browserContext.close = () => browserServer.close();
return browserContext;
}
private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
const {
ignoreDefaultArgs = false,
args = [],
@ -82,14 +92,19 @@ export class Chromium implements BrowserType {
else
chromeArguments.push(...args);
let temporaryUserDataDir: string | null = null;
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');
if (!chromeArguments.some(argument => argument.startsWith('--remote-debugging-')))
chromeArguments.push(isServer ? '--remote-debugging-port=0' : '--remote-debugging-pipe');
if (!chromeArguments.some(arg => arg.startsWith('--user-data-dir'))) {
let temporaryUserDataDir: string | null = null;
if (!userDataDir) {
userDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
temporaryUserDataDir = await mkdtempAsync(CHROMIUM_PROFILE_PATH);
chromeArguments.push(`--user-data-dir=${temporaryUserDataDir}`);
}
chromeArguments.push(`--user-data-dir=${userDataDir}`);
chromeArguments.push(launchType === 'server' ? `--remote-debugging-port=${port || 0}` : '--remote-debugging-pipe');
let chromeExecutable = executablePath;
if (!executablePath) {
@ -99,11 +114,7 @@ export class Chromium implements BrowserType {
chromeExecutable = executablePath;
}
const usePipe = chromeArguments.includes('--remote-debugging-pipe');
if (usePipe && isServer)
throw new Error(`Argument "--remote-debugging-pipe" is not compatible with the launchBrowserApp.`);
let browserApp: BrowserApp | undefined = undefined;
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: chromeExecutable!,
args: chromeArguments,
@ -112,10 +123,10 @@ export class Chromium implements BrowserType {
handleSIGTERM,
handleSIGHUP,
dumpio,
pipe: usePipe,
pipe: launchType !== 'server',
tempDir: temporaryUserDataDir || undefined,
attemptToGracefullyClose: async () => {
if (!browserApp)
if (!browserServer)
return Promise.reject();
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
@ -125,14 +136,14 @@ export class Chromium implements BrowserType {
t.send(JSON.stringify(message));
},
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(Events.BrowserApp.Close, exitCode, signal);
if (browserServer)
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
},
});
let transport: ConnectionTransport | undefined;
let browserWSEndpoint: string | null;
if (isServer) {
if (launchType === 'server') {
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chromium! The only Chromium revision guaranteed to work is r${this._revision}`);
const match = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
browserWSEndpoint = match[1];
@ -140,8 +151,8 @@ export class Chromium implements BrowserType {
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
browserWSEndpoint = null;
}
browserApp = new BrowserApp(launchedProcess, gracefullyClose, browserWSEndpoint);
return { browserApp, transport };
browserServer = new BrowserServer(launchedProcess, gracefullyClose, browserWSEndpoint);
return { browserServer, transport };
}
async connect(options: ConnectOptions): Promise<CRBrowser> {
@ -166,11 +177,8 @@ export class Chromium implements BrowserType {
devtools = false,
headless = !devtools,
args = [],
userDataDir = null
} = options;
const chromeArguments = [...DEFAULT_ARGS];
if (userDataDir)
chromeArguments.push(`--user-data-dir=${userDataDir}`);
if (devtools)
chromeArguments.push('--auto-open-devtools-for-tabs');
if (headless) {

Просмотреть файл

@ -29,10 +29,11 @@ import * as util from 'util';
import { TimeoutError } from '../errors';
import { assert } from '../helper';
import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
import { ConnectOptions, LaunchType } from '../browser';
import { BrowserServer } from './browserServer';
import { Events } from '../events';
import { ConnectionTransport } from '../transport';
import { BrowserContext } from '../browserContext';
export class Firefox implements BrowserType {
private _projectRoot: string;
@ -48,19 +49,28 @@ export class Firefox implements BrowserType {
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<FFBrowser> {
const { browserApp, transport } = await this._launchBrowserApp(options, false);
const { browserServer, transport } = await this._launchServer(options, 'local');
const browser = await FFBrowser.connect(transport!, options && options.slowMo);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => browserApp.close();
(browser as any)['__app__'] = browserApp;
browser.close = () => browserServer.close();
(browser as any)['__server__'] = browserServer;
return browser;
}
async launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp> {
return (await this._launchBrowserApp(options, true)).browserApp;
async launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer> {
return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer;
}
private async _launchBrowserApp(options: LaunchOptions = {}, isServer: boolean): Promise<{ browserApp: BrowserApp, transport?: ConnectionTransport }> {
async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
const browser = await FFBrowser.connect(transport!);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext;
browserContext.close = () => browserServer.close();
return browserContext;
}
private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
const {
ignoreDefaultArgs = false,
args = [],
@ -81,14 +91,20 @@ export class Firefox implements BrowserType {
else
firefoxArguments.push(...args);
if (!firefoxArguments.includes('-juggler'))
firefoxArguments.unshift('-juggler', '0');
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 (!firefoxArguments.includes('-profile') && !firefoxArguments.includes('--profile')) {
temporaryProfileDir = await createProfile();
firefoxArguments.unshift(`-profile`, temporaryProfileDir);
if (!userDataDir) {
userDataDir = await createProfile();
temporaryProfileDir = userDataDirArg;
}
firefoxArguments.unshift(`-profile`, 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));
let firefoxExecutable = executablePath;
if (!firefoxExecutable) {
@ -98,7 +114,7 @@ export class Firefox implements BrowserType {
firefoxExecutable = executablePath;
}
let browserApp: BrowserApp | undefined = undefined;
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: firefoxExecutable,
args: firefoxArguments,
@ -114,7 +130,7 @@ export class Firefox implements BrowserType {
pipe: false,
tempDir: temporaryProfileDir || undefined,
attemptToGracefullyClose: async () => {
if (!browserApp)
if (!browserServer)
return Promise.reject();
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
@ -124,16 +140,16 @@ export class Firefox implements BrowserType {
transport.send(JSON.stringify(message));
},
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(Events.BrowserApp.Close, exitCode, signal);
if (browserServer)
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
},
});
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
const browserWSEndpoint = match[1];
browserApp = new BrowserApp(launchedProcess, gracefullyClose, isServer ? browserWSEndpoint : null);
return { browserApp, transport: isServer ? undefined : await platform.createWebSocketTransport(browserWSEndpoint) };
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? browserWSEndpoint : null);
return { browserServer, transport: launchType === 'server' ? undefined : await platform.createWebSocketTransport(browserWSEndpoint) };
}
async connect(options: ConnectOptions): Promise<FFBrowser> {
@ -158,13 +174,10 @@ export class Firefox implements BrowserType {
devtools = false,
headless = !devtools,
args = [],
userDataDir = null,
} = options;
if (devtools)
throw new Error('Option "devtools" is not supported by Firefox');
const firefoxArguments = [...DEFAULT_ARGS];
if (userDataDir)
firefoxArguments.push('-profile', userDataDir);
if (headless)
firefoxArguments.push('-headless');
else

Просмотреть файл

@ -34,9 +34,10 @@ import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { ConnectionTransport } from '../transport';
import * as ws from 'ws';
import * as uuidv4 from 'uuid/v4';
import { ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';
import { ConnectOptions, LaunchType } from '../browser';
import { BrowserServer } from './browserServer';
import { Events } from '../events';
import { BrowserContext } from '../browserContext';
export class WebKit implements BrowserType {
private _projectRoot: string;
@ -52,19 +53,28 @@ export class WebKit implements BrowserType {
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<WKBrowser> {
const { browserApp, transport } = await this._launchBrowserApp(options, false);
const { browserServer, transport } = await this._launchServer(options, 'local', null);
const browser = await WKBrowser.connect(transport!, options && options.slowMo);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => browserApp.close();
(browser as any)['__app__'] = browserApp;
browser.close = () => browserServer.close();
(browser as any)['__server__'] = browserServer;
return browser;
}
async launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp> {
return (await this._launchBrowserApp(options, true)).browserApp;
async launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer> {
return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer;
}
private async _launchBrowserApp(options: LaunchOptions = {}, isServer: boolean): Promise<{ browserApp: BrowserApp, transport?: ConnectionTransport }> {
async launchPersistent(options?: LaunchOptions & { userDataDir?: string }): Promise<BrowserContext> {
const { browserServer, transport } = await this._launchServer(options, 'persistent', options && options.userDataDir);
const browser = await WKBrowser.connect(transport!);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext;
browserContext.close = () => browserServer.close();
return browserContext;
}
private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
const {
ignoreDefaultArgs = false,
args = [],
@ -84,16 +94,16 @@ export class WebKit implements BrowserType {
else
webkitArguments.push(...args);
let userDataDir: string;
let temporaryUserDataDir: string | null = null;
const userDataDirArg = webkitArguments.find(arg => arg.startsWith('--user-data-dir='));
if (userDataDirArg) {
userDataDir = userDataDirArg.substr('--user-data-dir='.length).trim();
} else {
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=${temporaryUserDataDir}`);
temporaryUserDataDir = userDataDir!;
}
webkitArguments.push(`--user-data-dir=${userDataDir}`);
let webkitExecutable = executablePath;
if (!executablePath) {
@ -104,11 +114,11 @@ export class WebKit implements BrowserType {
}
let transport: PipeTransport | undefined = undefined;
let browserApp: BrowserApp | undefined = undefined;
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: webkitExecutable!,
args: webkitArguments,
env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') },
env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir!, 'cookiejar.db') },
handleSIGINT,
handleSIGTERM,
handleSIGHUP,
@ -125,14 +135,14 @@ export class WebKit implements BrowserType {
transport.send(message);
},
onkill: (exitCode, signal) => {
if (browserApp)
browserApp.emit(Events.BrowserApp.Close, exitCode, signal);
if (browserServer)
browserServer.emit(Events.BrowserServer.Close, exitCode, signal);
},
});
transport = new PipeTransport(launchedProcess.stdio[3] as NodeJS.WritableStream, launchedProcess.stdio[4] as NodeJS.ReadableStream);
browserApp = new BrowserApp(launchedProcess, gracefullyClose, isServer ? wrapTransportWithWebSocket(transport) : null);
return { browserApp, transport };
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, port || 0) : null);
return { browserServer, transport };
}
async connect(options: ConnectOptions): Promise<WKBrowser> {
@ -157,13 +167,10 @@ export class WebKit implements BrowserType {
devtools = false,
headless = !devtools,
args = [],
userDataDir = null
} = options;
if (devtools)
throw new Error('Option "devtools" is not supported by WebKit');
const webkitArguments = ['--inspector-pipe'];
if (userDataDir)
webkitArguments.push(`--user-data-dir=${userDataDir}`);
if (headless)
webkitArguments.push('--headless');
webkitArguments.push(...args);
@ -230,8 +237,8 @@ function getMacVersion(): string {
return cachedMacVersion;
}
function wrapTransportWithWebSocket(transport: ConnectionTransport) {
const server = new ws.Server({ port: 0 });
function wrapTransportWithWebSocket(transport: ConnectionTransport, port: number) {
const server = new ws.Server({ port });
let socket: ws | undefined;
const guid = uuidv4();

Просмотреть файл

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Browser } from '../browser';
import { Browser, collectPages } from '../browser';
import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
@ -33,7 +33,7 @@ const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) Appl
export class WKBrowser extends platform.EventEmitter implements Browser {
private readonly _connection: WKConnection;
private readonly _browserSession: WKSession;
private readonly _defaultContext: BrowserContext;
readonly _defaultContext: BrowserContext;
private readonly _contexts = new Map<string, BrowserContext>();
private readonly _pageProxies = new Map<string, WKPageProxy>();
private readonly _eventListeners: RegisteredListener[];
@ -84,11 +84,16 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
}
browserContexts(): BrowserContext[] {
return [this._defaultContext, ...Array.from(this._contexts.values())];
return Array.from(this._contexts.values());
}
defaultContext(): BrowserContext {
return this._defaultContext;
async pages(): Promise<Page[]> {
return collectPages(this);
}
async newPage(url?: string, options?: BrowserContextOptions): Promise<Page> {
const browserContext = await this.newContext(options);
return browserContext.newPage(url);
}
async _waitForFirstPageTarget(timeout: number): Promise<void> {

46
test/browser.spec.js Normal file
Просмотреть файл

@ -0,0 +1,46 @@
/**
* Copyright 2020 Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const utils = require('./utils');
module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WEBKIT}) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('Browser', function() {
it('should create new page', async function({browser}) {
expect((await browser.pages()).length).toBe(0);
const page1 = await browser.newPage();
expect((await browser.pages()).length).toBe(1);
expect(browser.browserContexts().length).toBe(1);
const page2 = await browser.newPage();
expect((await browser.pages()).length).toBe(2);
expect(browser.browserContexts().length).toBe(2);
await page1.close();
expect((await browser.pages()).length).toBe(1);
expect(browser.browserContexts().length).toBe(2);
await page2.browserContext().close();
expect((await browser.pages()).length).toBe(0);
expect(browser.browserContexts().length).toBe(1);
await page1.browserContext().close();
expect(browser.browserContexts().length).toBe(0);
});
});
};

Просмотреть файл

@ -23,21 +23,13 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('BrowserContext', function() {
it('should have default context', async function({browser, server}) {
expect(browser.browserContexts().length).toBe(1);
const defaultContext = browser.browserContexts()[0];
let error = null;
await defaultContext.close().catch(e => error = e);
expect(browser.defaultContext()).toBe(defaultContext);
expect(error.message).toContain('cannot be closed');
});
it('should create new incognito context', async function({browser, newContext}) {
expect(browser.browserContexts().length).toBe(1);
it('should create new context', async function({browser, newContext}) {
expect(browser.browserContexts().length).toBe(0);
const context = await newContext();
expect(browser.browserContexts().length).toBe(2);
expect(browser.browserContexts().length).toBe(1);
expect(browser.browserContexts().indexOf(context) !== -1).toBe(true);
await context.close();
expect(browser.browserContexts().length).toBe(1);
expect(browser.browserContexts().length).toBe(0);
});
it('window.open should use parent tab context', async function({newContext, server}) {
const context = await newContext();
@ -91,7 +83,7 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE
context1.close(),
context2.close()
]);
expect(browser.browserContexts().length).toBe(1);
expect(browser.browserContexts().length).toBe(0);
});
it('should propagate default viewport to the page', async({ newPage }) => {
const page = await newPage({ viewport: { width: 456, height: 789 } });

Просмотреть файл

@ -41,12 +41,6 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const browserTarget = targets.find(target => target.type() === 'browser');
expect(browserTarget).toBe(browser.browserTarget());
});
it('should be able to use the default page in the browser', async({page, server, browser}) => {
// The pages will be the testing page and the original newtab page
const originalPage = (await browser.defaultContext().pages())[0];
expect(await originalPage.evaluate(() => ['Hello', 'world'].join(' '))).toBe('Hello world');
expect(await originalPage.$('body')).toBeTruthy();
});
it('should report when a new page is created and closed', async({browser, page, server, context}) => {
const [otherPage] = await Promise.all([
browser.waitForTarget(target => target.url() === server.CROSS_PROCESS_PREFIX + '/empty.html').then(target => target.page()),
@ -182,30 +176,21 @@ module.exports.describe = function({testRunner, expect, playwright, FFOX, CHROMI
const target = await targetPromise;
expect(await target.page()).toBe(page);
});
it('should timeout waiting for a non-existent target', async function({browser, server, newContext}) {
const context = await newContext();
it('should timeout waiting for a non-existent target', async function({browser, context, server}) {
const error = await browser.waitForTarget(target => target.browserContext() === context && target.url() === server.EMPTY_PAGE, {timeout: 1}).catch(e => e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
await context.close();
});
it('should wait for a target', async function({browser, server}) {
let resolved = false;
const targetPromise = browser.waitForTarget(target => target.url() === server.EMPTY_PAGE);
targetPromise.then(() => resolved = true);
const page = await browser.defaultContext().newPage();
const page = await browser.newPage();
expect(resolved).toBe(false);
await page.goto(server.EMPTY_PAGE);
const target = await targetPromise;
expect(await target.page()).toBe(page);
await page.close();
});
it('should timeout waiting for a non-existent target', async function({browser, server}) {
let error = null;
await browser.waitForTarget(target => target.url() === server.EMPTY_PAGE, {
timeout: 1
}).catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
});
it('should fire target events', async function({browser, newContext, server}) {
const context = await newContext();
const events = [];

Просмотреть файл

@ -47,7 +47,7 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
describe('ChromiumHeadful', function() {
it('background_page target type should be available', async() => {
const browserWithExtension = await playwright.launch(extensionOptions);
const page = await browserWithExtension.defaultContext().newPage();
const page = await browserWithExtension.newPage();
const backgroundPageTarget = await browserWithExtension.waitForTarget(target => target.type() === 'background_page');
await page.close();
await browserWithExtension.close();
@ -65,7 +65,7 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
xit('OOPIF: should report google.com frame', async({server}) => {
// https://google.com is isolated by default in Chromium embedder.
const browser = await playwright.launch(headfulOptions);
const page = await browser.defaultContext().newPage();
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.interception.enable();
page.on('request', r => page.interception.fulfill(r, {body: 'YO, GOOGLE.COM'}));

Просмотреть файл

@ -34,20 +34,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.launch webSocket option', function() {
it('should support the remote-debugging-port argument', async() => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-port=0'].concat(options.args || []);
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
expect(browserApp.wsEndpoint()).not.toBe(null);
const page = await browser.defaultContext().newPage();
const browserServer = await playwright.launchServer({ ...options, port: 0 });
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browserServer.wsEndpoint()).not.toBe(null);
const page = await browser.newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserApp.close();
await browserServer.close();
});
it('should throw with remote-debugging-pipe argument and webSocket', async() => {
const options = Object.assign({}, defaultBrowserOptions);
options.args = ['--remote-debugging-pipe'].concat(options.args || []);
const error = await playwright.launchBrowserApp(options).catch(e => e);
expect(error.message).toBe('Argument "--remote-debugging-pipe" is not compatible with the launchBrowserApp.');
const error = await playwright.launchServer(options).catch(e => e);
expect(error.message).toContain('Playwright manages remote debugging connection itself');
});
});
});
@ -59,7 +58,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
browser.on('targetcreated', () => events.push('CREATED'));
browser.on('targetchanged', () => events.push('CHANGED'));
browser.on('targetdestroyed', () => events.push('DESTROYED'));
const page = await browser.defaultContext().newPage();
const page = await browser.newPage();
await page.goto(server.EMPTY_PAGE);
await page.close();
expect(events).toEqual(['CREATED', 'CHANGED', 'DESTROYED']);

Просмотреть файл

@ -26,7 +26,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
beforeEach(async function(state) {
state.outputFile = path.join(ASSETS_DIR, `trace-${state.parallelIndex}.json`);
state.browser = await playwright.launch(defaultBrowserOptions);
state.page = await state.browser.defaultContext().newPage();
state.page = await state.browser.newPage();
});
afterEach(async function(state) {
await state.browser.close();
@ -52,7 +52,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
it('should throw if tracing on two pages', async({browser, page, server, outputFile}) => {
await browser.startTracing(page, {path: outputFile});
const newPage = await browser.defaultContext().newPage();
const newPage = await browser.newPage();
let error = null;
await browser.startTracing(newPage, {path: outputFile}).catch(e => error = e);
await newPage.close();

Просмотреть файл

@ -20,14 +20,14 @@ module.exports.describe = function ({ testRunner, expect, defaultBrowserOptions,
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('defaultContext()', function() {
describe('launchPersistent()', function() {
beforeEach(async state => {
state.browser = await playwright.launch(defaultBrowserOptions);
state.page = await state.browser.defaultContext().newPage();
state.browserContext = await playwright.launchPersistent(defaultBrowserOptions);
state.page = await state.browserContext.newPage();
});
afterEach(async state => {
await state.browser.close();
delete state.browser;
await state.browserContext.close();
delete state.browserContext;
delete state.page;
});
it('context.cookies() should work', async({page, server}) => {

Просмотреть файл

@ -66,18 +66,17 @@ module.exports.describe = function({testRunner, expect, product, playwright, pla
}
describe('Fixtures', function() {
it('dumpio option should work with webSocket option', async({server}) => {
xit('should dump browser process stderr', async({server}) => {
const browserServer = await playwright.launchServer({ dumpio: true });
let dumpioData = '';
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));
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();
expect(dumpioData).toContain('message from dumpio');
});
it('should close the browser when the node process closes', async () => {

8
test/fixtures/closeme.js поставляемый
Просмотреть файл

@ -1,9 +1,9 @@
(async() => {
const [, , playwrightRoot, product, options] = process.argv;
const browserApp = await require(playwrightRoot)[product.toLowerCase()].launchBrowserApp(JSON.parse(options));
browserApp.on('close', (exitCode, signal) => {
const browserServer = await require(playwrightRoot)[product.toLowerCase()].launchServer(JSON.parse(options));
browserServer.on('close', (exitCode, signal) => {
console.log(`browserClose:${exitCode}:${signal}:browserClose`);
});
console.log(`browserPid:${browserApp.process().pid}:browserPid`);
console.log(`browserWS:${browserApp.wsEndpoint()}:browserWS`);
console.log(`browserPid:${browserServer.process().pid}:browserPid`);
console.log(`browserWS:${browserServer.wsEndpoint()}:browserWS`);
})();

24
test/fixtures/dumpio.js поставляемый
Просмотреть файл

@ -1,24 +0,0 @@
(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 = {
webSocket: useWebSocket === 'usewebsocket',
ignoreDefaultArgs: true,
dumpio: true,
timeout: 1,
executablePath: 'node',
args: ['-e', 'console.error("message from dumpio")', '--']
}
console.error('using web socket: ' + options.webSocket);
if (product.toLowerCase() === 'firefox')
options.args.push('-juggler', '-profile');
try {
await require(playwrightRoot)[product.toLowerCase()].launchBrowserApp(options);
console.error('Browser launch unexpectedly succeeded.');
} catch (e) {
}
})();

Просмотреть файл

@ -38,38 +38,38 @@ module.exports.describe = function({testRunner, expect, playwright, defaultBrows
describe('Headful', function() {
it('should have default url when launching browser', async function() {
const browser = await playwright.launch(headfulOptions);
const pages = (await browser.defaultContext().pages()).map(page => page.url());
const browserContext = await playwright.launchPersistent(headfulOptions);
const pages = (await browserContext.pages()).map(page => page.url());
expect(pages).toEqual(['about:blank']);
await browser.close();
await browserContext.close();
});
// 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);
// Write a cookie in headful chrome
const headfulBrowser = await playwright.launch(Object.assign({userDataDir}, headfulOptions));
const headfulPage = await headfulBrowser.defaultContext().newPage();
const headfulContext = await playwright.launchPersistent(Object.assign({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 headfulBrowser.close();
await headfulContext.close();
// Read the cookie from headless chrome
const headlessBrowser = await playwright.launch(Object.assign({userDataDir}, headlessOptions));
const headlessPage = await headlessBrowser.defaultContext().newPage();
const headlessContext = await playwright.launchPersistent(Object.assign({userDataDir}, headlessOptions));
const headlessPage = await headlessContext.newPage();
await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie);
await headlessBrowser.close();
await headlessContext.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await rmAsync(userDataDir).catch(e => {});
expect(cookie).toBe('foo=true');
});
it.skip(FFOX)('should close browser with beforeunload page', async({server}) => {
const browser = await playwright.launch(headfulOptions);
const page = await browser.defaultContext().newPage();
const browserContext = await playwright.launchPersistent(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 browser.close();
await browserContext.close();
});
});
};

Просмотреть файл

@ -34,7 +34,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.launch', function() {
it('should reject all promises when browser is closed', async() => {
const browser = await playwright.launch(defaultBrowserOptions);
const page = await browser.defaultContext().newPage();
const page = await (await browser.newContext()).newPage();
let error = null;
const neverResolves = page.evaluate(() => new Promise(r => {})).catch(e => error = e);
await browser.close();
@ -48,28 +48,28 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(waitError.message).toContain('Failed to launch');
});
it('should have default URL when launching browser', async function() {
const browser = await playwright.launch(defaultBrowserOptions);
const pages = (await browser.defaultContext().pages()).map(page => page.url());
const browserContext = await playwright.launchPersistent(defaultBrowserOptions);
const pages = (await browserContext.pages()).map(page => page.url());
expect(pages).toEqual(['about:blank']);
await browser.close();
await browserContext.close();
});
it('should have custom URL when launching browser', async function({server}) {
const options = Object.assign({}, defaultBrowserOptions);
options.args = [server.EMPTY_PAGE].concat(options.args || []);
const browser = await playwright.launch(options);
const pages = await browser.defaultContext().pages();
const browserContext = await playwright.launchPersistent(options);
const pages = await browserContext.pages();
expect(pages.length).toBe(1);
const page = pages[0];
if (page.url() !== server.EMPTY_PAGE) {
await page.waitForNavigation();
}
expect(page.url()).toBe(server.EMPTY_PAGE);
await browser.close();
await browserContext.close();
});
it('should return child_process instance', async () => {
const browserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
expect(browserApp.process().pid).toBeGreaterThan(0);
await browserApp.close();
const browserServer = await playwright.launchServer(defaultBrowserOptions);
expect(browserServer.process().pid).toBeGreaterThan(0);
await browserServer.close();
});
});
@ -100,19 +100,18 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
expect(playwright.defaultArgs()).toContain('--no-first-run');
expect(playwright.defaultArgs()).toContain(FFOX ? '-headless' : '--headless');
expect(playwright.defaultArgs({headless: false})).not.toContain(FFOX ? '-headless' : '--headless');
expect(playwright.defaultArgs({userDataDir: 'foo'})).toContain(FFOX ? 'foo' : '--user-data-dir=foo');
});
it('should filter out ignored default arguments', async() => {
const defaultArgsWithoutUserDataDir = playwright.defaultArgs(defaultBrowserOptions);
const defaultArgsWithUserDataDir = playwright.defaultArgs({...defaultBrowserOptions, userDataDir: 'fake-profile'});
const browserApp = await playwright.launchBrowserApp(Object.assign({}, defaultBrowserOptions, {
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 = browserApp.process().spawnargs;
const spawnargs = browserServer.process().spawnargs;
expect(spawnargs.some(x => x.includes('fake-profile'))).toBe(false);
await browserApp.close();
await browserServer.close();
});
});
});
@ -132,19 +131,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Browser.isConnected', () => {
it('should set the browser connected state', async () => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(remote.isConnected()).toBe(true);
await remote.disconnect();
expect(remote.isConnected()).toBe(false);
await browserApp.close();
await browserServer.close();
});
it('should throw when used after isConnected returns false', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await Promise.all([
browserApp.close(),
browserServer.close(),
new Promise(f => remote.once('disconnected', f)),
]);
expect(remote.isConnected()).toBe(false);
@ -156,21 +155,21 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Browser.disconnect', function() {
it('should reject navigation when browser closes', async({server}) => {
server.setRoute('/one-style.css', () => {});
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const navigationPromise = page.goto(server.PREFIX + '/one-style.html', {timeout: 60000}).catch(e => e);
await server.waitForRequest('/one-style.css');
await remote.disconnect();
const error = await navigationPromise;
expect(error.message).toBe('Navigation failed because browser has disconnected!');
await browserApp.close();
await browserServer.close();
});
it('should reject waitForSelector when browser closes', async({server}) => {
server.setRoute('/empty.html', () => {});
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
const watchdog = page.waitForSelector('div', { timeout: 60000 }).catch(e => e);
// Make sure the previous waitForSelector has time to make it to the browser before we disconnect.
@ -179,28 +178,28 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await remote.disconnect();
const error = await watchdog;
expect(error.message).toContain('Protocol error');
await browserApp.close();
await browserServer.close();
});
it('should throw if used after disconnect', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
await remote.disconnect();
const error = await page.evaluate('1 + 1').catch(e => e);
expect(error.message).toContain('has been closed');
await browserApp.close();
await browserServer.close();
});
});
describe('Browser.close', function() {
it('should terminate network waiters', async({context, server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const newPage = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const newPage = await remote.newPage();
const results = await Promise.all([
newPage.waitForRequest(server.EMPTY_PAGE).catch(e => e),
newPage.waitForResponse(server.EMPTY_PAGE).catch(e => e),
browserApp.close()
browserServer.close()
]);
for (let i = 0; i < 2; i++) {
const message = results[i].message;
@ -209,10 +208,10 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
}
});
it('should be able to close remote browser', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
await Promise.all([
new Promise(f => browserApp.once('close', f)),
new Promise(f => browserServer.once('close', f)),
remote.close(),
]);
});
@ -220,36 +219,36 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.launch |webSocket| option', function() {
it('should support the webSocket option', async() => {
const options = Object.assign({}, defaultBrowserOptions);
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browserApp.wsEndpoint()).not.toBe(null);
const page = await browser.defaultContext().newPage();
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
expect((await browserContext.pages()).length).toBe(0);
expect(browserServer.wsEndpoint()).not.toBe(null);
const page = await browserContext.newPage();
expect(await page.evaluate('11 * 11')).toBe(121);
await page.close();
await browserApp.close();
await browserServer.close();
});
it('should fire "disconnected" when closing with webSocket', async() => {
const options = Object.assign({}, defaultBrowserOptions);
const browserApp = await playwright.launchBrowserApp(options);
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const disconnectedEventPromise = new Promise(resolve => browser.once('disconnected', resolve));
browserApp.kill();
browserServer.kill();
await disconnectedEventPromise;
});
});
describe('Playwright.connect', function() {
it.skip(WEBKIT)('should be able to reconnect to a browser', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await browser.defaultContext().newPage();
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const browserContext = await browser.newContext();
const page = await browserContext.newPage();
await page.goto(server.PREFIX + '/frames/nested-frames.html');
await browser.disconnect();
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const pages = await remote.defaultContext().pages();
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const pages = await remote.pages();
const restoredPage = pages.find(page => page.url() === server.PREFIX + '/frames/nested-frames.html');
expect(utils.dumpFrames(restoredPage.mainFrame())).toEqual([
'http://localhost:<PORT>/frames/nested-frames.html',
@ -259,36 +258,19 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
' http://localhost:<PORT>/frames/frame.html (uno)',
]);
expect(await restoredPage.evaluate(() => 7 * 8)).toBe(56);
await browserApp.close();
await browserServer.close();
});
});
describe('Playwright.launch({userDataDir})', function() {
describe('Playwright.launchPersistent', function() {
it('userDataDir option', async({server}) => {
const userDataDir = await mkdtempAsync(TMP_FOLDER);
const options = Object.assign({userDataDir}, defaultBrowserOptions);
const browser = await playwright.launch(options);
const browserContext = await playwright.launchPersistent(options);
// Open a page to make sure its functional.
await browser.defaultContext().newPage();
await browserContext.newPage();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
await browser.close();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await rmAsync(userDataDir).catch(e => {});
});
it('userDataDir argument', async({server}) => {
const userDataDir = await mkdtempAsync(TMP_FOLDER);
const options = Object.assign({}, defaultBrowserOptions);
options.args = [...(defaultBrowserOptions.args || [])];
if (FFOX)
options.args.push('-profile', userDataDir);
else
options.args.push(`--user-data-dir=${userDataDir}`);
const browser = await playwright.launch(options);
// Open a page to make sure its functional.
await browser.defaultContext().newPage();
expect(fs.readdirSync(userDataDir).length).toBeGreaterThan(0);
await browser.close();
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 => {});
@ -296,23 +278,23 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
it.skip(FFOX)('userDataDir option should restore state', async({server}) => {
const userDataDir = await mkdtempAsync(TMP_FOLDER);
const options = Object.assign({userDataDir}, defaultBrowserOptions);
const browser = await playwright.launch(options);
const page = await browser.defaultContext().newPage();
const browserContext = await playwright.launchPersistent(options);
const page = await browserContext.newPage();
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => localStorage.hey = 'hello');
await browser.close();
await browserContext.close();
const browser2 = await playwright.launch(options);
const page2 = await browser2.defaultContext().newPage();
const browserContext2 = await playwright.launchPersistent(options);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => localStorage.hey)).toBe('hello');
await browser2.close();
await browserContext2.close();
const browser3 = await playwright.launch(defaultBrowserOptions);
const page3 = await browser3.defaultContext().newPage();
const browserContext3 = await playwright.launchPersistent(defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('hello');
await browser3.close();
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await rmAsync(userDataDir).catch(e => {});
@ -321,23 +303,23 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
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 browser = await playwright.launch(options);
const page = await browser.defaultContext().newPage();
const browserContext = await playwright.launchPersistent(options);
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 browser.close();
await browserContext.close();
const browser2 = await playwright.launch(options);
const page2 = await browser2.defaultContext().newPage();
const browserContext2 = await playwright.launchPersistent(options);
const page2 = await browserContext2.newPage();
await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true');
await browser2.close();
await browserContext2.close();
const browser3 = await playwright.launch(defaultBrowserOptions);
const page3 = await browser3.defaultContext().newPage();
const browserContext3 = await playwright.launchPersistent(defaultBrowserOptions);
const page3 = await browserContext3.newPage();
await page3.goto(server.EMPTY_PAGE);
expect(await page3.evaluate(() => localStorage.hey)).not.toBe('doSomethingOnlyOnce=true');
await browser3.close();
await browserContext3.close();
// This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
await rmAsync(userDataDir).catch(e => {});

Просмотреть файл

@ -24,23 +24,23 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('BrowserContext', function() {
it('should work across sessions', async () => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
expect(browser.browserContexts().length).toBe(1);
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
expect(browser.browserContexts().length).toBe(0);
await browser.newContext();
expect(browser.browserContexts().length).toBe(2);
const remoteBrowser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
expect(browser.browserContexts().length).toBe(1);
const remoteBrowser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const contexts = remoteBrowser.browserContexts();
expect(contexts.length).toBe(2);
await browserApp.close();
expect(contexts.length).toBe(1);
await browserServer.close();
});
});
describe('Browser.Events.disconnected', function() {
it('should be emitted when: browser gets closed, disconnected or underlying websocket gets closed', async () => {
const browserApp = await playwright.launchBrowserApp({ ...defaultBrowserOptions });
const originalBrowser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const wsEndpoint = browserApp.wsEndpoint();
const browserServer = await playwright.launchServer(defaultBrowserOptions);
const originalBrowser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const wsEndpoint = browserServer.wsEndpoint();
const remoteBrowser1 = await playwright.connect({ wsEndpoint });
const remoteBrowser2 = await playwright.connect({ wsEndpoint });
@ -63,7 +63,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
await Promise.all([
utils.waitEvent(remoteBrowser1, 'disconnected'),
utils.waitEvent(originalBrowser, 'disconnected'),
browserApp.close(),
browserServer.close(),
]);
expect(disconnectedOriginal).toBe(1);
@ -74,21 +74,21 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
describe('Playwright.connect', function() {
it('should be able to connect multiple times to the same browser', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const local = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await remote.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const local = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await remote.newPage();
expect(await page.evaluate(() => 7 * 8)).toBe(56);
remote.disconnect();
const secondPage = await local.defaultContext().newPage();
const secondPage = await local.newPage();
expect(await secondPage.evaluate(() => 7 * 6)).toBe(42, 'original browser should still work');
await browserApp.close();
await browserServer.close();
});
it('should be able to close remote browser', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const local = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const remote = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const local = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const remote = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
await Promise.all([
utils.waitEvent(local, 'disconnected'),
remote.close(),
@ -96,15 +96,15 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
// @see https://github.com/GoogleChrome/puppeteer/issues/4197#issuecomment-481793410
it('should be able to connect to the same page simultaneously', async({server}) => {
const browserApp = await playwright.launchBrowserApp({...defaultBrowserOptions });
const browser1 = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page1 = await browser1.defaultContext().newPage();
const browserServer = await playwright.launchServer({...defaultBrowserOptions });
const browser1 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page1 = await browser1.newPage();
await page1.goto(server.EMPTY_PAGE);
const browser2 = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page2 = (await browser2.defaultContext().pages()).find(page => page.url() === server.EMPTY_PAGE);
const browser2 = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page2 = (await browser2.pages()).find(page => page.url() === server.EMPTY_PAGE);
expect(await page1.evaluate(() => 7 * 8)).toBe(56);
expect(await page2.evaluate(() => 7 * 6)).toBe(42);
await browserApp.close();
await browserServer.close();
});
});
};

Просмотреть файл

@ -92,14 +92,14 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
describe('Browser', function() {
beforeAll(async state => {
state.browser = await playwright.launch();
state.browserApp = state.browser.__app__;
state.browser = await playwright.launch(defaultBrowserOptions);
state.browserServer = state.browser.__server__;
});
afterAll(async state => {
await state.browserApp.close();
await state.browserServer.close();
state.browser = null;
state.browserApp = null;
state.browserServer = null;
});
beforeEach(async(state, test) => {
@ -107,8 +107,8 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
const onLine = (line) => test.output += line + '\n';
let rl;
if (state.browserApp.process().stderr) {
rl = require('readline').createInterface({ input: state.browserApp.process().stderr });
if (state.browserServer.process().stderr) {
rl = require('readline').createInterface({ input: state.browserServer.process().stderr });
test.output = '';
rl.on('line', onLine);
}
@ -190,6 +190,7 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
});
// Browser-level tests that are given a browser.
testRunner.loadTests(require('./browser.spec.js'), testOptions);
testRunner.loadTests(require('./browsercontext.spec.js'), testOptions);
testRunner.loadTests(require('./ignorehttpserrors.spec.js'), testOptions);
});

Просмотреть файл

@ -21,7 +21,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
(CHROMIUM || FFOX) && describe('Web SDK', function() {
beforeAll(async state => {
state.controlledBrowserApp = await playwright.launchBrowserApp(defaultBrowserOptions);
state.controlledBrowserApp = await playwright.launchServer(defaultBrowserOptions);
state.hostBrowser = await playwright.launch(defaultBrowserOptions);
});
@ -35,7 +35,7 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p
});
beforeEach(async state => {
state.page = await state.hostBrowser.defaultContext().newPage();
state.page = await state.hostBrowser.newPage();
state.page.on('console', message => console.log('TEST: ' + message.text()));
await state.page.goto(state.sourceServer.PREFIX + '/test/assets/playwrightweb.html');
await state.page.evaluate((product, wsEndpoint) => setup(product, wsEndpoint), product.toLowerCase(), state.controlledBrowserApp.wsEndpoint());

Просмотреть файл

@ -30,12 +30,12 @@ const {describe, xdescribe, fdescribe} = runner;
const {it, fit, xit} = runner;
const {beforeAll, beforeEach, afterAll, afterEach} = runner;
let browser;
let browserContext;
let page;
beforeAll(async function() {
browser = await playwright.launch();
page = await browser.defaultContext().newPage();
page = await browser.newPage();
});
afterAll(async function() {

Просмотреть файл

@ -47,7 +47,7 @@ async function run() {
messages.push(...await preprocessor.ensureReleasedAPILinks([readme], VERSION));
const browser = await playwright.launch();
const page = await browser.defaultContext().newPage();
const page = await browser.newPage();
const checkPublicAPI = require('./check_public_api');
const jsSources = await Source.readdir(path.join(PROJECT_DIR, 'src'));
const externalDependencies = Object.keys(require('../../src/web.webpack.config').externals);

Просмотреть файл

@ -10,13 +10,11 @@ async function generateChromiunProtocol(revision) {
if (revision.local && fs.existsSync(outputPath))
return;
const playwright = await require('../../index').chromium;
const browserApp = await playwright.launchBrowserApp({ executablePath: revision.executablePath });
const origin = browserApp.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const browser = await playwright.connect({ wsEndpoint: browserApp.wsEndpoint() });
const page = await browser.defaultContext().newPage();
const browserContext = await playwright.launchPersistent({ executablePath: revision.executablePath });
const page = await browserContext.newPage();
await page.goto(`http://${origin}/json/protocol`);
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
await browserApp.close();
await browserContext.close();
fs.writeFileSync(outputPath, jsonToTS(json));
console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`);
}