From 7f9df9488e3fcaacdea79956e98c4d538f0d859d Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 4 Mar 2020 17:58:12 -0800 Subject: [PATCH] api(popups): move Page.setOfflineMode -> BrowserContext.setOffline (#1223) --- docs/api.md | 13 ++++++++----- src/browserContext.ts | 2 ++ src/chromium/crBrowser.ts | 8 ++++++++ src/chromium/crNetworkManager.ts | 6 ++---- src/chromium/crPage.ts | 6 ++---- src/firefox/ffBrowser.ts | 8 ++++++++ src/firefox/ffPage.ts | 4 ---- src/page.ts | 10 ---------- src/webkit/wkBrowser.ts | 8 ++++++++ src/webkit/wkPage.ts | 8 ++++---- test/browsercontext.spec.js | 24 ++++++++++++++++++++++++ test/interception.spec.js | 19 ------------------- test/popup.spec.js | 12 ++++++++++++ test/test.js | 2 +- 14 files changed, 79 insertions(+), 51 deletions(-) diff --git a/docs/api.md b/docs/api.md index 9df72c0ced..f1e832b494 100644 --- a/docs/api.md +++ b/docs/api.md @@ -209,6 +209,7 @@ Indicates that the browser is connected. - `locale` Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. - `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details. - `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings. + - `offline` <[boolean]> Whether to emulate network being offline for the browser context. - returns: <[Promise]<[BrowserContext]>> Creates a new browser context. It won't share cookies/cache with other browser contexts. @@ -243,6 +244,7 @@ Creates a new browser context. It won't share cookies/cache with other browser c - `locale` Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. - `permissions` <[Object]> A map from origin keys to permissions values. See [browserContext.setPermissions](#browsercontextsetpermissionsorigin-permissions) for more details. - `extraHTTPHeaders` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings. + - `offline` <[boolean]> Whether to emulate network being offline for the browser context. - returns: <[Promise]<[Page]>> Creates a new page in a new browser context. Closing this page will close the context as well. @@ -287,6 +289,7 @@ await context.close(); - [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) - [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders) - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) +- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) - [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions) @@ -509,6 +512,10 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667}); > **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation. +#### browserContext.setOffline(offline) +- `offline` <[boolean]> Whether to emulate network being offline for the browser context. +- returns: <[Promise]> + #### browserContext.setPermissions(origin, permissions[]) - `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com". - `permissions` <[Array]<[string]>> An array of permissions to grant. All permissions that are not listed here will be automatically denied. Permissions can be one of the following values: @@ -635,7 +642,6 @@ page.removeListener('request', logRequest); - [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout) - [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) - [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders) -- [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) - [page.setViewportSize(viewportSize)](#pagesetviewportsizeviewportsize) - [page.title()](#pagetitle) - [page.tripleclick(selector[, options])](#pagetripleclickselector-options) @@ -1475,10 +1481,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. -#### page.setOfflineMode(enabled) -- `enabled` <[boolean]> When `true`, enables offline mode for the page. -- returns: <[Promise]> - #### page.setViewportSize(viewportSize) - `viewportSize` <[Object]> - `width` <[number]> page width in pixels. **required** @@ -3723,6 +3725,7 @@ const backgroundPage = await backroundPageTarget.page(); - [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) - [browserContext.setExtraHTTPHeaders(headers)](#browsercontextsetextrahttpheadersheaders) - [browserContext.setGeolocation(geolocation)](#browsercontextsetgeolocationgeolocation) +- [browserContext.setOffline(offline)](#browsercontextsetofflineoffline) - [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions) diff --git a/src/browserContext.ts b/src/browserContext.ts index 54fdd05a02..40362e5abd 100644 --- a/src/browserContext.ts +++ b/src/browserContext.ts @@ -32,6 +32,7 @@ export type BrowserContextOptions = { geolocation?: types.Geolocation, permissions?: { [key: string]: string[] }, extraHTTPHeaders?: network.Headers, + offline?: boolean, }; export interface BrowserContext { @@ -46,6 +47,7 @@ export interface BrowserContext { clearPermissions(): Promise; setGeolocation(geolocation: types.Geolocation | null): Promise; setExtraHTTPHeaders(headers: network.Headers): Promise; + setOffline(offline: boolean): Promise; addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]): Promise; exposeFunction(name: string, playwrightFunction: Function): Promise; close(): Promise; diff --git a/src/chromium/crBrowser.ts b/src/chromium/crBrowser.ts index f9d78346cb..a539157088 100644 --- a/src/chromium/crBrowser.ts +++ b/src/chromium/crBrowser.ts @@ -221,6 +221,8 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1]))); if (this._options.geolocation) await this.setGeolocation(this._options.geolocation); + if (this._options.offline) + await this.setOffline(this._options.offline); } _existingPages(): Page[] { @@ -319,6 +321,12 @@ export class CRBrowserContext extends platform.EventEmitter implements BrowserCo await (page._delegate as CRPage).updateExtraHTTPHeaders(); } + async setOffline(offline: boolean): Promise { + this._options.offline = offline; + for (const page of this._existingPages()) + await (page._delegate as CRPage)._networkManager.setOffline(offline); + } + async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) { const source = await helper.evaluationScript(script, args); this._evaluateOnNewDocumentSources.push(source); diff --git a/src/chromium/crNetworkManager.ts b/src/chromium/crNetworkManager.ts index abc8b18137..4ce018e2c9 100644 --- a/src/chromium/crNetworkManager.ts +++ b/src/chromium/crNetworkManager.ts @@ -29,7 +29,6 @@ export class CRNetworkManager { private _page: Page; private _requestIdToRequest = new Map(); private _requestIdToRequestWillBeSentEvent = new Map(); - private _offline = false; private _credentials: {username: string, password: string} | null = null; private _attemptedAuthentications = new Set(); private _userRequestInterceptionEnabled = false; @@ -68,10 +67,9 @@ export class CRNetworkManager { await this._updateProtocolRequestInterception(); } - async setOfflineMode(value: boolean) { - this._offline = value; + async setOffline(offline: boolean) { await this._client.send('Network.emulateNetworkConditions', { - offline: this._offline, + offline, // values of 0 remove any active throttling. crbug.com/456324#c9 latency: 0, downloadThroughput: -1, diff --git a/src/chromium/crPage.ts b/src/chromium/crPage.ts index de04ad6874..c4d3702e70 100644 --- a/src/chromium/crPage.ts +++ b/src/chromium/crPage.ts @@ -119,6 +119,8 @@ export class CRPage implements PageDelegate { if (options.geolocation) promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation)); promises.push(this.updateExtraHTTPHeaders()); + if (options.offline) + promises.push(this._networkManager.setOffline(options.offline)); for (const binding of this._browserContext._pageBindings.values()) promises.push(this._initBinding(binding)); for (const source of this._browserContext._evaluateOnNewDocumentSources) @@ -373,10 +375,6 @@ export class CRPage implements PageDelegate { await this._networkManager.setRequestInterception(enabled); } - async setOfflineMode(value: boolean) { - await this._networkManager.setOfflineMode(value); - } - async authenticate(credentials: types.Credentials | null) { await this._networkManager.authenticate(credentials); } diff --git a/src/firefox/ffBrowser.ts b/src/firefox/ffBrowser.ts index 335a35526c..cfd55946e9 100644 --- a/src/firefox/ffBrowser.ts +++ b/src/firefox/ffBrowser.ts @@ -286,6 +286,8 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo await this.setGeolocation(this._options.geolocation); if (this._options.extraHTTPHeaders) await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders); + if (this._options.offline) + await this.setOffline(this._options.offline); } _existingPages(): Page[] { @@ -366,6 +368,12 @@ export class FFBrowserContext extends platform.EventEmitter implements BrowserCo await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId || undefined, headers: headersArray(this._options.extraHTTPHeaders) }); } + async setOffline(offline: boolean): Promise { + if (offline) + throw new Error('Offline mode is not implemented in Firefox'); + this._options.offline = offline; + } + async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) { const source = await helper.evaluationScript(script, args); this._evaluateOnNewDocumentSources.push(source); diff --git a/src/firefox/ffPage.ts b/src/firefox/ffPage.ts index d2882997b1..0965160346 100644 --- a/src/firefox/ffPage.ts +++ b/src/firefox/ffPage.ts @@ -279,10 +279,6 @@ export class FFPage implements PageDelegate { await this._networkManager.setRequestInterception(enabled); } - async setOfflineMode(enabled: boolean): Promise { - throw new Error('Offline mode not implemented in Firefox'); - } - async authenticate(credentials: types.Credentials | null): Promise { await this._session.send('Network.setAuthCredentials', credentials || { username: null, password: null }); } diff --git a/src/page.ts b/src/page.ts index 9ed872f01e..3c8425e927 100644 --- a/src/page.ts +++ b/src/page.ts @@ -50,7 +50,6 @@ export interface PageDelegate { setEmulateMedia(mediaType: types.MediaType | null, colorScheme: types.ColorScheme | null): Promise; setCacheEnabled(enabled: boolean): Promise; setRequestInterception(enabled: boolean): Promise; - setOfflineMode(enabled: boolean): Promise; authenticate(credentials: types.Credentials | null): Promise; setFileChooserIntercepted(enabled: boolean): Promise; @@ -82,7 +81,6 @@ type PageState = { extraHTTPHeaders: network.Headers | null; cacheEnabled: boolean | null; interceptNetwork: boolean | null; - offlineMode: boolean | null; credentials: types.Credentials | null; hasTouch: boolean | null; }; @@ -149,7 +147,6 @@ export class Page extends platform.EventEmitter { extraHTTPHeaders: null, cacheEnabled: null, interceptNetwork: null, - offlineMode: null, credentials: null, hasTouch: null, }; @@ -421,13 +418,6 @@ export class Page extends platform.EventEmitter { request.continue(); } - async setOfflineMode(enabled: boolean) { - if (this._state.offlineMode === enabled) - return; - this._state.offlineMode = enabled; - await this._delegate.setOfflineMode(enabled); - } - async authenticate(credentials: types.Credentials | null) { this._state.credentials = credentials; await this._delegate.authenticate(credentials); diff --git a/src/webkit/wkBrowser.ts b/src/webkit/wkBrowser.ts index b0684d4070..e8636b79c5 100644 --- a/src/webkit/wkBrowser.ts +++ b/src/webkit/wkBrowser.ts @@ -207,6 +207,8 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo await Promise.all(entries.map(entry => this.setPermissions(entry[0], entry[1]))); if (this._options.geolocation) await this.setGeolocation(this._options.geolocation); + if (this._options.offline) + await this.setOffline(this._options.offline); } _existingPages(): Page[] { @@ -291,6 +293,12 @@ export class WKBrowserContext extends platform.EventEmitter implements BrowserCo await (page._delegate as WKPage).updateExtraHTTPHeaders(); } + async setOffline(offline: boolean): Promise { + this._options.offline = offline; + for (const page of this._existingPages()) + await (page._delegate as WKPage).updateOffline(); + } + async addInitScript(script: Function | string | { path?: string, content?: string }, ...args: any[]) { const source = await helper.evaluationScript(script, args); this._evaluateOnNewDocumentSources.push(source); diff --git a/src/webkit/wkPage.ts b/src/webkit/wkPage.ts index 95c4e0d2f2..7cf3379f6b 100644 --- a/src/webkit/wkPage.ts +++ b/src/webkit/wkPage.ts @@ -129,8 +129,6 @@ export class WKPage implements PageDelegate { if (this._page._state.interceptNetwork) promises.push(session.send('Network.setInterceptionEnabled', { enabled: true, interceptRequests: true })); - if (this._page._state.offlineMode) - promises.push(session.send('Network.setEmulateOfflineState', { offline: true })); if (this._page._state.cacheEnabled === false) promises.push(session.send('Network.setResourceCachingDisabled', { disabled: true })); @@ -145,6 +143,8 @@ export class WKPage implements PageDelegate { if (contextOptions.bypassCSP) promises.push(session.send('Page.setBypassCSP', { enabled: true })); promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() })); + if (contextOptions.offline) + promises.push(session.send('Network.setEmulateOfflineState', { offline: true })); if (this._page._state.hasTouch) promises.push(session.send('Page.setTouchEmulationEnabled', { enabled: true })); if (contextOptions.timezoneId) { @@ -426,8 +426,8 @@ export class WKPage implements PageDelegate { await this._updateState('Network.setInterceptionEnabled', { enabled, interceptRequests: enabled }); } - async setOfflineMode(offline: boolean) { - await this._updateState('Network.setEmulateOfflineState', { offline }); + async updateOffline() { + await this._updateState('Network.setEmulateOfflineState', { offline: !!this._page.context()._options.offline }); } async authenticate(credentials: types.Credentials | null) { diff --git a/test/browsercontext.spec.js b/test/browsercontext.spec.js index 1d6eb330ea..46ff5a178e 100644 --- a/test/browsercontext.spec.js +++ b/test/browsercontext.spec.js @@ -360,6 +360,30 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, FF }); }); + describe.fail(FFOX)('BrowserContext.setOffline', function() { + it('should work with initial option', async({browser, server}) => { + const context = await browser.newContext({offline: true}); + const page = await context.newPage(); + let error = null; + await page.goto(server.EMPTY_PAGE).catch(e => error = e); + expect(error).toBeTruthy(); + await context.setOffline(false); + const response = await page.goto(server.EMPTY_PAGE); + expect(response.status()).toBe(200); + await context.close(); + }); + it('should emulate navigator.onLine', async({browser, server}) => { + const context = await browser.newContext(); + const page = await context.newPage(); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.setOffline(true); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); + await context.setOffline(false); + expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); + await context.close(); + }); + }); + describe('Events.BrowserContext.Page', function() { it('should report when a new page is created and closed', async({browser, server}) => { const context = await browser.newContext(); diff --git a/test/interception.spec.js b/test/interception.spec.js index 837e1905dc..6f5f349b54 100644 --- a/test/interception.spec.js +++ b/test/interception.spec.js @@ -524,25 +524,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, p }); }); - describe.fail(FFOX)('Interception.setOfflineMode', function() { - it('should work', async({page, server}) => { - await page.setOfflineMode(true); - let error = null; - await page.goto(server.EMPTY_PAGE).catch(e => error = e); - expect(error).toBeTruthy(); - await page.setOfflineMode(false); - const response = await page.goto(server.EMPTY_PAGE); - expect(response.status()).toBe(200); - }); - it('should emulate navigator.onLine', async({page, server}) => { - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); - await page.setOfflineMode(true); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(false); - await page.setOfflineMode(false); - expect(await page.evaluate(() => window.navigator.onLine)).toBe(true); - }); - }); - describe('Interception vs isNavigationRequest', () => { it('should work with request interception', async({page, server}) => { const requests = new Map(); diff --git a/test/popup.spec.js b/test/popup.spec.js index 3e7ce606d2..cd6a5adc98 100644 --- a/test/popup.spec.js +++ b/test/popup.spec.js @@ -48,6 +48,18 @@ module.exports.describe = function({testRunner, expect, playwright, CHROMIUM, WE await context.close(); expect(request.headers['foo']).toBe('bar'); }); + it.fail(FFOX)('should inherit offline from browser context', async function({browser, server}) { + const context = await browser.newContext(); + const page = await context.newPage(); + await page.goto(server.EMPTY_PAGE); + await context.setOffline(true); + const online = await page.evaluate(url => { + const win = window.open(url); + return win.navigator.onLine; + }, server.PREFIX + '/dummy.html'); + await context.close(); + expect(online).toBe(false); + }); it.skip(FFOX).fail(CHROMIUM)('should inherit touch support from browser context', async function({browser, server}) { const context = await browser.newContext({ viewport: { width: 400, height: 500, isMobile: true } diff --git a/test/test.js b/test/test.js index 1f067902af..351edd8882 100644 --- a/test/test.js +++ b/test/test.js @@ -87,7 +87,7 @@ if (process.env.BROWSER === 'firefox') { ...require('../lib/events').Events, ...require('../lib/chromium/events').Events, }; - missingCoverage = ['browserContext.setGeolocation', 'page.setOfflineMode']; + missingCoverage = ['browserContext.setGeolocation', 'browserContext.setOffline']; } else if (process.env.BROWSER === 'webkit') { product = 'WebKit'; events = require('../lib/events').Events;