Skip to content

Commit

Permalink
api: introduce BrowserType with a single interface, update top-level …
Browse files Browse the repository at this point in the history
…api (#636)
  • Loading branch information
dgozman committed Jan 24, 2020
1 parent 199d094 commit c453851
Show file tree
Hide file tree
Showing 13 changed files with 341 additions and 408 deletions.
28 changes: 14 additions & 14 deletions README.md
Expand Up @@ -39,15 +39,15 @@ Playwright can be used to create a browser instance, open pages, and then manipu
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.

```js
const pw = require('playwright');
const playwright = require('playwright');

(async () => {
for (const name of ['chromium', 'firefox', 'webkit']) {
const browser = await pw[name].launch();
for (const browserType of ['chromium', 'firefox', 'webkit']) {
const browser = await playwright[browserType].launch();
const context = await browser.newContext();
const page = await context.newPage('http://whatsmyuseragent.org/');
await page.screenshot({ path: `example-${name}.png` });

await page.screenshot({ path: `example-${browserType}.png` });
await browser.close();
}
})();
Expand All @@ -58,11 +58,11 @@ const pw = require('playwright');
This snippet emulates Mobile Safari on a device at a given geolocation, navigates to maps.google.com, performs action and takes a screenshot.

```js
const pw = require('playwright');
const iPhone11 = pw.devices['iPhone 11 Pro'];
const { webkit, devices } = require('playwright');
const iPhone11 = devices['iPhone 11 Pro'];

(async () => {
const browser = await pw.webkit.launch();
const browser = await webkit.launch();
const context = await browser.newContext({
viewport: iPhone11.viewport,
userAgent: iPhone11.userAgent,
Expand All @@ -73,19 +73,19 @@ const iPhone11 = pw.devices['iPhone 11 Pro'];
const page = await context.newPage('https://maps.google.com');
await page.click('text="Your location"');
await page.waitForRequest(/.*preview\/pwa/);
await page.screenshot({ path: 'colosseum-iphone.png' });
await page.screenshot({ path: 'colosseum-iphone.png' });
await browser.close();
})();
```

And here is the same script for Chrome on Android.

```js
const pw = require('playwright');
const pixel2 = pw.devices['Pixel 2'];
const { chromium, devices } = require('playwright');
const pixel2 = devices['Pixel 2'];

(async () => {
const browser = await pw.chromium.launch();
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: pixel2.viewport,
userAgent: pixel2.userAgent,
Expand All @@ -106,10 +106,10 @@ const pixel2 = pw.devices['Pixel 2'];
This code snippet navigates to example.com in Firefox, and executes a script in the page context.

```js
const pw = require('playwright');
const { firefox } = require('playwright');

(async () => {
const browser = await pw.firefox.launch(); // or 'chromium', 'webkit'
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();

Expand Down
501 changes: 214 additions & 287 deletions docs/api.md

Large diffs are not rendered by default.

5 changes: 1 addition & 4 deletions docs/web.md
Expand Up @@ -4,10 +4,7 @@ Playwright contains a version bundled for web browsers under `playwright/web.js`
installs playwright under `window.playwrightweb`.
You can use it in the web page to drive another browser instance.

API consists of a single `connect` function, similar to
[chromiumPlaywright.connect(options)](api.md#chromiumplaywrightconnectoptions),
[firefoxPlaywright.connect(options)](api.md#firefoxplaywrightconnectoptions) and
[webkitPlaywright.connect(options)](api.md#webkitplaywrightconnectoptions).
API consists of a single `connect` function, similar to [browserType.connect(options)](api.md#browsertypeconnectoptions).

```html
<script src='playwright/web.js'></script>
Expand Down
6 changes: 3 additions & 3 deletions download-browser.js
Expand Up @@ -15,13 +15,13 @@
*/

async function downloadBrowser(browser) {
const playwright = require('.')[browser];
const browserType = require('.')[browser];
let progressBar = null;
let lastDownloadedBytes = 0;
function onProgress(downloadedBytes, totalBytes) {
if (!progressBar) {
const ProgressBar = require('progress');
progressBar = new ProgressBar(`Downloading ${browser} ${playwright._revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
progressBar = new ProgressBar(`Downloading ${browser} ${browserType._revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
complete: '=',
incomplete: ' ',
width: 20,
Expand All @@ -33,7 +33,7 @@ async function downloadBrowser(browser) {
progressBar.tick(delta);
}

const fetcher = playwright._createBrowserFetcher();
const fetcher = browserType._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
Expand Down
8 changes: 5 additions & 3 deletions index.d.ts
Expand Up @@ -15,7 +15,9 @@
*/

export * from './lib/api';
export const chromium: import('./lib/api').ChromiumPlaywright;
export const firefox: import('./lib/api').FirefoxPlaywright;
export const webkit: import('./lib/api').WebKitPlaywright;
export const devices: typeof import('./lib/deviceDescriptors').DeviceDescriptors;
export const errors: { TimeoutError: typeof import('./lib/errors').TimeoutError };
export const chromium: import('./lib/api').Chromium;
export const firefox: import('./lib/api').Firefox;
export const webkit: import('./lib/api').WebKit;
export type PlaywrightWeb = typeof import('./lib/web');
17 changes: 11 additions & 6 deletions index.js
Expand Up @@ -17,16 +17,21 @@
const { helper } = require('./lib/helper');
const api = require('./lib/api');
const packageJson = require('./package.json');
const { DeviceDescriptors } = require('./lib/deviceDescriptors');
const DeviceDescriptors = require('./lib/deviceDescriptors');
const { TimeoutError } = require('./lib/errors');
const { Chromium } = require('./lib/server/chromium');
const { Firefox } = require('./lib/server/firefox');
const { WebKit } = require('./lib/server/webkit');

for (const className in api) {
if (typeof api[className] === 'function')
helper.installApiHooks(className, api[className]);
}

module.exports = {
chromium: new api.ChromiumPlaywright(__dirname, packageJson.playwright.chromium_revision),
firefox: new api.FirefoxPlaywright(__dirname, packageJson.playwright.firefox_revision),
webkit: new api.WebKitPlaywright(__dirname, packageJson.playwright.webkit_revision),
devices: DeviceDescriptors
};
devices: DeviceDescriptors,
errors: { TimeoutError },
chromium: new Chromium(__dirname, packageJson.playwright.chromium_revision),
firefox: new Firefox(__dirname, packageJson.playwright.firefox_revision),
webkit: new WebKit(__dirname, packageJson.playwright.webkit_revision),
}
3 changes: 1 addition & 2 deletions prepare.js
Expand Up @@ -54,8 +54,7 @@ async function downloadAndCleanup(browser) {
const revisionInfo = await downloadBrowser(browser);

// Remove previous revisions.
const playwright = require('.')[browser];
const fetcher = playwright._createBrowserFetcher();
const fetcher = require('.')[browser]._createBrowserFetcher();
const localRevisions = await fetcher.localRevisions();
const cleanupOldVersions = localRevisions.filter(revision => revision !== revisionInfo.revision).map(revision => fetcher.remove(revision));
await Promise.all([...cleanupOldVersions]);
Expand Down
5 changes: 1 addition & 4 deletions src/api.ts
Expand Up @@ -35,8 +35,5 @@ export { FFBrowser as FirefoxBrowser } from './firefox/ffBrowser';

export { WKBrowser as WebKitBrowser } from './webkit/wkBrowser';

export { Playwright } from './server/playwright';
export { BrowserType } from './server/browserType';
export { BrowserApp } from './server/browserApp';
export { CRPlaywright as ChromiumPlaywright } from './server/crPlaywright';
export { FFPlaywright as FirefoxPlaywright } from './server/ffPlaywright';
export { WKPlaywright as WebKitPlaywright } from './server/wkPlaywright';
50 changes: 50 additions & 0 deletions src/server/browserType.ts
@@ -0,0 +1,50 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as types from '../types';
import { TimeoutError } from '../errors';
import { Browser, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';

export type BrowserArgOptions = {
headless?: boolean,
args?: string[],
userDataDir?: string,
devtools?: boolean,
};

export type LaunchOptions = BrowserArgOptions & {
executablePath?: string,
ignoreDefaultArgs?: boolean | string[],
handleSIGINT?: boolean,
handleSIGTERM?: boolean,
handleSIGHUP?: boolean,
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
webSocket?: boolean,
slowMo?: number, // TODO: we probably don't want this in launchBrowserApp.
};

export interface BrowserType {
executablePath(): string;
launchBrowserApp(options?: LaunchOptions): Promise<BrowserApp>;
launch(options?: LaunchOptions): Promise<Browser>;
defaultArgs(options?: BrowserArgOptions): string[];
connect(options: ConnectOptions & { browserURL?: string }): Promise<Browser>;
devices: types.Devices;
errors: { TimeoutError: typeof TimeoutError };
}
29 changes: 3 additions & 26 deletions src/server/crPlaywright.ts → src/server/chromium.ts
Expand Up @@ -29,34 +29,11 @@ import { TimeoutError } from '../errors';
import { launchProcess, waitForLine } from '../server/processLauncher';
import { kBrowserCloseMessageId } from '../chromium/crConnection';
import { PipeTransport } from './pipeTransport';
import { Playwright } from './playwright';
import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { createTransport, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';

export type SlowMoOptions = {
slowMo?: number,
};

export type ChromiumArgOptions = {
headless?: boolean,
args?: string[],
userDataDir?: string,
devtools?: boolean,
};

export type LaunchOptions = ChromiumArgOptions & SlowMoOptions & {
executablePath?: string,
ignoreDefaultArgs?: boolean | string[],
handleSIGINT?: boolean,
handleSIGTERM?: boolean,
handleSIGHUP?: boolean,
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
webSocket?: boolean,
};

export class CRPlaywright implements Playwright {
export class Chromium implements BrowserType {
private _projectRoot: string;
readonly _revision: string;

Expand Down Expand Up @@ -184,7 +161,7 @@ export class CRPlaywright implements Playwright {
return { TimeoutError };
}

defaultArgs(options: ChromiumArgOptions = {}): string[] {
defaultArgs(options: BrowserArgOptions = {}): string[] {
const {
devtools = false,
headless = !devtools,
Expand Down
38 changes: 10 additions & 28 deletions src/server/ffPlaywright.ts → src/server/firefox.ts
Expand Up @@ -28,33 +28,11 @@ import * as path from 'path';
import * as util from 'util';
import { TimeoutError } from '../errors';
import { assert } from '../helper';
import { Playwright } from './playwright';
import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { createTransport, ConnectOptions } from '../browser';
import { BrowserApp } from './browserApp';

export type SlowMoOptions = {
slowMo?: number,
};

export type FirefoxArgOptions = {
headless?: boolean,
args?: string[],
userDataDir?: string,
};

export type LaunchOptions = FirefoxArgOptions & SlowMoOptions & {
executablePath?: string,
ignoreDefaultArgs?: boolean | string[],
handleSIGINT?: boolean,
handleSIGTERM?: boolean,
handleSIGHUP?: boolean,
timeout?: number,
dumpio?: boolean,
env?: {[key: string]: string} | undefined,
webSocket?: boolean,
};

export class FFPlaywright implements Playwright {
export class Firefox implements BrowserType {
private _projectRoot: string;
readonly _revision: string;

Expand Down Expand Up @@ -151,7 +129,9 @@ export class FFPlaywright implements Playwright {
return new BrowserApp(launchedProcess, gracefullyClose, connectOptions);
}

async connect(options: ConnectOptions): Promise<FFBrowser> {
async connect(options: ConnectOptions & { browserURL?: string }): Promise<FFBrowser> {
if (options.browserURL)
throw new Error('Option "browserURL" is not supported by Firefox');
if (options.transport && options.transport.onmessage)
throw new Error('Transport is already in use');
return FFBrowser.connect(options);
Expand All @@ -169,13 +149,15 @@ export class FFPlaywright implements Playwright {
return { TimeoutError };
}

// TODO: rename userDataDir to profile?
defaultArgs(options: FirefoxArgOptions = {}): string[] {
defaultArgs(options: BrowserArgOptions = {}): string[] {
const {
headless = true,
devtools = false,
headless = !devtools,
args = [],
userDataDir = null,
} = options;
if (devtools)
throw new Error('Option "devtools" is not supported by Firefox');
const firefoxArguments = [...DEFAULT_ARGS];
if (userDataDir)
firefoxArguments.push('-profile', userDataDir);
Expand Down
22 changes: 18 additions & 4 deletions src/server/playwright.ts
Expand Up @@ -16,9 +16,23 @@

import * as types from '../types';
import { TimeoutError } from '../errors';
import { DeviceDescriptors } from '../deviceDescriptors';
import { Chromium } from './chromium';
import { WebKit } from './webkit';
import { Firefox } from './firefox';

export interface Playwright {
executablePath(): string;
devices: types.Devices;
errors: { TimeoutError: typeof TimeoutError };
export class Playwright {
readonly devices: types.Devices;
readonly errors: { TimeoutError: typeof TimeoutError };
readonly chromium: Chromium;
readonly firefox: Firefox;
readonly webkit: WebKit;

constructor(projectRoot: string, revisions: { chromium_revision: string, firefox_revision: string, webkit_revision: string }) {
this.devices = DeviceDescriptors;
this.errors = { TimeoutError };
this.chromium = new Chromium(projectRoot, revisions.chromium_revision);
this.firefox = new Firefox(projectRoot, revisions.firefox_revision);
this.webkit = new WebKit(projectRoot, revisions.webkit_revision);
}
}

1 comment on commit c453851

@jperl
Copy link
Contributor

@jperl jperl commented on c453851 Jan 24, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Please sign in to comment.