feat(launch): introduce client, server & persistent launch modes (2) (#840)
This commit is contained in:
Родитель
0f1a42a5d3
Коммит
a2ab645e63
16
README.md
16
README.md
|
@ -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,
|
||||
|
|
176
docs/api.md
176
docs/api.md
|
@ -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 [ICU’s `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> {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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`);
|
||||
})();
|
||||
|
|
|
@ -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)}`);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче