fix(electron): return a ChromiumBrowserContext for electron (#4913)

This commit is contained in:
Joel Einbinder 2021-01-13 12:08:14 -08:00 committed by GitHub
parent df53cb2f83
commit decf373c81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 42 additions and 21 deletions

View File

@ -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<channels.AndroidDeviceChannel, c
});
}
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<ChromiumBrowserContext> {
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;
});
}

View File

@ -40,7 +40,6 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
_pages = new Set<Page>();
private _routes: { url: URLMatch, handler: network.RouteHandler }[] = [];
readonly _browser: Browser | null = null;
readonly _browserName: string;
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
_timeoutSettings = new TimeoutSettings();
_ownerPage: Page | undefined;
@ -55,11 +54,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
return context ? BrowserContext.from(context) : null;
}
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer, browserName: string) {
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.BrowserContextInitializer) {
super(parent, type, guid, initializer);
if (parent instanceof Browser)
this._browser = parent;
this._browserName = browserName;
this._channel.on('bindingCall', ({binding}) => this._onBinding(BindingCall.from(binding)));
this._channel.on('close', () => this._onClose());

View File

@ -27,9 +27,10 @@ import * as api from '../../types/types';
export class ChromiumBrowserContext extends BrowserContext implements api.ChromiumBrowserContext {
_backgroundPages = new Set<Page>();
_serviceWorkers = new Set<Worker>();
_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);

View File

@ -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':

View File

@ -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<channels.PageChannel, channels.PageInitia
this._channel.on('webSocket', ({ webSocket }) => 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 {

View File

@ -27,7 +27,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
private _context: BrowserContext;
constructor(scope: DispatcherScope, context: BrowserContext) {
super(scope, context, 'BrowserContext', { browserName: context._browser._options.name }, true);
super(scope, context, 'BrowserContext', { isChromium: context._browser._options.isChromium }, true);
this._context = context;
for (const page of context.pages())
@ -131,7 +131,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
}
async crNewCDPSession(params: channels.BrowserContextCrNewCDPSessionParams): Promise<channels.BrowserContextCrNewCDPSessionResult> {
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)) };

View File

@ -45,21 +45,21 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserIniti
}
async crNewBrowserCDPSession(): Promise<channels.BrowserCrNewBrowserCDPSessionResult> {
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<void> {
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<channels.BrowserCrStopTracingResult> {
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();

View File

@ -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;

View File

@ -507,7 +507,7 @@ BrowserContext:
type: interface
initializer:
browserName: string
isChromium: boolean
commands:

View File

@ -256,6 +256,7 @@ export class AndroidDevice extends EventEmitter {
const browserOptions: BrowserOptions = {
name: 'clank',
isChromium: true,
slowMo: 0,
persistent: { ...options, noDefaultViewport: true },
downloadsPath: undefined,

View File

@ -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.

View File

@ -38,7 +38,7 @@ const existsAsync = (path: string): Promise<boolean> => 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,

View File

@ -191,6 +191,7 @@ export class Electron {
};
const browserOptions: BrowserOptions = {
name: 'electron',
isChromium: true,
headful: true,
persistent: { noDefaultViewport: true },
browserProcess,

View File

@ -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,

View File

@ -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);
});
}

View File

@ -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);
});
});

4
types/android.d.ts vendored
View File

@ -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<AndroidSocket>;
installApk(file: string | Buffer, options?: { args?: string[] }): Promise<void>;
push(file: string | Buffer, path: string, options?: { mode?: number }): Promise<void>;
launchBrowser(options?: BrowserContextOptions & { pkg?: string }): Promise<BrowserContext>;
launchBrowser(options?: BrowserContextOptions & { pkg?: string }): Promise<ChromiumBrowserContext>;
close(): Promise<void>;
wait(selector: AndroidSelector, options?: { state?: 'gone' } & { timeout?: number }): Promise<void>;