feat(context): start moving overrides to the context level

This commit is contained in:
Pavel Feldman 2019-12-18 12:23:33 -08:00 committed by GitHub
parent f56726759b
commit 6d0dfd0abf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 479 additions and 664 deletions

View file

@ -32,15 +32,13 @@
* [browser.browserContexts()](#browserbrowsercontexts) * [browser.browserContexts()](#browserbrowsercontexts)
* [browser.chromium](#browserchromium) * [browser.chromium](#browserchromium)
* [browser.close()](#browserclose) * [browser.close()](#browserclose)
* [browser.defaultBrowserContext()](#browserdefaultbrowsercontext) * [browser.defaultContext()](#browserdefaultContext())
* [browser.disconnect()](#browserdisconnect) * [browser.disconnect()](#browserdisconnect)
* [browser.isConnected()](#browserisconnected) * [browser.isConnected()](#browserisconnected)
* [browser.newContext()](#browsernewcontext) * [browser.newContext(options)](#browsernewcontextoptions)
* [browser.newPage()](#browsernewpage) * [browser.newPage(options)](#browsernewpageoptions)
* [browser.pages()](#browserpages) * [browser.pages()](#browserpages)
* [browser.process()](#browserprocess) * [browser.process()](#browserprocess)
* [browser.userAgent()](#browseruseragent)
* [browser.version()](#browserversion)
- [class: BrowserContext](#class-browsercontext) - [class: BrowserContext](#class-browsercontext)
* [browserContext.browser()](#browsercontextbrowser) * [browserContext.browser()](#browsercontextbrowser)
* [browserContext.clearCookies()](#browsercontextclearcookies) * [browserContext.clearCookies()](#browsercontextclearcookies)
@ -48,12 +46,12 @@
* [browserContext.cookies([...urls])](#browsercontextcookiesurls) * [browserContext.cookies([...urls])](#browsercontextcookiesurls)
* [browserContext.isIncognito()](#browsercontextisincognito) * [browserContext.isIncognito()](#browsercontextisincognito)
* [browserContext.newPage()](#browsercontextnewpage) * [browserContext.newPage()](#browsercontextnewpage)
* [browserContext.overrides](#browsercontextoverrides)
* [browserContext.pages()](#browsercontextpages) * [browserContext.pages()](#browsercontextpages)
* [browserContext.permissions](#browsercontextpermissions) * [browserContext.permissions](#browsercontextpermissions)
* [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies) * [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
- [class: Overrides](#class-overrides) - [class: Overrides](#class-overrides)
* [overrides.setGeolocation(options)](#overridessetgeolocationoptions) * [overrides.setGeolocation(options)](#overridessetgeolocationoptions)
* [overrides.setTimezone(timezoneId)](#overridessettimezonetimezoneid)
- [class: Permissions](#class-permissions) - [class: Permissions](#class-permissions)
* [permissions.clearOverrides()](#permissionsclearoverrides) * [permissions.clearOverrides()](#permissionsclearoverrides)
* [permissions.override(origin, permissions)](#permissionsoverrideorigin-permissions) * [permissions.override(origin, permissions)](#permissionsoverrideorigin-permissions)
@ -88,8 +86,6 @@
* [page.content()](#pagecontent) * [page.content()](#pagecontent)
* [page.coverage](#pagecoverage) * [page.coverage](#pagecoverage)
* [page.dblclick(selector[, options])](#pagedblclickselector-options) * [page.dblclick(selector[, options])](#pagedblclickselector-options)
* [page.emulate(options)](#pageemulateoptions)
* [page.emulateMedia(options)](#pageemulatemediaoptions)
* [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args) * [page.evaluate(pageFunction[, ...args])](#pageevaluatepagefunction-args)
* [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args) * [page.evaluateHandle(pageFunction[, ...args])](#pageevaluatehandlepagefunction-args)
* [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args) * [page.evaluateOnNewDocument(pageFunction[, ...args])](#pageevaluateonnewdocumentpagefunction-args)
@ -106,25 +102,19 @@
* [page.keyboard](#pagekeyboard) * [page.keyboard](#pagekeyboard)
* [page.mainFrame()](#pagemainframe) * [page.mainFrame()](#pagemainframe)
* [page.mouse](#pagemouse) * [page.mouse](#pagemouse)
* [page.overrides](#pageoverrides)
* [page.pdf](#pagepdf) * [page.pdf](#pagepdf)
* [page.reload([options])](#pagereloadoptions) * [page.reload([options])](#pagereloadoptions)
* [page.screenshot([options])](#pagescreenshotoptions) * [page.screenshot([options])](#pagescreenshotoptions)
* [page.select(selector, ...values)](#pageselectselector-values) * [page.select(selector, ...values)](#pageselectselector-values)
* [page.setBypassCSP(enabled)](#pagesetbypasscspenabled)
* [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled) * [page.setCacheEnabled([enabled])](#pagesetcacheenabledenabled)
* [page.setContent(html[, options])](#pagesetcontenthtml-options) * [page.setContent(html[, options])](#pagesetcontenthtml-options)
* [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) * [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout)
* [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) * [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout)
* [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) * [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
* [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled)
* [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
* [page.setViewport(viewport)](#pagesetviewportviewport)
* [page.title()](#pagetitle) * [page.title()](#pagetitle)
* [page.tripleclick(selector[, options])](#pagetripleclickselector-options) * [page.tripleclick(selector[, options])](#pagetripleclickselector-options)
* [page.type(selector, text[, options])](#pagetypeselector-text-options) * [page.type(selector, text[, options])](#pagetypeselector-text-options)
* [page.url()](#pageurl) * [page.url()](#pageurl)
* [page.viewport()](#pageviewport)
* [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args) * [page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])](#pagewaitforselectororfunctionortimeout-options-args)
* [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions) * [page.waitForFileChooser([options])](#pagewaitforfilechooseroptions)
* [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args) * [page.waitForFunction(pageFunction[, options[, ...args]])](#pagewaitforfunctionpagefunction-options-args)
@ -381,14 +371,6 @@ const playwright = require('playwright');
- `options` <[Object]> - `options` <[Object]>
- `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to. - `browserWSEndpoint` <?[string]> a [browser websocket endpoint](#browserwsendpoint) to connect to.
- `browserURL` <?[string]> a browser url to connect to, in format `http://${host}:${port}`. Use interchangeably with `browserWSEndpoint` to let Playwright fetch it from [metadata endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target). - `browserURL` <?[string]> a browser url to connect to, in format `http://${host}:${port}`. Use interchangeably with `browserWSEndpoint` to let Playwright fetch it from [metadata endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target).
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `defaultViewport` <?[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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use. - `transport` <[ConnectionTransport]> **Experimental** Specify a custom transport object for Playwright to use.
- returns: <[Promise]<[Browser]>> - returns: <[Promise]<[Browser]>>
@ -476,17 +458,9 @@ try {
#### playwright.launch([options]) #### playwright.launch([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields: - `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:
- `ignoreHTTPSErrors` <[boolean]> Whether to ignore HTTPS errors during navigation. Defaults to `false`.
- `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`. - `headless` <[boolean]> Whether to run browser in [headless mode](https://developers.google.com/web/updates/2017/04/headless-chrome). Defaults to `true` unless the `devtools` option is `true`.
- `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. 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, use at your own risk. - `executablePath` <[string]> Path to a Chromium or Chrome executable to run instead of the bundled Chromium. 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, 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. - `slowMo` <[number]> Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
- `defaultViewport` <?[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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `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/). - `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 [`playwright.defaultArgs()`](#playwrightdefaultargsoptions). If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. - `ignoreDefaultArgs` <[boolean]|[Array]<[string]>> If `true`, then do not use [`playwright.defaultArgs()`](#playwrightdefaultargsoptions). 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`. - `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
@ -627,7 +601,7 @@ a single instance of [BrowserContext].
Closes Chromium and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore. Closes Chromium and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
#### browser.defaultBrowserContext() #### browser.defaultContext()
- returns: <[BrowserContext]> - returns: <[BrowserContext]>
Returns the default browser context. The default browser context can not be closed. Returns the default browser context. The default browser context can not be closed.
@ -642,7 +616,23 @@ Disconnects Playwright from the browser, but leaves the Chromium process running
Indicates that the browser is connected. Indicates that the browser is connected.
#### browser.newContext() #### browser.newContext(options)
- `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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `userAgent` <?[string]> Specific user agent to use in this page
- `mediaType` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `colorScheme` <?"dark"|"light"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the page. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs.
- returns: <[Promise]<[BrowserContext]>> - returns: <[Promise]<[BrowserContext]>>
Creates a new browser context. It won't share cookies/cache with other browser contexts. Creates a new browser context. It won't share cookies/cache with other browser contexts.
@ -659,10 +649,24 @@ Creates a new browser context. It won't share cookies/cache with other browser c
})(); })();
``` ```
#### browser.newPage() #### browser.newPage(options)
- returns: <[Promise]<[Page]>> - `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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `userAgent` <?[string]> Specific user agent to use in this page
- `mediaType` <?[string]> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `colorScheme` <?"dark"|"light"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- `javaScriptEnabled` <?[boolean]> Whether or not to enable or disable JavaScript in the page. Defaults to true.
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs.
Promise which resolves to a new [Page] object. The [Page] is created in a default browser context. Promise which resolves to a new [Page] object. The [Page] is created in a new browser context that it will own. Closing this page will close the context.
#### browser.pages() #### browser.pages()
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage). - returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage).
@ -673,16 +677,6 @@ the method will return an array with all the pages in all browser contexts.
#### browser.process() #### browser.process()
- returns: <?[ChildProcess]> Spawned browser process. Returns `null` if the browser instance was created with [`playwright.connect`](#playwrightconnectoptions) method. - returns: <?[ChildProcess]> Spawned browser process. Returns `null` if the browser instance was created with [`playwright.connect`](#playwrightconnectoptions) method.
#### browser.userAgent()
- returns: <[Promise]<[string]>> Promise which resolves to the browser's original user agent.
> **NOTE** Pages can override browser user agent with [page.setUserAgent](#pagesetuseragentuseragent)
#### browser.version()
- returns: <[Promise]<[string]>> For headless Chromium, this is similar to `HeadlessChrome/61.0.3153.0`. For non-headless, this is similar to `Chrome/61.0.3153.0`.
> **NOTE** the format of browser.version() might change with future releases of Chromium.
### class: BrowserContext ### class: BrowserContext
* extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) * extends: [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter)
@ -755,6 +749,9 @@ The default browser context is the only non-incognito browser context.
Creates a new page in the browser context. Creates a new page in the browser context.
#### browserContext.overrides
- returns: <[Overrides]>
#### browserContext.pages() #### browserContext.pages()
- returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage). - returns: <[Promise]<[Array]<[Page]>>> Promise which resolves to an array of all open pages. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using [target.page()](#targetpage).
@ -792,15 +789,11 @@ await browserContext.setCookies([cookieObject1, cookieObject2]);
Sets the page's geolocation. Sets the page's geolocation.
```js ```js
await page.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667}); await browserContext.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667});
``` ```
> **NOTE** Consider using [browserContext.permissions.override](#permissionsoverrideorigin-permissions) to grant permissions for the page to read its geolocation. > **NOTE** Consider using [browserContext.permissions.override](#permissionsoverrideorigin-permissions) to grant permissions for the page to read its geolocation.
#### overrides.setTimezone(timezoneId)
- `timezoneId` <?[string]> Changes the timezone of the page. See [ICUs `metaZones.txt`](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) for a list of supported timezone IDs. Passing `null` disables timezone emulation.
- returns: <[Promise]>
### class: Permissions ### class: Permissions
#### permissions.clearOverrides() #### permissions.clearOverrides()
@ -809,7 +802,7 @@ await page.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667});
Clears all permission overrides for the browser context. Clears all permission overrides for the browser context.
```js ```js
const context = browser.defaultBrowserContext(); const context = browser.defaultContext();
context.permissions.override('https://example.com', ['clipboard-read']); context.permissions.override('https://example.com', ['clipboard-read']);
// do stuff .. // do stuff ..
context.permissions.clearOverrides(); context.permissions.clearOverrides();
@ -838,7 +831,7 @@ context.permissions.clearOverrides();
```js ```js
const context = browser.defaultBrowserContext(); const context = browser.defaultContext();
await context.permissions.override('https://html5demos.com', ['geolocation']); await context.permissions.override('https://html5demos.com', ['geolocation']);
``` ```
@ -1150,77 +1143,6 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
Shortcut for [page.mainFrame().dblclick(selector[, options])](#framedblclickselector-options). Shortcut for [page.mainFrame().dblclick(selector[, options])](#framedblclickselector-options).
#### page.emulate(options)
- `options` <[Object]>
- `viewport` <[Object]>
- `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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- `userAgent` <[string]>
- returns: <[Promise]>
Emulates given device metrics and user agent. This method is a shortcut for calling two methods:
- [page.setUserAgent(userAgent)](#pagesetuseragentuseragent)
- [page.setViewport(viewport)](#pagesetviewportviewport)
To aid emulation, playwright provides a list of device descriptors which can be obtained via the [`playwright.devices`](#playwrightdevices).
`page.emulate` will resize the page. A lot of websites don't expect phones to change size, so you should emulate before navigating to the page.
```js
const playwright = require('playwright');
const iPhone = playwright.devices['iPhone 6'];
(async () => {
const browser = await playwright.launch();
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://www.google.com');
// other actions...
await browser.close();
})();
```
List of all available devices is available in the source code: [DeviceDescriptors.js](https://github.com/Microsoft/playwright/blob/master/lib/DeviceDescriptors.js).
#### page.emulateMedia(options)
- `options` <[Object]>
- `type` <?[string]> Optional. Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
- `colorScheme` <"dark"|"light"|"no-preference"> Optional. Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`.
- returns: <[Promise]>
```js
await page.evaluate(() => matchMedia('screen').matches));
// → true
await page.evaluate(() => matchMedia('print').matches));
// → true
await page.emulateMedia({ type: 'print' });
await page.evaluate(() => matchMedia('screen').matches));
// → false
await page.evaluate(() => matchMedia('print').matches));
// → true
await page.emulateMedia({});
await page.evaluate(() => matchMedia('screen').matches));
// → true
await page.evaluate(() => matchMedia('print').matches));
// → true
```
```js
await page.emulateMedia({ colorScheme: 'dark' }] });
await page.evaluate(() => matchMedia('(prefers-color-scheme: dark)').matches));
// → true
await page.evaluate(() => matchMedia('(prefers-color-scheme: light)').matches));
// → false
await page.evaluate(() => matchMedia('(prefers-color-scheme: no-preference)').matches));
// → false
```
#### page.evaluate(pageFunction[, ...args]) #### page.evaluate(pageFunction[, ...args])
- `pageFunction` <[function]|[string]> Function to be evaluated in the page context - `pageFunction` <[function]|[string]> Function to be evaluated in the page context
- `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction` - `...args` <...[Serializable]|[JSHandle]> Arguments to pass to `pageFunction`
@ -1480,9 +1402,6 @@ Page is guaranteed to have a main frame which persists during navigations.
- returns: <[Mouse]> - returns: <[Mouse]>
#### page.overrides
- returns: <[Overrides]>
#### page.pdf #### page.pdf
- returns: <[PDF]> - returns: <[PDF]>
@ -1539,15 +1458,6 @@ page.select('select#colors', { value: 'blue' }, { index: 2 }, 'red');
Shortcut for [page.mainFrame().select()](#frameselectselector-values) Shortcut for [page.mainFrame().select()](#frameselectselector-values)
#### page.setBypassCSP(enabled)
- `enabled` <[boolean]> sets bypassing of page's Content-Security-Policy.
- returns: <[Promise]>
Toggles bypassing page's Content-Security-Policy.
> **NOTE** CSP bypassing happens at the moment of CSP initialization rather then evaluation. Usually this means
that `page.setBypassCSP` should be called before navigating to the domain.
#### page.setCacheEnabled([enabled]) #### page.setCacheEnabled([enabled])
- `enabled` <[boolean]> sets the `enabled` state of the cache. - `enabled` <[boolean]> sets the `enabled` state of the cache.
- returns: <[Promise]> - returns: <[Promise]>
@ -1607,42 +1517,6 @@ The extra HTTP headers will be sent with every request the page initiates.
> **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests. > **NOTE** page.setExtraHTTPHeaders does not guarantee the order of headers in the outgoing requests.
#### page.setJavaScriptEnabled(enabled)
- `enabled` <[boolean]> Whether or not to enable JavaScript on the page.
- returns: <[Promise]>
> **NOTE** changing this value won't affect scripts that have already been run. It will take full effect on the next [navigation](#pagegotourl-options).
#### page.setUserAgent(userAgent)
- `userAgent` <[string]> Specific user agent to use in this page
- returns: <[Promise]> Promise which resolves when the user agent is set.
#### page.setViewport(viewport)
- `viewport` <[Object]>
- `width` <[number]> page width in pixels. **required**
- `height` <[number]> page height in pixels. **required**
- `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`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
- returns: <[Promise]>
> **NOTE** in certain cases, setting viewport will reload the page in order to set the `isMobile` or `hasTouch` properties.
In the case of multiple pages in a single browser, each page can have its own viewport size.
`page.setViewport` will resize the page. A lot of websites don't expect phones to change size, so you should set the viewport before navigating to the page.
```js
const page = await browser.newPage();
await page.setViewport({
width: 640,
height: 480,
deviceScaleFactor: 1,
});
await page.goto('https://example.com');
```
#### page.title() #### page.title()
- returns: <[Promise]<[string]>> The page's title. - returns: <[Promise]<[string]>> The page's title.
@ -1691,15 +1565,6 @@ Shortcut for [page.mainFrame().type(selector, text[, options])](#frametypeselect
This is a shortcut for [page.mainFrame().url()](#frameurl) This is a shortcut for [page.mainFrame().url()](#frameurl)
#### page.viewport()
- returns: <?[Object]>
- `width` <[number]> page width in pixels.
- `height` <[number]> page height in pixels.
- `deviceScaleFactor` <[number]> Specify device scale factor (can be though of as dpr). Defaults to `1`.
- `isMobile` <[boolean]> Whether the `meta viewport` tag is taken into account. Defaults to `false`.
- `hasTouch`<[boolean]> Specifies if viewport supports touch events. Defaults to `false`
- `isLandscape` <[boolean]> Specifies if viewport is in landscape mode. Defaults to `false`.
#### page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]) #### page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]])
- `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for - `selectorOrFunctionOrTimeout` <[string]|[number]|[function]> A [selector], predicate or timeout to wait for
- `options` <[Object]> Optional waiting parameters - `options` <[Object]> Optional waiting parameters

View file

@ -17,14 +17,16 @@
import { assert } from './helper'; import { assert } from './helper';
import { Page } from './page'; import { Page } from './page';
import * as input from './input';
import * as network from './network'; import * as network from './network';
import * as types from './types';
import * as childProcess from 'child_process'; import * as childProcess from 'child_process';
export interface BrowserInterface { export interface BrowserInterface {
browserContexts(): BrowserContext[]; browserContexts(): BrowserContext[];
close(): Promise<void>; close(): Promise<void>;
newContext(): Promise<BrowserContext>; newContext(): Promise<BrowserContext>;
defaultBrowserContext(): BrowserContext; defaultContext(): BrowserContext;
newPage(): Promise<Page>; newPage(): Promise<Page>;
pages(): Promise<Page[]>; pages(): Promise<Page[]>;
process(): childProcess.ChildProcess | null; process(): childProcess.ChildProcess | null;
@ -41,15 +43,30 @@ export interface BrowserDelegate {
setContextCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>; setContextCookies(cookies: network.SetNetworkCookieParam[]): Promise<void>;
} }
export type BrowserContextOptions = {
viewport?: types.Viewport | null,
ignoreHTTPSErrors?: boolean,
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
mediaType?: input.MediaType,
colorScheme?: input.ColorScheme,
userAgent?: string,
timezoneId?: string
};
export class BrowserContext { export class BrowserContext {
private readonly _delegate: BrowserDelegate; private readonly _delegate: BrowserDelegate;
private readonly _browser: BrowserInterface; private readonly _browser: BrowserInterface;
private readonly _isIncognito: boolean; private readonly _isIncognito: boolean;
readonly _options: BrowserContextOptions;
constructor(delegate: BrowserDelegate, browser: BrowserInterface, isIncognito: boolean) { constructor(delegate: BrowserDelegate, browser: BrowserInterface, isIncognito: boolean, options: BrowserContextOptions) {
this._delegate = delegate; this._delegate = delegate;
this._browser = browser; this._browser = browser;
this._isIncognito = isIncognito; this._isIncognito = isIncognito;
this._options = options;
if (!options.viewport && options.viewport !== null)
options.viewport = { width: 800, height: 600 };
} }
async pages(): Promise<Page[]> { async pages(): Promise<Page[]> {
@ -64,6 +81,12 @@ export class BrowserContext {
return this._delegate.createPageInContext(); return this._delegate.createPageInContext();
} }
async _createOwnerPage(): Promise<Page> {
const page = await this._delegate.createPageInContext();
page._isContextOwner = true;
return page;
}
browser(): BrowserInterface { browser(): BrowserInterface {
return this._browser; return this._browser;
} }

View file

@ -19,20 +19,19 @@ import * as childProcess from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { Events } from './events'; import { Events } from './events';
import { assert, helper } from '../helper'; import { assert, helper } from '../helper';
import { BrowserContext, BrowserInterface } from '../browserContext'; import { BrowserContext, BrowserInterface, BrowserContextOptions } from '../browserContext';
import { Connection, ConnectionEvents, CDPSession } from './Connection'; import { Connection, ConnectionEvents, CDPSession } from './Connection';
import { Page } from '../page'; import { Page } from '../page';
import { Target } from './Target'; import { Target } from './Target';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import { Chromium } from './features/chromium'; import { Chromium } from './features/chromium';
import * as types from '../types';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import * as events from '../events';
import * as network from '../network'; import * as network from '../network';
import { Permissions } from './features/permissions'; import { Permissions } from './features/permissions';
import { Overrides } from './features/overrides';
export class Browser extends EventEmitter implements BrowserInterface { export class Browser extends EventEmitter implements BrowserInterface {
private _ignoreHTTPSErrors: boolean;
private _defaultViewport: types.Viewport;
private _process: childProcess.ChildProcess; private _process: childProcess.ChildProcess;
_connection: Connection; _connection: Connection;
_client: CDPSession; _client: CDPSession;
@ -45,11 +44,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
static async create( static async create(
connection: Connection, connection: Connection,
contextIds: string[], contextIds: string[],
ignoreHTTPSErrors: boolean,
defaultViewport: types.Viewport | null,
process: childProcess.ChildProcess | null, process: childProcess.ChildProcess | null,
closeCallback?: (() => Promise<void>)) { closeCallback?: (() => Promise<void>)) {
const browser = new Browser(connection, contextIds, ignoreHTTPSErrors, defaultViewport, process, closeCallback); const browser = new Browser(connection, contextIds, process, closeCallback);
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true }); await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
return browser; return browser;
} }
@ -57,22 +54,18 @@ export class Browser extends EventEmitter implements BrowserInterface {
constructor( constructor(
connection: Connection, connection: Connection,
contextIds: string[], contextIds: string[],
ignoreHTTPSErrors: boolean,
defaultViewport: types.Viewport | null,
process: childProcess.ChildProcess | null, process: childProcess.ChildProcess | null,
closeCallback?: (() => Promise<void>)) { closeCallback?: (() => Promise<void>)) {
super(); super();
this._connection = connection; this._connection = connection;
this._client = connection.rootSession; this._client = connection.rootSession;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport;
this._process = process; this._process = process;
this._closeCallback = closeCallback || (() => Promise.resolve()); this._closeCallback = closeCallback || (() => Promise.resolve());
this.chromium = new Chromium(this); this.chromium = new Chromium(this);
this._defaultContext = this._createBrowserContext(null); this._defaultContext = this._createBrowserContext(null, {});
for (const contextId of contextIds) for (const contextId of contextIds)
this._contexts.set(contextId, this._createBrowserContext(contextId)); this._contexts.set(contextId, this._createBrowserContext(contextId, {}));
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected)); this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected));
this._client.on('Target.targetCreated', this._targetCreated.bind(this)); this._client.on('Target.targetCreated', this._targetCreated.bind(this));
@ -80,8 +73,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this)); this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
} }
_createBrowserContext(contextId: string | null): BrowserContext { _createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext {
const isIncognito = !!contextId; const isIncognito = !!contextId;
let overrides: Overrides | null = null;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page[]> => { contextPages: async (): Promise<Page[]> => {
const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page'); const targets = this._allTargets().filter(target => target.browserContext() === context && target.type() === 'page');
@ -94,6 +88,25 @@ export class Browser extends EventEmitter implements BrowserInterface {
const target = this._targets.get(targetId); const target = this._targets.get(targetId);
assert(await target._initializedPromise, 'Failed to create target for page'); assert(await target._initializedPromise, 'Failed to create target for page');
const page = await target.page(); const page = await target.page();
const session = (page._delegate as FrameManager)._client;
const promises: Promise<any>[] = [ overrides._applyOverrides(page) ];
if (options.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (options.ignoreHTTPSErrors)
promises.push(session.send('Security.setIgnoreCertificateErrors', { ignore: true }));
if (options.viewport)
promises.push(page._delegate.setViewport(options.viewport));
if (options.javaScriptEnabled === false)
promises.push(session.send('Emulation.setScriptExecutionDisabled', { value: true }));
if (options.userAgent)
(page._delegate as FrameManager)._networkManager.setUserAgent(options.userAgent);
if (options.mediaType || options.colorScheme) {
const features = options.colorScheme ? [{ name: 'prefers-color-scheme', value: options.colorScheme }] : [];
promises.push(session.send('Emulation.setEmulatedMedia', { media: options.mediaType || '', features }));
}
if (options.timezoneId)
promises.push(emulateTimezone(session, options.timezoneId));
await Promise.all(promises);
return page; return page;
}, },
@ -119,8 +132,10 @@ export class Browser extends EventEmitter implements BrowserInterface {
setContextCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => { setContextCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => {
await this._client.send('Storage.setCookies', { cookies, browserContextId: contextId || undefined }); await this._client.send('Storage.setCookies', { cookies, browserContextId: contextId || undefined });
}, },
}, this, isIncognito); }, this, isIncognito, options);
overrides = new Overrides(context);
(context as any).permissions = new Permissions(this._client, contextId); (context as any).permissions = new Permissions(this._client, contextId);
(context as any).overrides = overrides;
return context; return context;
} }
@ -128,9 +143,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
return this._process; return this._process;
} }
async newContext(): Promise<BrowserContext> { async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
const {browserContextId} = await this._client.send('Target.createBrowserContext'); const { browserContextId } = await this._client.send('Target.createBrowserContext');
const context = this._createBrowserContext(browserContextId); const context = this._createBrowserContext(browserContextId, options);
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
@ -139,7 +154,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
defaultBrowserContext(): BrowserContext { defaultContext(): BrowserContext {
return this._defaultContext; return this._defaultContext;
} }
@ -148,7 +163,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
const {browserContextId} = targetInfo; const {browserContextId} = targetInfo;
const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext; const context = (browserContextId && this._contexts.has(browserContextId)) ? this._contexts.get(browserContextId) : this._defaultContext;
const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo), this._ignoreHTTPSErrors, this._defaultViewport); const target = new Target(targetInfo, context, () => this._connection.createSession(targetInfo));
assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated'); assert(!this._targets.has(event.targetInfo.targetId), 'Target should not exist before targetCreated');
this._targets.set(event.targetInfo.targetId, target); this._targets.set(event.targetInfo.targetId, target);
@ -175,8 +190,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
this.chromium.emit(Events.Chromium.TargetChanged, target); this.chromium.emit(Events.Chromium.TargetChanged, target);
} }
async newPage(): Promise<Page> { async newPage(options?: BrowserContextOptions): Promise<Page> {
return this._defaultContext.newPage(); const context = await this.newContext(options);
return context._createOwnerPage();
} }
async _closePage(page: Page) { async _closePage(page: Page) {
@ -250,3 +266,13 @@ export class Browser extends EventEmitter implements BrowserInterface {
return this._client.send('Browser.getVersion'); return this._client.send('Browser.getVersion');
} }
} }
async function emulateTimezone(session: CDPSession, timezoneId: string) {
try {
await session.send('Emulation.setTimezoneOverride', { timezoneId: timezoneId });
} catch (exception) {
if (exception.message.includes('Invalid timezone'))
throw new Error(`Invalid timezone ID: ${timezoneId}`);
throw exception;
}
}

View file

@ -33,7 +33,6 @@ import { Accessibility } from './features/accessibility';
import { Coverage } from './features/coverage'; import { Coverage } from './features/coverage';
import { PDF } from './features/pdf'; import { PDF } from './features/pdf';
import { Workers } from './features/workers'; import { Workers } from './features/workers';
import { Overrides } from './features/overrides';
import { Interception } from './features/interception'; import { Interception } from './features/interception';
import { Browser } from './Browser'; import { Browser } from './Browser';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
@ -46,24 +45,23 @@ const UTILITY_WORLD_NAME = '__playwright_utility_world__';
export class FrameManager implements PageDelegate { export class FrameManager implements PageDelegate {
_client: CDPSession; _client: CDPSession;
private _page: Page; private _page: Page;
private _networkManager: NetworkManager; readonly _networkManager: NetworkManager;
private _contextIdToContext = new Map<number, dom.FrameExecutionContext>(); private _contextIdToContext = new Map<number, dom.FrameExecutionContext>();
private _isolatedWorlds = new Set<string>(); private _isolatedWorlds = new Set<string>();
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
rawMouse: RawMouseImpl; rawMouse: RawMouseImpl;
rawKeyboard: RawKeyboardImpl; rawKeyboard: RawKeyboardImpl;
constructor(client: CDPSession, browserContext: BrowserContext, ignoreHTTPSErrors: boolean) { constructor(client: CDPSession, browserContext: BrowserContext) {
this._client = client; this._client = client;
this.rawKeyboard = new RawKeyboardImpl(client); this.rawKeyboard = new RawKeyboardImpl(client);
this.rawMouse = new RawMouseImpl(client); this.rawMouse = new RawMouseImpl(client);
this._page = new Page(this, browserContext); this._page = new Page(this, browserContext);
this._networkManager = new NetworkManager(client, ignoreHTTPSErrors, this._page); this._networkManager = new NetworkManager(client, this._page);
(this._page as any).accessibility = new Accessibility(client); (this._page as any).accessibility = new Accessibility(client);
(this._page as any).coverage = new Coverage(client); (this._page as any).coverage = new Coverage(client);
(this._page as any).pdf = new PDF(client); (this._page as any).pdf = new PDF(client);
(this._page as any).workers = new Workers(client, this._page._addConsoleMessage.bind(this._page), error => this._page.emit(Events.Page.PageError, error)); (this._page as any).workers = new Workers(client, this._page._addConsoleMessage.bind(this._page), error => this._page.emit(Events.Page.PageError, error));
(this._page as any).overrides = new Overrides(client);
(this._page as any).interception = new Interception(this._networkManager); (this._page as any).interception = new Interception(this._networkManager);
this._eventListeners = [ this._eventListeners = [
@ -276,18 +274,6 @@ export class FrameManager implements PageDelegate {
await this._client.send('Network.setExtraHTTPHeaders', { headers }); await this._client.send('Network.setExtraHTTPHeaders', { headers });
} }
setUserAgent(userAgent: string): Promise<void> {
return this._networkManager.setUserAgent(userAgent);
}
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
await this._client.send('Emulation.setScriptExecutionDisabled', { value: !enabled });
}
async setBypassCSP(enabled: boolean): Promise<void> {
await this._client.send('Page.setBypassCSP', { enabled });
}
async setViewport(viewport: types.Viewport): Promise<void> { async setViewport(viewport: types.Viewport): Promise<void> {
const { const {
width, width,
@ -306,7 +292,7 @@ export class FrameManager implements PageDelegate {
]); ]);
} }
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> { async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.ColorScheme | null): Promise<void> {
const features = mediaColorScheme ? [{ name: 'prefers-color-scheme', value: mediaColorScheme }] : []; const features = mediaColorScheme ? [{ name: 'prefers-color-scheme', value: mediaColorScheme }] : [];
await this._client.send('Emulation.setEmulatedMedia', { media: mediaType || '', features }); await this._client.send('Emulation.setEmulatedMedia', { media: mediaType || '', features });
} }

View file

@ -26,7 +26,6 @@ import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
import { Connection } from './Connection'; import { Connection } from './Connection';
import { TimeoutError } from '../errors'; import { TimeoutError } from '../errors';
import { assert, debugError, helper } from '../helper'; import { assert, debugError, helper } from '../helper';
import * as types from '../types';
import { ConnectionTransport, WebSocketTransport, PipeTransport } from '../transport'; import { ConnectionTransport, WebSocketTransport, PipeTransport } from '../transport';
import * as util from 'util'; import * as util from 'util';
import { launchProcess, waitForLine } from '../processLauncher'; import { launchProcess, waitForLine } from '../processLauncher';
@ -71,7 +70,7 @@ export class Launcher {
this._preferredRevision = preferredRevision; this._preferredRevision = preferredRevision;
} }
async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) = {}): Promise<Browser> { async launch(options: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) = {}): Promise<Browser> {
const { const {
ignoreDefaultArgs = false, ignoreDefaultArgs = false,
args = [], args = [],
@ -82,8 +81,6 @@ export class Launcher {
handleSIGINT = true, handleSIGINT = true,
handleSIGTERM = true, handleSIGTERM = true,
handleSIGHUP = true, handleSIGHUP = true,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0, slowMo = 0,
timeout = 30000 timeout = 30000
} = options; } = options;
@ -146,7 +143,7 @@ export class Launcher {
const transport = new PipeTransport(launched.process.stdio[3] as NodeJS.WritableStream, launched.process.stdio[4] as NodeJS.ReadableStream); const transport = new PipeTransport(launched.process.stdio[3] as NodeJS.WritableStream, launched.process.stdio[4] as NodeJS.ReadableStream);
connection = new Connection('', transport, slowMo); connection = new Connection('', transport, slowMo);
} }
const browser = await Browser.create(connection, [], ignoreHTTPSErrors, defaultViewport, launched.process, launched.gracefullyClose); const browser = await Browser.create(connection, [], launched.process, launched.gracefullyClose);
await browser._waitForTarget(t => t.type() === 'page'); await browser._waitForTarget(t => t.type() === 'page');
return browser; return browser;
} catch (e) { } catch (e) {
@ -184,15 +181,13 @@ export class Launcher {
return this._resolveExecutablePath().executablePath; return this._resolveExecutablePath().executablePath;
} }
async connect(options: (LauncherBrowserOptions & { async connect(options: (ConnectionOptions & {
browserWSEndpoint?: string; browserWSEndpoint?: string;
browserURL?: string; browserURL?: string;
transport?: ConnectionTransport; })): Promise<Browser> { transport?: ConnectionTransport; })): Promise<Browser> {
const { const {
browserWSEndpoint, browserWSEndpoint,
browserURL, browserURL,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
transport, transport,
slowMo = 0, slowMo = 0,
} = options; } = options;
@ -212,7 +207,7 @@ export class Launcher {
} }
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts'); const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
return Browser.create(connection, browserContextIds, ignoreHTTPSErrors, defaultViewport, null, async () => { return Browser.create(connection, browserContextIds, null, async () => {
connection.rootSession.send('Browser.close').catch(debugError); connection.rootSession.send('Browser.close').catch(debugError);
}); });
} }
@ -275,9 +270,7 @@ export type LauncherLaunchOptions = {
pipe?: boolean, pipe?: boolean,
}; };
export type LauncherBrowserOptions = { export type ConnectionOptions = {
ignoreHTTPSErrors?: boolean,
defaultViewport?: types.Viewport | null,
slowMo?: number, slowMo?: number,
}; };

View file

@ -24,7 +24,6 @@ import * as frames from '../frames';
export class NetworkManager { export class NetworkManager {
private _client: CDPSession; private _client: CDPSession;
private _ignoreHTTPSErrors: boolean;
private _page: Page; private _page: Page;
private _requestIdToRequest = new Map<string, InterceptableRequest>(); private _requestIdToRequest = new Map<string, InterceptableRequest>();
private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>(); private _requestIdToRequestWillBeSentEvent = new Map<string, Protocol.Network.requestWillBeSentPayload>();
@ -37,9 +36,8 @@ export class NetworkManager {
private _requestIdToInterceptionId = new Map<string, string>(); private _requestIdToInterceptionId = new Map<string, string>();
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
constructor(client: CDPSession, ignoreHTTPSErrors: boolean, page: Page) { constructor(client: CDPSession, page: Page) {
this._client = client; this._client = client;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._page = page; this._page = page;
this._eventListeners = [ this._eventListeners = [
@ -54,8 +52,6 @@ export class NetworkManager {
async initialize() { async initialize() {
await this._client.send('Network.enable'); await this._client.send('Network.enable');
if (this._ignoreHTTPSErrors)
await this._client.send('Security.setIgnoreCertificateErrors', {ignore: true});
} }
dispose() { dispose() {

View file

@ -20,7 +20,7 @@ import { BrowserFetcher, BrowserFetcherOptions, BrowserFetcherRevisionInfo, OnPr
import { ConnectionTransport } from '../transport'; import { ConnectionTransport } from '../transport';
import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors'; import { DeviceDescriptors, DeviceDescriptor } from '../deviceDescriptors';
import * as Errors from '../errors'; import * as Errors from '../errors';
import { Launcher, LauncherBrowserOptions, LauncherChromeArgOptions, LauncherLaunchOptions, createBrowserFetcher } from './Launcher'; import { Launcher, ConnectionOptions, LauncherChromeArgOptions, LauncherLaunchOptions, createBrowserFetcher } from './Launcher';
type Devices = { [name: string]: DeviceDescriptor } & DeviceDescriptor[]; type Devices = { [name: string]: DeviceDescriptor } & DeviceDescriptor[];
@ -42,11 +42,11 @@ export class Playwright {
return revisionInfo; return revisionInfo;
} }
launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & LauncherBrowserOptions) | undefined): Promise<Browser> { launch(options?: (LauncherLaunchOptions & LauncherChromeArgOptions & ConnectionOptions) | undefined): Promise<Browser> {
return this._launcher.launch(options); return this._launcher.launch(options);
} }
connect(options: (LauncherBrowserOptions & { connect(options: (ConnectionOptions & {
browserWSEndpoint?: string; browserWSEndpoint?: string;
browserURL?: string; browserURL?: string;
transport?: ConnectionTransport; })): Promise<Browser> { transport?: ConnectionTransport; })): Promise<Browser> {

View file

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
import * as types from '../types';
import { Browser } from './Browser'; import { Browser } from './Browser';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import { CDPSession, CDPSessionEvents } from './Connection'; import { CDPSession, CDPSessionEvents } from './Connection';
@ -33,8 +32,6 @@ export class Target {
private _browserContext: BrowserContext; private _browserContext: BrowserContext;
_targetId: string; _targetId: string;
private _sessionFactory: () => Promise<CDPSession>; private _sessionFactory: () => Promise<CDPSession>;
private _ignoreHTTPSErrors: boolean;
private _defaultViewport: types.Viewport;
private _pagePromise: Promise<Page> | null = null; private _pagePromise: Promise<Page> | null = null;
private _frameManager: FrameManager | null = null; private _frameManager: FrameManager | null = null;
private _workerPromise: Promise<Worker> | null = null; private _workerPromise: Promise<Worker> | null = null;
@ -49,15 +46,11 @@ export class Target {
constructor( constructor(
targetInfo: Protocol.Target.TargetInfo, targetInfo: Protocol.Target.TargetInfo,
browserContext: BrowserContext, browserContext: BrowserContext,
sessionFactory: () => Promise<CDPSession>, sessionFactory: () => Promise<CDPSession>) {
ignoreHTTPSErrors: boolean,
defaultViewport: types.Viewport | null) {
this._targetInfo = targetInfo; this._targetInfo = targetInfo;
this._browserContext = browserContext; this._browserContext = browserContext;
this._targetId = targetInfo.targetId; this._targetId = targetInfo.targetId;
this._sessionFactory = sessionFactory; this._sessionFactory = sessionFactory;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport;
this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => { this._initializedPromise = new Promise(fulfill => this._initializedCallback = fulfill).then(async success => {
if (!success) if (!success)
return false; return false;
@ -84,7 +77,7 @@ export class Target {
async page(): Promise<Page | null> { async page(): Promise<Page | null> {
if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) { if ((this._targetInfo.type === 'page' || this._targetInfo.type === 'background_page') && !this._pagePromise) {
this._pagePromise = this._sessionFactory().then(async client => { this._pagePromise = this._sessionFactory().then(async client => {
this._frameManager = new FrameManager(client, this._browserContext, this._ignoreHTTPSErrors); this._frameManager = new FrameManager(client, this._browserContext);
const page = this._frameManager.page(); const page = this._frameManager.page();
(page as any)[targetSymbol] = this; (page as any)[targetSymbol] = this;
client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect()); client.once(CDPSessionEvents.Disconnected, () => page._didDisconnect());
@ -96,8 +89,6 @@ export class Target {
}); });
await this._frameManager.initialize(); await this._frameManager.initialize();
await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true}); await client.send('Target.setAutoAttach', {autoAttach: true, waitForDebuggerOnStart: false, flatten: true});
if (this._defaultViewport)
await page.setViewport(this._defaultViewport);
return page; return page;
}); });
} }

View file

@ -15,16 +15,26 @@
* limitations under the License. * limitations under the License.
*/ */
import { CDPSession } from '../Connection'; import { BrowserContext } from '../../browserContext';
import { FrameManager } from '../FrameManager';
import { Page } from '../api';
export class Overrides { export class Overrides {
private _client: CDPSession; private _context: BrowserContext;
private _geolocation: { longitude?: number; latitude?: number; accuracy?: number; } | null = null;
constructor(client: CDPSession) { constructor(context: BrowserContext) {
this._client = client; this._context = context;
}
async setGeolocation(options: { longitude?: number; latitude?: number; accuracy?: (number | undefined); } | null) {
if (!options) {
for (const page of await this._context.pages())
await (page._delegate as FrameManager)._client.send('Emulation.clearGeolocationOverride', {});
this._geolocation = null;
return;
} }
async setGeolocation(options: { longitude: number; latitude: number; accuracy: (number | undefined); }) {
const { longitude, latitude, accuracy = 0} = options; const { longitude, latitude, accuracy = 0} = options;
if (longitude < -180 || longitude > 180) if (longitude < -180 || longitude > 180)
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`); throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
@ -32,16 +42,13 @@ export class Overrides {
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`); throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0) if (accuracy < 0)
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`); throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
await this._client.send('Emulation.setGeolocationOverride', {longitude, latitude, accuracy}); this._geolocation = { longitude, latitude, accuracy };
for (const page of await this._context.pages())
await (page._delegate as FrameManager)._client.send('Emulation.setGeolocationOverride', this._geolocation);
} }
async setTimezone(timezoneId: string | null) { async _applyOverrides(page: Page): Promise<void> {
try { if (this._geolocation)
await this._client.send('Emulation.setTimezoneOverride', {timezoneId: timezoneId || ''}); await (page._delegate as FrameManager)._client.send('Emulation.setGeolocationOverride', this._geolocation);
} catch (exception) {
if (exception.message.includes('Invalid timezone'))
throw new Error(`Invalid timezone ID: ${timezoneId}`);
throw exception;
}
} }
} }

View file

@ -123,7 +123,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'}); element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
} }
return false; return false;
}, !!this._page._state.javascriptEnabled); }, !!this._page.browserContext()._options.javaScriptEnabled);
if (error) if (error)
throw new Error(error); throw new Error(error);
} }

View file

@ -26,11 +26,10 @@ import * as types from '../types';
import { FrameManager } from './FrameManager'; import { FrameManager } from './FrameManager';
import { Firefox } from './features/firefox'; import { Firefox } from './features/firefox';
import * as network from '../network'; import * as network from '../network';
import { BrowserContext, BrowserInterface } from '../browserContext'; import { BrowserContext, BrowserInterface, BrowserContextOptions } from '../browserContext';
export class Browser extends EventEmitter implements BrowserInterface { export class Browser extends EventEmitter implements BrowserInterface {
_connection: Connection; _connection: Connection;
_defaultViewport: types.Viewport;
private _process: import('child_process').ChildProcess; private _process: import('child_process').ChildProcess;
private _closeCallback: () => Promise<void>; private _closeCallback: () => Promise<void>;
_targets: Map<string, Target>; _targets: Map<string, Target>;
@ -39,27 +38,26 @@ export class Browser extends EventEmitter implements BrowserInterface {
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
readonly firefox: Firefox; readonly firefox: Firefox;
static async create(connection: Connection, defaultViewport: types.Viewport | null, process: import('child_process').ChildProcess | null, closeCallback: () => Promise<void>) { static async create(connection: Connection, process: import('child_process').ChildProcess | null, closeCallback: () => Promise<void>) {
const {browserContextIds} = await connection.send('Target.getBrowserContexts'); const {browserContextIds} = await connection.send('Target.getBrowserContexts');
const browser = new Browser(connection, browserContextIds, defaultViewport, process, closeCallback); const browser = new Browser(connection, browserContextIds, process, closeCallback);
await connection.send('Target.enable'); await connection.send('Target.enable');
return browser; return browser;
} }
constructor(connection: Connection, browserContextIds: Array<string>, defaultViewport: types.Viewport | null, process: import('child_process').ChildProcess | null, closeCallback: () => Promise<void>) { constructor(connection: Connection, browserContextIds: Array<string>, process: import('child_process').ChildProcess | null, closeCallback: () => Promise<void>) {
super(); super();
this._connection = connection; this._connection = connection;
this._defaultViewport = defaultViewport;
this._process = process; this._process = process;
this._closeCallback = closeCallback; this._closeCallback = closeCallback;
this.firefox = new Firefox(this); this.firefox = new Firefox(this);
this._targets = new Map(); this._targets = new Map();
this._defaultContext = this._createBrowserContext(null); this._defaultContext = this._createBrowserContext(null, {});
this._contexts = new Map(); this._contexts = new Map();
for (const browserContextId of browserContextIds) for (const browserContextId of browserContextIds)
this._contexts.set(browserContextId, this._createBrowserContext(browserContextId)); this._contexts.set(browserContextId, this._createBrowserContext(browserContextId, {}));
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected)); this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected));
@ -78,9 +76,12 @@ export class Browser extends EventEmitter implements BrowserInterface {
return !this._connection._closed; return !this._connection._closed;
} }
async newContext(): Promise<BrowserContext> { async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
const {browserContextId} = await this._connection.send('Target.createBrowserContext'); const {browserContextId} = await this._connection.send('Target.createBrowserContext');
const context = this._createBrowserContext(browserContextId); // TODO: move ignoreHTTPSErrors to browser context level.
if (options.ignoreHTTPSErrors)
await this._connection.send('Browser.setIgnoreHTTPSErrors', { enabled: true });
const context = this._createBrowserContext(browserContextId, options);
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
@ -89,7 +90,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
defaultBrowserContext() { defaultContext() {
return this._defaultContext; return this._defaultContext;
} }
@ -114,7 +115,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
const existingTarget = this._allTargets().find(predicate); const existingTarget = this._allTargets().find(predicate);
if (existingTarget) if (existingTarget)
return existingTarget; return existingTarget;
let resolve; let resolve: (t: Target) => void;
const targetPromise = new Promise<Target>(x => resolve = x); const targetPromise = new Promise<Target>(x => resolve = x);
this.on('targetchanged', check); this.on('targetchanged', check);
try { try {
@ -131,8 +132,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
} }
} }
newPage(): Promise<Page> { async newPage(options?: BrowserContextOptions): Promise<Page> {
return this._defaultContext.newPage(); const context = await this.newContext(options);
return context._createOwnerPage();
} }
async pages() { async pages() {
@ -173,7 +175,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
await this._closeCallback(); await this._closeCallback();
} }
_createBrowserContext(browserContextId: string | null): BrowserContext { _createBrowserContext(browserContextId: string | null, options: BrowserContextOptions): BrowserContext {
const isIncognito = !!browserContextId; const isIncognito = !!browserContextId;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page[]> => { contextPages: async (): Promise<Page[]> => {
@ -187,7 +189,21 @@ export class Browser extends EventEmitter implements BrowserInterface {
browserContextId: browserContextId || undefined browserContextId: browserContextId || undefined
}); });
const target = this._targets.get(targetId); const target = this._targets.get(targetId);
return await target.page(); const page = await target.page();
const session = (page._delegate as FrameManager)._session;
const promises: Promise<any>[] = [];
if (options.viewport)
promises.push(page._delegate.setViewport(options.viewport));
if (options.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (options.javaScriptEnabled === false)
promises.push(session.send('Page.setJavascriptEnabled', { enabled: false }));
if (options.userAgent)
promises.push(session.send('Page.setUserAgent', { userAgent: options.userAgent }));
if (options.mediaType || options.colorScheme)
promises.push(session.send('Page.setEmulatedMedia', { type: options.mediaType, colorScheme: options.colorScheme }));
Promise.all(promises);
return page;
}, },
closeContext: async (): Promise<void> => { closeContext: async (): Promise<void> => {
@ -211,7 +227,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
setContextCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => { setContextCookies: async (cookies: network.SetNetworkCookieParam[]): Promise<void> => {
await this._connection.send('Browser.setCookies', { browserContextId: browserContextId || undefined, cookies }); await this._connection.send('Browser.setCookies', { browserContextId: browserContextId || undefined, cookies });
}, },
}, this, isIncognito); }, this, isIncognito, options);
(context as any).permissions = new Permissions(this._connection, browserContextId); (context as any).permissions = new Permissions(this._connection, browserContextId);
return context; return context;
} }
@ -267,8 +283,6 @@ export class Target {
const page = this._frameManager._page; const page = this._frameManager._page;
session.once(JugglerSessionEvents.Disconnected, () => page._didDisconnect()); session.once(JugglerSessionEvents.Disconnected, () => page._didDisconnect());
await this._frameManager._initialize(); await this._frameManager._initialize();
if (this._browser._defaultViewport)
await page.setViewport(this._browser._defaultViewport);
f(page); f(page);
}); });
} }

View file

@ -189,18 +189,6 @@ export class FrameManager implements PageDelegate {
await this._session.send('Network.setExtraHTTPHeaders', { headers: array }); await this._session.send('Network.setExtraHTTPHeaders', { headers: array });
} }
async setUserAgent(userAgent: string): Promise<void> {
await this._session.send('Page.setUserAgent', { userAgent });
}
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
await this._session.send('Page.setJavascriptEnabled', { enabled });
}
async setBypassCSP(enabled: boolean): Promise<void> {
await this._session.send('Page.setBypassCSP', { enabled });
}
async setViewport(viewport: types.Viewport): Promise<void> { async setViewport(viewport: types.Viewport): Promise<void> {
const { const {
width, width,
@ -215,7 +203,7 @@ export class FrameManager implements PageDelegate {
}); });
} }
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> { async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.ColorScheme | null): Promise<void> {
await this._session.send('Page.setEmulatedMedia', { await this._session.send('Page.setEmulatedMedia', {
type: mediaType === null ? undefined : mediaType, type: mediaType === null ? undefined : mediaType,
colorScheme: mediaColorScheme === null ? undefined : mediaColorScheme colorScheme: mediaColorScheme === null ? undefined : mediaColorScheme

View file

@ -69,8 +69,6 @@ export class Launcher {
handleSIGHUP = true, handleSIGHUP = true,
handleSIGINT = true, handleSIGINT = true,
handleSIGTERM = true, handleSIGTERM = true,
ignoreHTTPSErrors = false,
defaultViewport = {width: 800, height: 600},
slowMo = 0, slowMo = 0,
timeout = 30000, timeout = 30000,
} = options; } = options;
@ -129,9 +127,7 @@ export class Launcher {
const url = match[1]; const url = match[1];
const transport = await WebSocketTransport.create(url); const transport = await WebSocketTransport.create(url);
connection = new Connection(url, transport, slowMo); connection = new Connection(url, transport, slowMo);
const browser = await Browser.create(connection, defaultViewport, launched.process, launched.gracefullyClose); const browser = await Browser.create(connection, launched.process, launched.gracefullyClose);
if (ignoreHTTPSErrors)
await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
await browser._waitForTarget(t => t.type() === 'page'); await browser._waitForTarget(t => t.type() === 'page');
return browser; return browser;
} catch (e) { } catch (e) {
@ -144,16 +140,11 @@ export class Launcher {
const { const {
browserWSEndpoint, browserWSEndpoint,
slowMo = 0, slowMo = 0,
defaultViewport = {width: 800, height: 600},
ignoreHTTPSErrors = false,
} = options; } = options;
let connection = null; let connection = null;
const transport = await WebSocketTransport.create(browserWSEndpoint); const transport = await WebSocketTransport.create(browserWSEndpoint);
connection = new Connection(browserWSEndpoint, transport, slowMo); connection = new Connection(browserWSEndpoint, transport, slowMo);
const browser = await Browser.create(connection, defaultViewport, null, () => connection.send('Browser.close').catch(debugError)); return await Browser.create(connection, null, () => connection.send('Browser.close').catch(debugError));
if (ignoreHTTPSErrors)
await connection.send('Browser.setIgnoreHTTPSErrors', {enabled: true});
return browser;
} }
executablePath(): string { executablePath(): string {

View file

@ -406,5 +406,5 @@ export type FilePayload = {
export type MediaType = 'screen' | 'print'; export type MediaType = 'screen' | 'print';
export const mediaTypes: Set<MediaType> = new Set(['screen', 'print']); export const mediaTypes: Set<MediaType> = new Set(['screen', 'print']);
export type MediaColorScheme = 'dark' | 'light' | 'no-preference'; export type ColorScheme = 'dark' | 'light' | 'no-preference';
export const mediaColorSchemes: Set<MediaColorScheme> = new Set(['dark', 'light', 'no-preference']); export const mediaColorSchemes: Set<ColorScheme> = new Set(['dark', 'light', 'no-preference']);

View file

@ -45,11 +45,8 @@ export interface PageDelegate {
needsLifecycleResetOnSetContent(): boolean; needsLifecycleResetOnSetContent(): boolean;
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>; setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void>;
setUserAgent(userAgent: string): Promise<void>;
setJavaScriptEnabled(enabled: boolean): Promise<void>;
setBypassCSP(enabled: boolean): Promise<void>;
setViewport(viewport: types.Viewport): Promise<void>; setViewport(viewport: types.Viewport): Promise<void>;
setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void>; setEmulateMedia(mediaType: input.MediaType | null, colorScheme: input.ColorScheme | null): Promise<void>;
setCacheEnabled(enabled: boolean): Promise<void>; setCacheEnabled(enabled: boolean): Promise<void>;
getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>; getBoundingBoxForScreenshot(handle: dom.ElementHandle<Node>): Promise<types.Rect | null>;
@ -69,12 +66,9 @@ export interface PageDelegate {
type PageState = { type PageState = {
viewport: types.Viewport | null; viewport: types.Viewport | null;
userAgent: string | null;
mediaType: input.MediaType | null; mediaType: input.MediaType | null;
mediaColorScheme: input.MediaColorScheme | null; colorScheme: input.ColorScheme | null;
javascriptEnabled: boolean | null;
extraHTTPHeaders: network.Headers | null; extraHTTPHeaders: network.Headers | null;
bypassCSP: boolean | null;
cacheEnabled: boolean | null; cacheEnabled: boolean | null;
}; };
@ -99,6 +93,7 @@ export class Page extends EventEmitter {
private _pageBindings = new Map<string, Function>(); private _pageBindings = new Map<string, Function>();
readonly _screenshotter: Screenshotter; readonly _screenshotter: Screenshotter;
readonly _frameManager: frames.FrameManager; readonly _frameManager: frames.FrameManager;
_isContextOwner = false;
constructor(delegate: PageDelegate, browserContext: BrowserContext) { constructor(delegate: PageDelegate, browserContext: BrowserContext) {
super(); super();
@ -107,13 +102,10 @@ export class Page extends EventEmitter {
this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f); this._disconnectedPromise = new Promise(f => this._disconnectedCallback = f);
this._browserContext = browserContext; this._browserContext = browserContext;
this._state = { this._state = {
viewport: null, viewport: browserContext._options.viewport || null,
userAgent: null, mediaType: browserContext._options.mediaType || null,
mediaType: null, colorScheme: browserContext._options.colorScheme || null,
mediaColorScheme: null,
javascriptEnabled: null,
extraHTTPHeaders: null, extraHTTPHeaders: null,
bypassCSP: null,
cacheEnabled: null, cacheEnabled: null,
}; };
this.keyboard = new input.Keyboard(delegate.rawKeyboard); this.keyboard = new input.Keyboard(delegate.rawKeyboard);
@ -244,11 +236,6 @@ export class Page extends EventEmitter {
return this._delegate.setExtraHTTPHeaders(headers); return this._delegate.setExtraHTTPHeaders(headers);
} }
setUserAgent(userAgent: string) {
this._state.userAgent = userAgent;
return this._delegate.setUserAgent(userAgent);
}
async _onBindingCalled(payload: string, context: js.ExecutionContext) { async _onBindingCalled(payload: string, context: js.ExecutionContext) {
const {name, seq, args} = JSON.parse(payload); const {name, seq, args} = JSON.parse(payload);
let expression = null; let expression = null;
@ -360,35 +347,15 @@ export class Page extends EventEmitter {
return waitPromise; return waitPromise;
} }
async emulate(options: { viewport: types.Viewport; userAgent: string; }) {
await Promise.all([
this.setViewport(options.viewport),
this.setUserAgent(options.userAgent)
]);
}
async setJavaScriptEnabled(enabled: boolean) { async emulateMedia(options: { type?: input.MediaType, colorScheme?: input.ColorScheme }) {
if (this._state.javascriptEnabled === enabled)
return;
this._state.javascriptEnabled = enabled;
await this._delegate.setJavaScriptEnabled(enabled);
}
async setBypassCSP(enabled: boolean) {
if (this._state.bypassCSP === enabled)
return;
this._state.bypassCSP = enabled;
await this._delegate.setBypassCSP(enabled);
}
async emulateMedia(options: { type?: input.MediaType, colorScheme?: input.MediaColorScheme }) {
assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type); assert(!options.type || input.mediaTypes.has(options.type), 'Unsupported media type: ' + options.type);
assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme); assert(!options.colorScheme || input.mediaColorSchemes.has(options.colorScheme), 'Unsupported color scheme: ' + options.colorScheme);
if (options.type !== undefined) if (options.type !== undefined)
this._state.mediaType = options.type; this._state.mediaType = options.type;
if (options.colorScheme !== undefined) if (options.colorScheme !== undefined)
this._state.mediaColorScheme = options.colorScheme; this._state.colorScheme = options.colorScheme;
await this._delegate.setEmulateMedia(this._state.mediaType, this._state.mediaColorScheme); await this._delegate.setEmulateMedia(this._state.mediaType, this._state.colorScheme);
} }
async setViewport(viewport: types.Viewport) { async setViewport(viewport: types.Viewport) {
@ -436,6 +403,8 @@ export class Page extends EventEmitter {
await this._delegate.closePage(runBeforeUnload); await this._delegate.closePage(runBeforeUnload);
if (!runBeforeUnload) if (!runBeforeUnload)
await this._closedPromise; await this._closedPromise;
if (this._isContextOwner)
await this._browserContext.close();
} }
isClosed(): boolean { isClosed(): boolean {

View file

@ -19,43 +19,37 @@ import * as childProcess from 'child_process';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { helper, RegisteredListener, debugError } from '../helper'; import { helper, RegisteredListener, debugError } from '../helper';
import * as network from '../network'; import * as network from '../network';
import * as types from '../types';
import { Connection, ConnectionEvents, TargetSession } from './Connection'; import { Connection, ConnectionEvents, TargetSession } from './Connection';
import { Page } from '../page'; import { Page } from '../page';
import { Target } from './Target'; import { Target } from './Target';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
import * as types from '../types';
import { Events } from '../events'; import { Events } from '../events';
import { BrowserContext, BrowserInterface } from '../browserContext'; import { BrowserContext, BrowserInterface, BrowserContextOptions } from '../browserContext';
export class Browser extends EventEmitter implements BrowserInterface { export class Browser extends EventEmitter implements BrowserInterface {
readonly _defaultViewport: types.Viewport;
private readonly _process: childProcess.ChildProcess; private readonly _process: childProcess.ChildProcess;
readonly _connection: Connection; readonly _connection: Connection;
private _closeCallback: () => Promise<void>; private _closeCallback: () => Promise<void>;
private readonly _defaultContext: BrowserContext; private _defaultContext: BrowserContext;
private _contexts = new Map<string, BrowserContext>(); private _contexts = new Map<string, BrowserContext>();
_targets = new Map<string, Target>(); _targets = new Map<string, Target>();
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
private _privateEvents = new EventEmitter(); private _privateEvents = new EventEmitter();
private readonly _ignoreHTTPSErrors: boolean;
constructor( constructor(
connection: Connection, connection: Connection,
ignoreHTTPSErrors: boolean,
defaultViewport: types.Viewport | null,
process: childProcess.ChildProcess | null, process: childProcess.ChildProcess | null,
closeCallback?: (() => Promise<void>)) { closeCallback?: (() => Promise<void>)) {
super(); super();
this._connection = connection; this._connection = connection;
this._ignoreHTTPSErrors = ignoreHTTPSErrors;
this._defaultViewport = defaultViewport;
this._process = process; this._process = process;
this._closeCallback = closeCallback || (() => Promise.resolve()); this._closeCallback = closeCallback || (() => Promise.resolve());
/** @type {!Map<string, !Target>} */ /** @type {!Map<string, !Target>} */
this._targets = new Map(); this._targets = new Map();
this._defaultContext = this._createBrowserContext(undefined); this._defaultContext = this._createBrowserContext(undefined, {});
/** @type {!Map<string, !BrowserContext>} */ /** @type {!Map<string, !BrowserContext>} */
this._contexts = new Map(); this._contexts = new Map();
@ -70,9 +64,6 @@ export class Browser extends EventEmitter implements BrowserInterface {
debugError(e); debugError(e);
throw e; throw e;
}); });
if (this._ignoreHTTPSErrors)
this._setIgnoreTLSFailures(undefined);
} }
async userAgent(): Promise<string> { async userAgent(): Promise<string> {
@ -92,11 +83,11 @@ export class Browser extends EventEmitter implements BrowserInterface {
return this._process; return this._process;
} }
async newContext(): Promise<BrowserContext> { async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
const {browserContextId} = await this._connection.send('Browser.createContext'); const { browserContextId } = await this._connection.send('Browser.createContext');
if (this._ignoreHTTPSErrors) const context = this._createBrowserContext(browserContextId, options);
await this._setIgnoreTLSFailures(browserContextId); if (options.ignoreHTTPSErrors)
const context = this._createBrowserContext(browserContextId); await this._connection.send('Browser.setIgnoreCertificateErrors', { browserContextId, ignore: true });
this._contexts.set(browserContextId, context); this._contexts.set(browserContextId, context);
return context; return context;
} }
@ -105,15 +96,13 @@ export class Browser extends EventEmitter implements BrowserInterface {
return [this._defaultContext, ...Array.from(this._contexts.values())]; return [this._defaultContext, ...Array.from(this._contexts.values())];
} }
defaultBrowserContext(): BrowserContext { defaultContext(): BrowserContext {
return this._defaultContext; return this._defaultContext;
} }
async _disposeContext(browserContextId: string | null) { async newPage(options?: BrowserContextOptions): Promise<Page> {
} const context = await this.newContext(options);
return context._createOwnerPage();
async newPage(): Promise<Page> {
return this._defaultContext.newPage();
} }
targets(): Target[] { targets(): Target[] {
@ -220,7 +209,7 @@ export class Browser extends EventEmitter implements BrowserInterface {
await this._closeCallback.call(null); await this._closeCallback.call(null);
} }
_createBrowserContext(browserContextId: string | undefined): BrowserContext { _createBrowserContext(browserContextId: string | undefined, options: BrowserContextOptions): BrowserContext {
const isIncognito = !!browserContextId; const isIncognito = !!browserContextId;
const context = new BrowserContext({ const context = new BrowserContext({
contextPages: async (): Promise<Page[]> => { contextPages: async (): Promise<Page[]> => {
@ -256,13 +245,9 @@ export class Browser extends EventEmitter implements BrowserInterface {
const cc = cookies.map(c => ({ ...c, session: c.expires === -1 || c.expires === undefined })) as Protocol.Browser.SetCookieParam[]; const cc = cookies.map(c => ({ ...c, session: c.expires === -1 || c.expires === undefined })) as Protocol.Browser.SetCookieParam[];
await this._connection.send('Browser.setCookies', { cookies: cc, browserContextId }); await this._connection.send('Browser.setCookies', { cookies: cc, browserContextId });
}, },
}, this, isIncognito); }, this, isIncognito, options);
return context; return context;
} }
async _setIgnoreTLSFailures(browserContextId: string | undefined) {
await this._connection.send('Browser.setIgnoreCertificateErrors', { browserContextId, ignore: true });
}
} }
const BrowserEvents = { const BrowserEvents = {

View file

@ -86,16 +86,19 @@ export class FrameManager implements PageDelegate {
// Dialog agent resides in the UI process and should not be re-enabled on navigation. // Dialog agent resides in the UI process and should not be re-enabled on navigation.
promises.push(session.send('Dialog.enable')); promises.push(session.send('Dialog.enable'));
} }
if (this._page._state.userAgent !== null) const contextOptions = this._page.browserContext()._options;
promises.push(this._setUserAgent(session, this._page._state.userAgent)); if (contextOptions.userAgent)
if (this._page._state.mediaType !== null || this._page._state.mediaColorScheme !== null) promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
promises.push(this._setEmulateMedia(session, this._page._state.mediaType, this._page._state.mediaColorScheme)); if (this._page._state.mediaType || this._page._state.colorScheme)
if (this._page._state.javascriptEnabled !== null) promises.push(this._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
promises.push(this._setJavaScriptEnabled(session, this._page._state.javascriptEnabled)); if (contextOptions.javaScriptEnabled === false)
if (this._page._state.bypassCSP !== null) promises.push(session.send('Emulation.setJavaScriptEnabled', { enabled: false }));
promises.push(this._setBypassCSP(session, this._page._state.bypassCSP)); if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (this._page._state.extraHTTPHeaders !== null) if (this._page._state.extraHTTPHeaders !== null)
promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders)); promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
if (this._page._state.viewport)
promises.push(this.setViewport(this._page._state.viewport));
await Promise.all(promises); await Promise.all(promises);
} }
@ -256,19 +259,7 @@ export class FrameManager implements PageDelegate {
await session.send('Network.setExtraHTTPHeaders', { headers }); await session.send('Network.setExtraHTTPHeaders', { headers });
} }
private async _setUserAgent(session: TargetSession, userAgent: string): Promise<void> { private async _setEmulateMedia(session: TargetSession, mediaType: input.MediaType | null, mediaColorScheme: input.ColorScheme | null): Promise<void> {
await session.send('Page.overrideUserAgent', { value: userAgent });
}
private async _setJavaScriptEnabled(session: TargetSession, enabled: boolean): Promise<void> {
await session.send('Emulation.setJavaScriptEnabled', { enabled });
}
private async _setBypassCSP(session: TargetSession, enabled: boolean): Promise<void> {
await session.send('Page.setBypassCSP', { enabled });
}
private async _setEmulateMedia(session: TargetSession, mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> {
const promises = []; const promises = [];
promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' })); promises.push(session.send('Page.setEmulatedMedia', { media: mediaType || '' }));
if (mediaColorScheme !== null) { if (mediaColorScheme !== null) {
@ -286,23 +277,12 @@ export class FrameManager implements PageDelegate {
await this._setExtraHTTPHeaders(this._session, headers); await this._setExtraHTTPHeaders(this._session, headers);
} }
async setUserAgent(userAgent: string): Promise<void> { async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.ColorScheme | null): Promise<void> {
await this._setUserAgent(this._session, userAgent);
await this._page.reload();
}
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
await this._setJavaScriptEnabled(this._session, enabled);
}
async setBypassCSP(enabled: boolean): Promise<void> {
await this._setBypassCSP(this._session, enabled);
}
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> {
await this._setEmulateMedia(this._session, mediaType, mediaColorScheme); await this._setEmulateMedia(this._session, mediaType, mediaColorScheme);
} }
async setViewport(viewport: types.Viewport): Promise<void> { async setViewport(viewport: types.Viewport): Promise<void> {
if (viewport.isMobile || viewport.isLandscape || viewport.hasTouch) if (viewport.isMobile || viewport.isLandscape || viewport.hasTouch)
throw new Error('Not implemented'); throw new Error('Not implemented');

View file

@ -19,7 +19,6 @@ import { debugError, assert } from '../helper';
import { Browser } from './Browser'; import { Browser } from './Browser';
import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher'; import { BrowserFetcher, BrowserFetcherOptions } from '../browserFetcher';
import { Connection } from './Connection'; import { Connection } from './Connection';
import * as types from '../types';
import { PipeTransport } from '../transport'; import { PipeTransport } from '../transport';
import { execSync } from 'child_process'; import { execSync } from 'child_process';
import * as path from 'path'; import * as path from 'path';
@ -58,9 +57,7 @@ export class Launcher {
handleSIGINT = true, handleSIGINT = true,
handleSIGTERM = true, handleSIGTERM = true,
handleSIGHUP = true, handleSIGHUP = true,
defaultViewport = {width: 800, height: 600},
slowMo = 0, slowMo = 0,
ignoreHTTPSErrors = false
} = options; } = options;
const webkitArguments = []; const webkitArguments = [];
@ -104,7 +101,7 @@ export class Launcher {
try { try {
const transport = new PipeTransport(launched.process.stdio[3] as NodeJS.WritableStream, launched.process.stdio[4] as NodeJS.ReadableStream); const transport = new PipeTransport(launched.process.stdio[3] as NodeJS.WritableStream, launched.process.stdio[4] as NodeJS.ReadableStream);
connection = new Connection(transport, slowMo); connection = new Connection(transport, slowMo);
const browser = new Browser(connection, ignoreHTTPSErrors, defaultViewport, launched.process, launched.gracefullyClose); const browser = new Browser(connection, launched.process, launched.gracefullyClose);
await browser._waitForTarget(t => t._type === 'page'); await browser._waitForTarget(t => t._type === 'page');
return browser; return browser;
} catch (e) { } catch (e) {
@ -136,9 +133,7 @@ export type LauncherLaunchOptions = {
headless?: boolean, headless?: boolean,
dumpio?: boolean, dumpio?: boolean,
env?: {[key: string]: string} | undefined, env?: {[key: string]: string} | undefined,
defaultViewport?: types.Viewport | null,
slowMo?: number, slowMo?: number,
ignoreHTTPSErrors?: boolean,
}; };
let cachedMacVersion = undefined; let cachedMacVersion = undefined;

View file

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
import { Browser } from './Browser';
import { BrowserContext } from '../browserContext'; import { BrowserContext } from '../browserContext';
import { Page } from '../page'; import { Page } from '../page';
import { Protocol } from './protocol'; import { Protocol } from './protocol';
@ -86,7 +85,6 @@ export class Target {
async page(): Promise<Page> { async page(): Promise<Page> {
if (this._type === 'page' && !this._pagePromise) { if (this._type === 'page' && !this._pagePromise) {
const browser = this._browserContext.browser() as Browser;
this._frameManager = new FrameManager(this._browserContext); this._frameManager = new FrameManager(this._browserContext);
// Reference local page variable as |this._frameManager| may be // Reference local page variable as |this._frameManager| may be
// cleared on swap. // cleared on swap.
@ -94,8 +92,6 @@ export class Target {
this._pagePromise = new Promise(async f => { this._pagePromise = new Promise(async f => {
this._adoptPage(); this._adoptPage();
await this._initializeSession(this._session); await this._initializeSession(this._session);
if (browser._defaultViewport)
await page.setViewport(browser._defaultViewport);
f(page); f(page);
}); });
} }

View file

@ -17,7 +17,7 @@
const utils = require('./utils'); const utils = require('./utils');
module.exports.addTests = function({testRunner, expect}) { module.exports.addTests = function({testRunner, expect, playwright, WEBKIT}) {
const {describe, xdescribe, fdescribe} = testRunner; const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
@ -29,7 +29,7 @@ module.exports.addTests = function({testRunner, expect}) {
expect(defaultContext.isIncognito()).toBe(false); expect(defaultContext.isIncognito()).toBe(false);
let error = null; let error = null;
await defaultContext.close().catch(e => error = e); await defaultContext.close().catch(e => error = e);
expect(browser.defaultBrowserContext()).toBe(defaultContext); expect(browser.defaultContext()).toBe(defaultContext);
expect(error.message).toContain('cannot be closed'); expect(error.message).toContain('cannot be closed');
}); });
it('should create new incognito context', async function({browser, server}) { it('should create new incognito context', async function({browser, server}) {
@ -107,5 +107,161 @@ module.exports.addTests = function({testRunner, expect}) {
]); ]);
expect(browser.browserContexts().length).toBe(1); expect(browser.browserContexts().length).toBe(1);
}); });
it('should set the default viewport', async({ newPage }) => {
const page = await newPage({ viewport: { width: 456, height: 789 } });
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
});
it('should take fullPage screenshots when default viewport is null', async({server, newPage}) => {
const page = await newPage({ viewport: null });
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot({
fullPage: true
});
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
});
});
describe('BrowserContext({setUserAgent})', function() {
it('should work', async({newPage, server}) => {
{
const page = await newPage();
expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla');
}
{
const page = await newPage({ userAgent: 'foobar' });
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['user-agent']).toBe('foobar');
}
});
it('should work for subframes', async({newPage, server}) => {
{
const page = await newPage();
expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla');
}
{
const page = await newPage({ userAgent: 'foobar' });
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
utils.attachFrame(page, 'frame1', server.EMPTY_PAGE),
]);
expect(request.headers['user-agent']).toBe('foobar');
}
});
it('should emulate device user-agent', async({newPage, server}) => {
{
const page = await newPage();
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone');
}
{
const page = await newPage({ userAgent: playwright.devices['iPhone 6'].userAgent });
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
}
});
});
describe('BrowserContext({bypassCSP})', function() {
it('should bypass CSP meta tag', async({newPage, server}) => {
// Make sure CSP prohibits addScriptTag.
{
const page = await newPage();
await page.goto(server.PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await page.evaluate(() => window.__injected)).toBe(undefined);
}
// By-pass CSP and try one more time.
{
const page = await newPage({ bypassCSP: true });
await page.goto(server.PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
}
});
it('should bypass CSP header', async({newPage, server}) => {
// Make sure CSP prohibits addScriptTag.
server.setCSP('/empty.html', 'default-src "self"');
{
const page = await newPage();
await page.goto(server.EMPTY_PAGE);
await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await page.evaluate(() => window.__injected)).toBe(undefined);
}
// By-pass CSP and try one more time.
{
const page = await newPage({ bypassCSP: true });
await page.goto(server.EMPTY_PAGE);
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
}
});
it('should bypass after cross-process navigation', async({newPage, server}) => {
const page = await newPage({ bypassCSP: true });
await page.goto(server.PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
});
it('should bypass CSP in iframes as well', async({newPage, server}) => {
// Make sure CSP prohibits addScriptTag in an iframe.
{
const page = await newPage();
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html');
await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await frame.evaluate(() => window.__injected)).toBe(undefined);
}
// By-pass CSP and try one more time.
{
const page = await newPage({ bypassCSP: true });
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html');
await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await frame.evaluate(() => window.__injected)).toBe(42);
}
});
});
describe('BrowserContext({javaScriptEnabled})', function() {
it('should work', async({newPage}) => {
{
const page = await newPage({ javaScriptEnabled: false });
await page.goto('data:text/html, <script>var something = "forbidden"</script>');
let error = null;
await page.evaluate('something').catch(e => error = e);
if (WEBKIT)
expect(error.message).toContain('Can\'t find variable: something');
else
expect(error.message).toContain('something is not defined');
}
{
const page = await newPage();
await page.goto('data:text/html, <script>var something = "forbidden"</script>');
expect(await page.evaluate('something')).toBe('forbidden');
}
});
it('should be able to navigate after disabling javascript', async({newPage, server}) => {
const page = await newPage({ javaScriptEnabled: false });
await page.goto(server.EMPTY_PAGE);
});
}); });
}; };

View file

@ -55,16 +55,17 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
const originalBrowser = await playwright.launch(defaultBrowserOptions); const originalBrowser = await playwright.launch(defaultBrowserOptions);
const browserWSEndpoint = originalBrowser.chromium.wsEndpoint(); const browserWSEndpoint = originalBrowser.chromium.wsEndpoint();
const browser = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint, ignoreHTTPSErrors: true}); const browser = await playwright.connect({...defaultBrowserOptions, browserWSEndpoint});
const page = await browser.newPage(); const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage();
let error = null; let error = null;
const [serverRequest, response] = await Promise.all([ const [, response] = await Promise.all([
httpsServer.waitForRequest('/empty.html'), httpsServer.waitForRequest('/empty.html'),
page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e) page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e)
]); ]);
expect(error).toBe(null); expect(error).toBe(null);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
await page.close(); await context.close();
await browser.close(); await browser.close();
}); });
it('should be able to reconnect to a disconnected browser', async({server}) => { it('should be able to reconnect to a disconnected browser', async({server}) => {

View file

@ -26,7 +26,7 @@ module.exports.addTests = function ({ testRunner, expect }) {
it('should work', async({page, server, context}) => { it('should work', async({page, server, context}) => {
await context.permissions.override(server.PREFIX, ['geolocation']); await context.permissions.override(server.PREFIX, ['geolocation']);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.overrides.setGeolocation({longitude: 10, latitude: 10}); await context.overrides.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => { const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude}); resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
}))); })));
@ -35,10 +35,10 @@ module.exports.addTests = function ({ testRunner, expect }) {
longitude: 10 longitude: 10
}); });
}); });
it('should throw when invalid longitude', async({page, server, context}) => { it('should throw when invalid longitude', async({context}) => {
let error = null; let error = null;
try { try {
await page.overrides.setGeolocation({longitude: 200, latitude: 10}); await context.overrides.setGeolocation({longitude: 200, latitude: 10});
} catch (e) { } catch (e) {
error = e; error = e;
} }

View file

@ -71,13 +71,13 @@ module.exports.addTests = function({testRunner, expect, playwright, defaultBrows
const userDataDir = await mkdtempAsync(TMP_FOLDER); const userDataDir = await mkdtempAsync(TMP_FOLDER);
// Write a cookie in headful chrome // Write a cookie in headful chrome
const headfulBrowser = await playwright.launch(Object.assign({userDataDir}, headfulOptions)); const headfulBrowser = await playwright.launch(Object.assign({userDataDir}, headfulOptions));
const headfulPage = await headfulBrowser.newPage(); const headfulPage = await headfulBrowser.defaultContext().newPage();
await headfulPage.goto(server.EMPTY_PAGE); await headfulPage.goto(server.EMPTY_PAGE);
await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); await headfulPage.evaluate(() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await headfulBrowser.close(); await headfulBrowser.close();
// Read the cookie from headless chrome // Read the cookie from headless chrome
const headlessBrowser = await playwright.launch(Object.assign({userDataDir}, headlessOptions)); const headlessBrowser = await playwright.launch(Object.assign({userDataDir}, headlessOptions));
const headlessPage = await headlessBrowser.newPage(); const headlessPage = await headlessBrowser.defaultContext().newPage();
await headlessPage.goto(server.EMPTY_PAGE); await headlessPage.goto(server.EMPTY_PAGE);
const cookie = await headlessPage.evaluate(() => document.cookie); const cookie = await headlessPage.evaluate(() => document.cookie);
await headlessBrowser.close(); await headlessBrowser.close();

View file

@ -124,13 +124,13 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
const userDataDir = await mkdtempAsync(TMP_FOLDER); const userDataDir = await mkdtempAsync(TMP_FOLDER);
const options = Object.assign({userDataDir}, defaultBrowserOptions); const options = Object.assign({userDataDir}, defaultBrowserOptions);
const browser = await playwright.launch(options); const browser = await playwright.launch(options);
const page = await browser.newPage(); const page = await browser.defaultContext().newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => localStorage.hey = 'hello'); await page.evaluate(() => localStorage.hey = 'hello');
await browser.close(); await browser.close();
const browser2 = await playwright.launch(options); const browser2 = await playwright.launch(options);
const page2 = await browser2.newPage(); const page2 = await browser2.defaultContext().newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => localStorage.hey)).toBe('hello'); expect(await page2.evaluate(() => localStorage.hey)).toBe('hello');
await browser2.close(); await browser2.close();
@ -142,13 +142,13 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
const userDataDir = await mkdtempAsync(TMP_FOLDER); const userDataDir = await mkdtempAsync(TMP_FOLDER);
const options = Object.assign({userDataDir}, defaultBrowserOptions); const options = Object.assign({userDataDir}, defaultBrowserOptions);
const browser = await playwright.launch(options); const browser = await playwright.launch(options);
const page = await browser.newPage(); const page = await browser.defaultContext().newPage();
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'); await page.evaluate(() => document.cookie = 'doSomethingOnlyOnce=true; expires=Fri, 31 Dec 9999 23:59:59 GMT');
await browser.close(); await browser.close();
const browser2 = await playwright.launch(options); const browser2 = await playwright.launch(options);
const page2 = await browser2.newPage(); const page2 = await browser2.defaultContext().newPage();
await page2.goto(server.EMPTY_PAGE); await page2.goto(server.EMPTY_PAGE);
expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true'); expect(await page2.evaluate(() => document.cookie)).toBe('doSomethingOnlyOnce=true');
await browser2.close(); await browser2.close();
@ -161,7 +161,7 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
it('should support the pipe option', async() => { it('should support the pipe option', async() => {
const options = Object.assign({pipe: true}, defaultBrowserOptions); const options = Object.assign({pipe: true}, defaultBrowserOptions);
const browser = await playwright.launch(options); const browser = await playwright.launch(options);
expect((await browser.pages()).length).toBe(1); expect((await browser.defaultContext().pages()).length).toBe(1);
expect(browser.chromium.wsEndpoint()).toBe(''); expect(browser.chromium.wsEndpoint()).toBe('');
const page = await browser.newPage(); const page = await browser.newPage();
expect(await page.evaluate('11 * 11')).toBe(121); expect(await page.evaluate('11 * 11')).toBe(121);

View file

@ -70,8 +70,8 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
await page.click('button'); await page.click('button');
expect(await page.evaluate(() => result)).toBe('Clicked'); expect(await page.evaluate(() => result)).toBe('Clicked');
}); });
it.skip(FFOX)('should click with disabled javascript', async({page, server}) => { it.skip(FFOX)('should click with disabled javascript', async({newPage, server}) => {
await page.setJavaScriptEnabled(false); const page = await newPage({ javaScriptEnabled: false });
await page.goto(server.PREFIX + '/wrappedlink.html'); await page.goto(server.PREFIX + '/wrappedlink.html');
await Promise.all([ await Promise.all([
page.click('a'), page.click('a'),

View file

@ -20,7 +20,7 @@ module.exports.addTests = function ({ testRunner, expect, defaultBrowserOptions,
const {it, fit, xit} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('DefaultBrowserContext', function() { describe('defaultContext()', function() {
beforeEach(async state => { beforeEach(async state => {
state.browser = await playwright.launch(defaultBrowserOptions); state.browser = await playwright.launch(defaultBrowserOptions);
state.page = await state.browser.newPage(); state.page = await state.browser.newPage();

View file

@ -81,14 +81,14 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
}); });
describe('Page.emulate', function() { describe('Page.emulate', function() {
it.skip(WEBKIT)('should work', async({page, server}) => { it.skip(WEBKIT)('should work', async({newPage, server}) => {
const page = await newPage({ viewport: iPhone.viewport, userAgent: iPhone.userAgent });
await page.goto(server.PREFIX + '/mobile.html'); await page.goto(server.PREFIX + '/mobile.html');
await page.emulate(iPhone);
expect(await page.evaluate(() => window.innerWidth)).toBe(375); expect(await page.evaluate(() => window.innerWidth)).toBe(375);
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone'); expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
}); });
it.skip(WEBKIT)('should support clicking', async({page, server}) => { it.skip(WEBKIT)('should support clicking', async({newPage, server}) => {
await page.emulate(iPhone); const page = await newPage({ viewport: iPhone.viewport, userAgent: iPhone.userAgent });
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
const button = await page.$('button'); const button = await page.$('button');
await page.evaluate(button => button.style.marginTop = '200px', button); await page.evaluate(button => button.style.marginTop = '200px', button);
@ -143,29 +143,32 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
}); });
}); });
describe.skip(FFOX || WEBKIT)('Overrides.setTimezone', function() { describe.skip(FFOX || WEBKIT)('BrowserContext({timezoneId})', function() {
it('should work', async({page, server}) => { it('should work', async ({ newPage }) => {
page.evaluate(() => { const func = () => new Date(1479579154987).toString();
globalThis.date = new Date(1479579154987); {
}); const page = await newPage({ timezoneId: 'America/Jamaica' });
await page.overrides.setTimezone('America/Jamaica'); expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)');
expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 13:12:34 GMT-0500 (Eastern Standard Time)'); }
{
await page.overrides.setTimezone('Pacific/Honolulu'); const page = await newPage({ timezoneId: 'Pacific/Honolulu' });
expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)'); expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 08:12:34 GMT-1000 (Hawaii-Aleutian Standard Time)');
}
await page.overrides.setTimezone('America/Buenos_Aires'); {
expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)'); const page = await newPage({ timezoneId: 'America/Buenos_Aires' });
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 15:12:34 GMT-0300 (Argentina Standard Time)');
await page.overrides.setTimezone('Europe/Berlin'); }
expect(await page.evaluate(() => date.toString())).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)'); {
const page = await newPage({ timezoneId: 'Europe/Berlin' });
expect(await page.evaluate(func)).toBe('Sat Nov 19 2016 19:12:34 GMT+0100 (Central European Standard Time)');
}
}); });
it('should throw for invalid timezone IDs', async({page, server}) => { it('should throw for invalid timezone IDs', async({newPage}) => {
let error = null; let error = null;
await page.overrides.setTimezone('Foo/Bar').catch(e => error = e); await newPage({ timezoneId: 'Foo/Bar' }).catch(e => error = e);
expect(error.message).toBe('Invalid timezone ID: Foo/Bar'); expect(error.message).toBe('Invalid timezone ID: Foo/Bar');
await page.overrides.setTimezone('Baz/Qux').catch(e => error = e); await newPage({ timezoneId: 'Baz/Qux' }).catch(e => error = e);
expect(error.message).toBe('Invalid timezone ID: Baz/Qux'); expect(error.message).toBe('Invalid timezone ID: Baz/Qux');
}); });
}); });

View file

@ -663,17 +663,13 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
}); });
describe('ignoreHTTPSErrors', function() { describe('ignoreHTTPSErrors', function() {
it('should work with request interception', async({httpsServer}) => { it('should work with request interception', async({newPage, httpsServer}) => {
const browser = await playwright.launch({...defaultBrowserOptions, ignoreHTTPSErrors: true}); const page = await newPage({ ignoreHTTPSErrors: true });
const context = await browser.newContext();
const page = await context.newPage();
await page.interception.enable(); await page.interception.enable();
page.on('request', request => page.interception.continue(request)); page.on('request', request => page.interception.continue(request));
const response = await page.goto(httpsServer.EMPTY_PAGE); const response = await page.goto(httpsServer.EMPTY_PAGE);
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
await browser.close();
}); });
}); });
}; };

View file

@ -1,7 +1,7 @@
(async() => { (async() => {
const [, , playwrightRoot, options] = process.argv; const [, , playwrightRoot, options] = process.argv;
const browser = await require(playwrightRoot).launch(JSON.parse(options)); const browser = await require(playwrightRoot).launch(JSON.parse(options));
const page = await browser.newPage(); const page = await browser.defaultContext().newPage();
await page.evaluate(() => console.error('message from dumpio')); await page.evaluate(() => console.error('message from dumpio'));
await page.close(); await page.close();
await browser.close(); await browser.close();

View file

@ -20,33 +20,18 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
const {it, fit, xit} = testRunner; const {it, fit, xit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
describe('ignoreHTTPSErrors', function() { describe('ignoreHTTPSErrors', function() {
beforeAll(async state => { it('should work', async({newPage, httpsServer}) => {
state.browser = await playwright.launch({...defaultBrowserOptions, ignoreHTTPSErrors: true});
});
afterAll(async state => {
await state.browser.close();
delete state.browser;
});
beforeEach(async state => {
state.context = await state.browser.newContext();
state.page = await state.context.newPage();
});
afterEach(async state => {
await state.context.close();
delete state.context;
delete state.page;
});
it('should work', async({page, httpsServer}) => {
let error = null; let error = null;
const page = await newPage({ ignoreHTTPSErrors: true });
const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); const response = await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
expect(error).toBe(null); expect(error).toBe(null);
expect(response.ok()).toBe(true); expect(response.ok()).toBe(true);
}); });
it('should work with mixed content', async({page, server, httpsServer}) => { it('should work with mixed content', async({newPage, server, httpsServer}) => {
httpsServer.setRoute('/mixedcontent.html', (req, res) => { httpsServer.setRoute('/mixedcontent.html', (req, res) => {
res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`); res.end(`<iframe src=${server.EMPTY_PAGE}></iframe>`);
}); });
const page = await newPage({ ignoreHTTPSErrors: true });
await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'load'}); await page.goto(httpsServer.PREFIX + '/mixedcontent.html', {waitUntil: 'load'});
expect(page.frames().length).toBe(2); expect(page.frames().length).toBe(2);
// Make sure blocked iframe has functional execution context // Make sure blocked iframe has functional execution context

View file

@ -45,46 +45,6 @@ module.exports.addTests = function({testRunner, expect, defaultBrowserOptions, p
await playwright.launch(options).catch(e => waitError = e); await playwright.launch(options).catch(e => waitError = e);
expect(waitError.message).toContain('Failed to launch'); expect(waitError.message).toContain('Failed to launch');
}); });
it('should set the default viewport', async() => {
const options = Object.assign({}, defaultBrowserOptions, {
defaultViewport: {
width: 456,
height: 789
}
});
const browser = await playwright.launch(options);
const page = await browser.newPage();
expect(await page.evaluate('window.innerWidth')).toBe(456);
expect(await page.evaluate('window.innerHeight')).toBe(789);
await browser.close();
});
it('should disable the default viewport', async() => {
const options = Object.assign({}, defaultBrowserOptions, {
defaultViewport: null
});
const browser = await playwright.launch(options);
const page = await browser.newPage();
expect(page.viewport()).toBe(null);
await browser.close();
});
it('should take fullPage screenshots when defaultViewport is null', async({server}) => {
const options = Object.assign({}, defaultBrowserOptions, {
defaultViewport: null
});
const browser = await playwright.launch(options);
const page = await browser.newPage();
await page.goto(server.PREFIX + '/grid.html');
const sizeBefore = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
const screenshot = await page.screenshot({
fullPage: true
});
expect(screenshot).toBeInstanceOf(Buffer);
const sizeAfter = await page.evaluate(() => ({ width: document.body.offsetWidth, height: document.body.offsetHeight }));
expect(sizeBefore.width).toBe(sizeAfter.width);
expect(sizeBefore.height).toBe(sizeAfter.height);
await browser.close();
});
it('should have default URL when launching browser', async function() { it('should have default URL when launching browser', async function() {
const browser = await playwright.launch(defaultBrowserOptions); const browser = await playwright.launch(defaultBrowserOptions);
const pages = (await browser.pages()).map(page => page.url()); const pages = (await browser.pages()).map(page => page.url());

View file

@ -125,7 +125,7 @@ module.exports.addTests = function({testRunner, expect, playwright, FFOX, CHROME
const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'}); const response = await page.goto(server.EMPTY_PAGE, {waitUntil: 'domcontentloaded'});
expect(response.status()).toBe(200); expect(response.status()).toBe(200);
}); });
it('should work when page calls history API in beforeunload', async({page, server}) => { it.skip(WEBKIT)('should work when page calls history API in beforeunload', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => { await page.evaluate(() => {
window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false); window.addEventListener('beforeunload', () => history.replaceState(null, 'initial', window.location.href), false);

View file

@ -166,7 +166,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await page.evaluate(() => !!window.opener)).toBe(false);
expect(await popup.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(false);
}); });
it('should not treat navigations as new popups', async({page, server}) => { it.skip(WEBKIT)('should not treat navigations as new popups', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>'); await page.setContent('<a target=_blank rel=noopener href="/one-style.html">yo</a>');
const [popup] = await Promise.all([ const [popup] = await Promise.all([
@ -585,33 +585,6 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
}); });
}); });
describe('Page.setUserAgent', function() {
it('should work', async({page, server}) => {
expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla');
await page.setUserAgent('foobar');
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
page.goto(server.EMPTY_PAGE),
]);
expect(request.headers['user-agent']).toBe('foobar');
});
it('should work for subframes', async({page, server}) => {
expect(await page.evaluate(() => navigator.userAgent)).toContain('Mozilla');
await page.setUserAgent('foobar');
const [request] = await Promise.all([
server.waitForRequest('/empty.html'),
utils.attachFrame(page, 'frame1', server.EMPTY_PAGE),
]);
expect(request.headers['user-agent']).toBe('foobar');
});
it('should emulate device user-agent', async({page, server}) => {
await page.goto(server.PREFIX + '/mobile.html');
expect(await page.evaluate(() => navigator.userAgent)).not.toContain('iPhone');
await page.setUserAgent(playwright.devices['iPhone 6'].userAgent);
expect(await page.evaluate(() => navigator.userAgent)).toContain('iPhone');
});
});
describe('Page.setContent', function() { describe('Page.setContent', function() {
const expectedOutput = '<html><head></head><body><div>hello</div></body></html>'; const expectedOutput = '<html><head></head><body><div>hello</div></body></html>';
it('should work', async({page, server}) => { it('should work', async({page, server}) => {
@ -705,64 +678,6 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
}); });
}); });
describe('Page.setBypassCSP', function() {
it('should bypass CSP meta tag', async({page, server}) => {
// Make sure CSP prohibits addScriptTag.
await page.goto(server.PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await page.evaluate(() => window.__injected)).toBe(undefined);
// By-pass CSP and try one more time.
await page.setBypassCSP(true);
await page.reload();
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
});
it('should bypass CSP header', async({page, server}) => {
// Make sure CSP prohibits addScriptTag.
server.setCSP('/empty.html', 'default-src "self"');
await page.goto(server.EMPTY_PAGE);
await page.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await page.evaluate(() => window.__injected)).toBe(undefined);
// By-pass CSP and try one more time.
await page.setBypassCSP(true);
await page.reload();
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
});
it('should bypass after cross-process navigation', async({page, server}) => {
await page.setBypassCSP(true);
await page.goto(server.PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
await page.goto(server.CROSS_PROCESS_PREFIX + '/csp.html');
await page.addScriptTag({content: 'window.__injected = 42;'});
expect(await page.evaluate(() => window.__injected)).toBe(42);
});
it('should bypass CSP in iframes as well', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
{
// Make sure CSP prohibits addScriptTag in an iframe.
const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html');
await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await frame.evaluate(() => window.__injected)).toBe(undefined);
}
// By-pass CSP and try one more time.
await page.setBypassCSP(true);
await page.reload();
{
const frame = await utils.attachFrame(page, 'frame1', server.PREFIX + '/csp.html');
await frame.addScriptTag({content: 'window.__injected = 42;'}).catch(e => void e);
expect(await frame.evaluate(() => window.__injected)).toBe(42);
}
});
});
describe('Page.addScriptTag', function() { describe('Page.addScriptTag', function() {
it('should throw an error if no options are provided', async({page, server}) => { it('should throw an error if no options are provided', async({page, server}) => {
@ -924,27 +839,6 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF
}); });
}); });
describe('Page.setJavaScriptEnabled', function() {
it('should work', async({page, server}) => {
await page.setJavaScriptEnabled(false);
await page.goto('data:text/html, <script>var something = "forbidden"</script>');
let error = null;
await page.evaluate('something').catch(e => error = e);
if (WEBKIT)
expect(error.message).toContain('Can\'t find variable: something');
else
expect(error.message).toContain('something is not defined');
await page.setJavaScriptEnabled(true);
await page.goto('data:text/html, <script>var something = "forbidden"</script>');
expect(await page.evaluate('something')).toBe('forbidden');
});
it('should be able to navigate after disabling javascript', async({page, server}) => {
await page.setJavaScriptEnabled(false);
await page.goto(server.EMPTY_PAGE);
});
});
describe('Page.setCacheEnabled', function() { describe('Page.setCacheEnabled', function() {
// FIXME: 'if-modified-since' is not set for some reason even if cache is on. // FIXME: 'if-modified-since' is not set for some reason even if cache is on.
it.skip(WEBKIT)('should enable or disable the cache based on the state passed', async({page, server}) => { it.skip(WEBKIT)('should enable or disable the cache based on the state passed', async({page, server}) => {

View file

@ -98,36 +98,51 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
state.browser = null; state.browser = null;
}); });
if (!WEBKIT) {
beforeEach(async(state, test) => { beforeEach(async(state, test) => {
const rl = require('readline').createInterface({input: state.browser.process().stderr}); const pages = [];
const contexts = [];
const onLine = (line) => test.output += line + '\n';
let rl;
if (!WEBKIT) {
rl = require('readline').createInterface({ input: state.browser.process().stderr });
test.output = ''; test.output = '';
rl.on('line', onLine); rl.on('line', onLine);
state.tearDown = () => {
rl.removeListener('line', onLine);
rl.close();
};
function onLine(line) {
test.output += line + '\n';
}
});
} }
state.tearDown = async () => {
await Promise.all(pages.map(p => p.close()));
await Promise.all(contexts.map(c => c.close()));
if (!WEBKIT) { if (!WEBKIT) {
afterEach(async state => { rl.removeListener('line', onLine);
state.tearDown(); rl.close();
});
} }
};
state.newContext = async (options) => {
const context = await state.browser.newContext(options);
contexts.push(context);
return context;
};
state.newPage = async (options) => {
const page = await state.browser.newPage(options);
pages.push(page);
return page;
};
});
afterEach(async state => {
await state.tearDown();
});
describe('Page', function() { describe('Page', function() {
beforeEach(async state => { beforeEach(async state => {
state.context = await state.browser.newContext(); state.context = await state.newContext();
state.page = await state.context.newPage(); state.page = await state.context.newPage();
}); });
afterEach(async state => { afterEach(async state => {
// This closes all pages.
await state.context.close();
state.context = null; state.context = null;
state.page = null; state.page = null;
}); });
@ -172,12 +187,12 @@ module.exports.addTests = ({testRunner, product, playwrightPath}) => {
// Browser-level tests that are given a browser. // Browser-level tests that are given a browser.
require('./browsercontext.spec.js').addTests(testOptions); require('./browsercontext.spec.js').addTests(testOptions);
require('./ignorehttpserrors.spec.js').addTests(testOptions);
}); });
// Top-level tests that launch Browser themselves. // Top-level tests that launch Browser themselves.
require('./defaultbrowsercontext.spec.js').addTests(testOptions); require('./defaultbrowsercontext.spec.js').addTests(testOptions);
require('./fixtures.spec.js').addTests(testOptions); require('./fixtures.spec.js').addTests(testOptions);
require('./ignorehttpserrors.spec.js').addTests(testOptions);
require('./launcher.spec.js').addTests(testOptions); require('./launcher.spec.js').addTests(testOptions);
if (CHROME) { if (CHROME) {