From decf373c81de02b8c4457295ae6f5fa33eb97cc2 Mon Sep 17 00:00:00 2001 From: Joel Einbinder Date: Wed, 13 Jan 2021 12:08:14 -0800 Subject: [PATCH] fix(electron): return a ChromiumBrowserContext for electron (#4913) --- src/client/android.ts | 5 +++-- src/client/browserContext.ts | 4 +--- src/client/chromiumBrowserContext.ts | 3 ++- src/client/connection.ts | 6 +++--- src/client/page.ts | 3 ++- src/dispatchers/browserContextDispatcher.ts | 4 ++-- src/dispatchers/browserDispatcher.ts | 6 +++--- src/protocol/channels.ts | 2 +- src/protocol/protocol.yml | 2 +- src/server/android/android.ts | 1 + src/server/browser.ts | 1 + src/server/browserType.ts | 3 ++- src/server/electron/electron.ts | 1 + src/utils/browserPaths.ts | 2 +- test/android/browser.spec.ts | 8 ++++++++ test/electron/electron-app.spec.ts | 8 ++++++++ types/android.d.ts | 4 ++-- 17 files changed, 42 insertions(+), 21 deletions(-) diff --git a/src/client/android.ts b/src/client/android.ts index a20ece1929b6e..26f784dde8bcd 100644 --- a/src/client/android.ts +++ b/src/client/android.ts @@ -27,6 +27,7 @@ import { Page } from './page'; import { TimeoutSettings } from '../utils/timeoutSettings'; import { Waiter } from './waiter'; import { EventEmitter } from 'events'; +import { ChromiumBrowserContext } from './chromiumBrowserContext'; type Direction = 'down' | 'up' | 'left' | 'right'; type SpeedOptions = { speed?: number }; @@ -233,11 +234,11 @@ export class AndroidDevice extends ChannelOwner { + async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise { return this._wrapApiCall('androidDevice.launchBrowser', async () => { const contextOptions = await prepareBrowserContextOptions(options); const { context } = await this._channel.launchBrowser(contextOptions); - return BrowserContext.from(context); + return BrowserContext.from(context) as ChromiumBrowserContext; }); } diff --git a/src/client/browserContext.ts b/src/client/browserContext.ts index e589a1491860d..f786994c34017 100644 --- a/src/client/browserContext.ts +++ b/src/client/browserContext.ts @@ -40,7 +40,6 @@ export class BrowserContext extends ChannelOwner(); private _routes: { url: URLMatch, handler: network.RouteHandler }[] = []; readonly _browser: Browser | null = null; - readonly _browserName: string; readonly _bindings = new Map any>(); _timeoutSettings = new TimeoutSettings(); _ownerPage: Page | undefined; @@ -55,11 +54,10 @@ export class BrowserContext extends ChannelOwner this._onBinding(BindingCall.from(binding))); this._channel.on('close', () => this._onClose()); diff --git a/src/client/chromiumBrowserContext.ts b/src/client/chromiumBrowserContext.ts index a7bc1ae3f1d0c..1bb21b669fdb6 100644 --- a/src/client/chromiumBrowserContext.ts +++ b/src/client/chromiumBrowserContext.ts @@ -27,9 +27,10 @@ import * as api from '../../types/types'; export class ChromiumBrowserContext extends BrowserContext implements api.ChromiumBrowserContext { _backgroundPages = new Set(); _serviceWorkers = new Set(); + _isChromium = true; constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer) { - super(parent, type, guid, initializer, 'chromium'); + super(parent, type, guid, initializer); this._channel.on('crBackgroundPage', ({ page }) => { const backgroundPage = Page.from(page); this._backgroundPages.add(backgroundPage); diff --git a/src/client/connection.ts b/src/client/connection.ts index d902a19431c8a..73d587dde2c30 100644 --- a/src/client/connection.ts +++ b/src/client/connection.ts @@ -173,11 +173,11 @@ export class Connection { break; } case 'BrowserContext': { - const browserName = (initializer as channels.BrowserContextInitializer).browserName; - if (browserName === 'chromium') + const {isChromium} = (initializer as channels.BrowserContextInitializer); + if (isChromium) result = new ChromiumBrowserContext(parent, type, guid, initializer); else - result = new BrowserContext(parent, type, guid, initializer, browserName); + result = new BrowserContext(parent, type, guid, initializer); break; } case 'BrowserType': diff --git a/src/client/page.ts b/src/client/page.ts index c6be7904c7145..9081222dc9066 100644 --- a/src/client/page.ts +++ b/src/client/page.ts @@ -46,6 +46,7 @@ import { evaluationScript, urlMatches } from './clientHelper'; import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } from '../utils/utils'; import { isSafeCloseError } from '../utils/errors'; import { Video } from './video'; +import type { ChromiumBrowserContext } from './chromiumBrowserContext'; const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs)); const mkdirAsync = util.promisify(fs.mkdir); @@ -133,7 +134,7 @@ export class Page extends ChannelOwner this.emit(Events.Page.WebSocket, WebSocket.from(webSocket))); this._channel.on('worker', ({ worker }) => this._onWorker(Worker.from(worker))); - if (this._browserContext._browserName === 'chromium') { + if ((this._browserContext as ChromiumBrowserContext)._isChromium) { this.coverage = new ChromiumCoverage(this._channel); this.pdf = options => this._pdf(options); } else { diff --git a/src/dispatchers/browserContextDispatcher.ts b/src/dispatchers/browserContextDispatcher.ts index d2d1eed39afb8..c583a07c416af 100644 --- a/src/dispatchers/browserContextDispatcher.ts +++ b/src/dispatchers/browserContextDispatcher.ts @@ -27,7 +27,7 @@ export class BrowserContextDispatcher extends Dispatcher { - if (this._object._browser._options.name !== 'chromium') + if (!this._object._browser._options.isChromium) throw new Error(`CDP session is only available in Chromium`); const crBrowserContext = this._object as CRBrowserContext; return { session: new CDPSessionDispatcher(this._scope, await crBrowserContext.newCDPSession((params.page as PageDispatcher)._object)) }; diff --git a/src/dispatchers/browserDispatcher.ts b/src/dispatchers/browserDispatcher.ts index 5dc669f3cd2e4..8fca3f155701c 100644 --- a/src/dispatchers/browserDispatcher.ts +++ b/src/dispatchers/browserDispatcher.ts @@ -45,21 +45,21 @@ export class BrowserDispatcher extends Dispatcher { - if (this._object._options.name !== 'chromium') + if (!this._object._options.isChromium) throw new Error(`CDP session is only available in Chromium`); const crBrowser = this._object as CRBrowser; return { session: new CDPSessionDispatcher(this._scope, await crBrowser.newBrowserCDPSession()) }; } async crStartTracing(params: channels.BrowserCrStartTracingParams): Promise { - if (this._object._options.name !== 'chromium') + if (!this._object._options.isChromium) throw new Error(`Tracing is only available in Chromium`); const crBrowser = this._object as CRBrowser; await crBrowser.startTracing(params.page ? (params.page as PageDispatcher)._object : undefined, params); } async crStopTracing(): Promise { - if (this._object._options.name !== 'chromium') + if (!this._object._options.isChromium) throw new Error(`Tracing is only available in Chromium`); const crBrowser = this._object as CRBrowser; const buffer = await crBrowser.stopTracing(); diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index aea4e3a96077b..c54e5a5c4ef46 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -530,7 +530,7 @@ export type BrowserCrStopTracingResult = { // ----------- BrowserContext ----------- export type BrowserContextInitializer = { - browserName: string, + isChromium: boolean, }; export interface BrowserContextChannel extends Channel { on(event: 'bindingCall', callback: (params: BrowserContextBindingCallEvent) => void): this; diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 7559d3e2a9b1f..4573bb64da96b 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -507,7 +507,7 @@ BrowserContext: type: interface initializer: - browserName: string + isChromium: boolean commands: diff --git a/src/server/android/android.ts b/src/server/android/android.ts index a682b0988bcbf..b3eae364a12e9 100644 --- a/src/server/android/android.ts +++ b/src/server/android/android.ts @@ -256,6 +256,7 @@ export class AndroidDevice extends EventEmitter { const browserOptions: BrowserOptions = { name: 'clank', + isChromium: true, slowMo: 0, persistent: { ...options, noDefaultViewport: true }, downloadsPath: undefined, diff --git a/src/server/browser.ts b/src/server/browser.ts index 30df25e6dd32f..c37501dca7c44 100644 --- a/src/server/browser.ts +++ b/src/server/browser.ts @@ -32,6 +32,7 @@ export interface BrowserProcess { export type BrowserOptions = types.UIOptions & { name: string, + isChromium: boolean, downloadsPath?: string, headful?: boolean, persistent?: types.BrowserContextOptions, // Undefined means no persistent context. diff --git a/src/server/browserType.ts b/src/server/browserType.ts index 140f5b2734499..b24dbf7220ab8 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -38,7 +38,7 @@ const existsAsync = (path: string): Promise => new Promise(resolve => f const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-'); export abstract class BrowserType { - private _name: string; + private _name: browserPaths.BrowserName; private _executablePath: string; private _browserDescriptor: browserPaths.BrowserDescriptor; readonly _browserPath: string; @@ -88,6 +88,7 @@ export abstract class BrowserType { await (options as any).__testHookBeforeCreateBrowser(); const browserOptions: BrowserOptions = { name: this._name, + isChromium: this._name === 'chromium', slowMo: options.slowMo, persistent, headful: !options.headless, diff --git a/src/server/electron/electron.ts b/src/server/electron/electron.ts index eb688f4f29057..f3f14a78f3783 100644 --- a/src/server/electron/electron.ts +++ b/src/server/electron/electron.ts @@ -191,6 +191,7 @@ export class Electron { }; const browserOptions: BrowserOptions = { name: 'electron', + isChromium: true, headful: true, persistent: { noDefaultViewport: true }, browserProcess, diff --git a/src/utils/browserPaths.ts b/src/utils/browserPaths.ts index e52b68fd0a479..2ccc0e9128eb2 100644 --- a/src/utils/browserPaths.ts +++ b/src/utils/browserPaths.ts @@ -21,7 +21,7 @@ import * as path from 'path'; import { getUbuntuVersionSync } from './ubuntuVersion'; import { getFromENV } from './utils'; -export type BrowserName = 'chromium'|'webkit'|'firefox'|'clank'; +export type BrowserName = 'chromium'|'webkit'|'firefox'; export type BrowserPlatform = 'win32'|'win64'|'mac10.13'|'mac10.14'|'mac10.15'|'mac11.0'|'mac11.0-arm64'|'mac11.1'|'mac11.1-arm64'|'ubuntu18.04'|'ubuntu20.04'; export type BrowserDescriptor = { name: BrowserName, diff --git a/test/android/browser.spec.ts b/test/android/browser.spec.ts index 05f58c4a20d83..8ef155d3c01c8 100644 --- a/test/android/browser.spec.ts +++ b/test/android/browser.spec.ts @@ -48,4 +48,12 @@ if (process.env.PW_ANDROID_TESTS) { await page.close(); await context.close(); }); + it('should be able to send CDP messages', async ({ device }) => { + const context = await device.launchBrowser(); + const [page] = context.pages(); + const client = await context.newCDPSession(page); + await client.send('Runtime.enable'); + const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true}); + expect(evalResponse.result.value).toBe(3); + }); } diff --git a/test/electron/electron-app.spec.ts b/test/electron/electron-app.spec.ts index f8852ab4cedb4..01f1115ac1540 100644 --- a/test/electron/electron-app.spec.ts +++ b/test/electron/electron-app.spec.ts @@ -124,4 +124,12 @@ describe('electron app', (suite, { browserName }) => { const clipboardContentRead = await application.evaluate(async ({clipboard}) => clipboard.readText()); await expect(clipboardContentRead).toEqual(clipboardContentToWrite); }); + + it('should be able to send CDP messages', async ({application, window}) => { + const context = await application.context(); + const client = await context.newCDPSession(window); + await client.send('Runtime.enable'); + const evalResponse = await client.send('Runtime.evaluate', {expression: '1 + 2', returnByValue: true}); + expect(evalResponse.result.value).toBe(3); + }); }); diff --git a/types/android.d.ts b/types/android.d.ts index 01813a5bc8e9d..eb87e975d91a0 100644 --- a/types/android.d.ts +++ b/types/android.d.ts @@ -15,7 +15,7 @@ */ import { EventEmitter } from 'events'; -import { BrowserContextOptions, BrowserContext, Page } from './types'; +import { BrowserContextOptions, Page, ChromiumBrowserContext } from './types'; export interface Android extends EventEmitter { setDefaultTimeout(timeout: number): void; @@ -37,7 +37,7 @@ export interface AndroidDevice extends EventEmitter { open(command: string): Promise; installApk(file: string | Buffer, options?: { args?: string[] }): Promise; push(file: string | Buffer, path: string, options?: { mode?: number }): Promise; - launchBrowser(options?: BrowserContextOptions & { pkg?: string }): Promise; + launchBrowser(options?: BrowserContextOptions & { pkg?: string }): Promise; close(): Promise; wait(selector: AndroidSelector, options?: { state?: 'gone' } & { timeout?: number }): Promise;