Skip to content

Commit

Permalink
fix(electron): return a ChromiumBrowserContext for electron (#4913)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelEinbinder committed Jan 13, 2021
1 parent df53cb2 commit decf373
Show file tree
Hide file tree
Showing 17 changed files with 42 additions and 21 deletions.
5 changes: 3 additions & 2 deletions src/client/android.ts
Expand Up @@ -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 };
Expand Down Expand Up @@ -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;
});
}

Expand Down
4 changes: 1 addition & 3 deletions src/client/browserContext.ts
Expand Up @@ -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;
Expand All @@ -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());
Expand Down
3 changes: 2 additions & 1 deletion src/client/chromiumBrowserContext.ts
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/client/connection.ts
Expand Up @@ -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':
Expand Down
3 changes: 2 additions & 1 deletion src/client/page.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/dispatchers/browserContextDispatcher.ts
Expand Up @@ -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())
Expand Down Expand Up @@ -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)) };
Expand Down
6 changes: 3 additions & 3 deletions src/dispatchers/browserDispatcher.ts
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/channels.ts
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/protocol.yml
Expand Up @@ -507,7 +507,7 @@ BrowserContext:
type: interface

initializer:
browserName: string
isChromium: boolean

commands:

Expand Down
1 change: 1 addition & 0 deletions src/server/android/android.ts
Expand Up @@ -256,6 +256,7 @@ export class AndroidDevice extends EventEmitter {

const browserOptions: BrowserOptions = {
name: 'clank',
isChromium: true,
slowMo: 0,
persistent: { ...options, noDefaultViewport: true },
downloadsPath: undefined,
Expand Down
1 change: 1 addition & 0 deletions src/server/browser.ts
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/server/browserType.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/server/electron/electron.ts
Expand Up @@ -191,6 +191,7 @@ export class Electron {
};
const browserOptions: BrowserOptions = {
name: 'electron',
isChromium: true,
headful: true,
persistent: { noDefaultViewport: true },
browserProcess,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/browserPaths.ts
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions test/android/browser.spec.ts
Expand Up @@ -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);
});
}
8 changes: 8 additions & 0 deletions test/electron/electron-app.spec.ts
Expand Up @@ -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 changes: 2 additions & 2 deletions types/android.d.ts
Expand Up @@ -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;
Expand All @@ -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>;
Expand Down

0 comments on commit decf373

Please sign in to comment.