Skip to content

Commit

Permalink
feat(screencast): add expreimental public API on context (#3766)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s authored Sep 5, 2020
1 parent f6aab9e commit 66985fc
Show file tree
Hide file tree
Showing 24 changed files with 287 additions and 130 deletions.
19 changes: 19 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ Indicates that the browser is connected.
- `password` <[string]>
- `colorScheme` <"light"|"dark"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See [page.emulateMedia(options)](#pageemulatemediaoptions) for more details. Defaults to '`light`'.
- `logger` <[Logger]> Logger sink for Playwright logging.
- `_recordVideos` <[Object]> **experimental** Enables automatic video recording for new pages. The video will have frames with the provided dimensions. Actual picture of the page will be scaled down if necessary to fit specified size.
- `width` <[number]> Video frame width.
- `height` <[number]> Video frame height.
- returns: <[Promise]<[BrowserContext]>>

Creates a new browser context. It won't share cookies/cache with other browser contexts.
Expand Down Expand Up @@ -262,6 +265,9 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `password` <[string]>
- `colorScheme` <"light"|"dark"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See [page.emulateMedia(options)](#pageemulatemediaoptions) for more details. Defaults to '`light`'.
- `logger` <[Logger]> Logger sink for Playwright logging.
- `_recordVideos` <[Object]> **experimental** Enables automatic video recording for the new page. The video will have frames with the provided dimensions. Actual picture of the page will be scaled down if necessary to fit specified size.
- `width` <[number]> Video frame width.
- `height` <[number]> Video frame height.
- returns: <[Promise]<[Page]>>

Creates a new page in a new browser context. Closing this page will close the context as well.
Expand Down Expand Up @@ -684,6 +690,7 @@ page.removeListener('request', logRequest);
```

<!-- GEN:toc -->
- [event: '_videostarted'](#event-videostarted)
- [event: 'close'](#event-close-1)
- [event: 'console'](#event-console)
- [event: 'crash'](#event-crash)
Expand Down Expand Up @@ -770,6 +777,12 @@ page.removeListener('request', logRequest);
- [page.workers()](#pageworkers)
<!-- GEN:stop -->

#### event: '_videostarted'
- <[Object]> Video object.

**experimental**
Emitted when video recording has started for this page. The event will fire only if [`_recordVideos`](#browsernewcontextoptions) option is configured on the parent context.

#### event: 'close'

Emitted when the page closes.
Expand Down Expand Up @@ -4157,6 +4170,7 @@ This methods attaches Playwright to an existing browser instance.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
- `firefoxUserPrefs` <[Object]<[string], [string]|[number]|[boolean]>> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
Expand Down Expand Up @@ -4231,6 +4245,10 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
- `username` <[string]>
- `password` <[string]>
- `colorScheme` <"light"|"dark"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See [page.emulateMedia(options)](#pageemulatemediaoptions) for more details. Defaults to '`light`'.
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `_recordVideos` <[Object]> **experimental** Enables automatic video recording for the new page. The video will have frames with the provided dimensions. Actual picture of the page will be scaled down if necessary to fit specified size.
- `width` <[number]> Video frame width.
- `height` <[number]> Video frame height.
- returns: <[Promise]<[BrowserContext]>> Promise that resolves to the persistent browser context instance.

Launches browser that uses persistent storage located at `userDataDir` and returns the only context. Closing this context will automatically close the browser.
Expand All @@ -4248,6 +4266,7 @@ Launches browser that uses persistent storage located at `userDataDir` and retur
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
- `firefoxUserPrefs` <[Object]<[string], [string]|[number]|[boolean]>> Firefox user preferences. Learn more about the Firefox user preferences at [`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
Expand Down
2 changes: 1 addition & 1 deletion src/client/channelOwner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
const base = new EventEmitter();
this._channel = new Proxy(base, {
get: (obj: any, prop) => {
if (String(prop).startsWith('_'))
if (String(prop).startsWith('_') && String(prop) !== '_enableScreencast' && String(prop) !== '_disableScreencast')
return obj[prop];
if (prop === 'then')
return obj.then;
Expand Down
4 changes: 4 additions & 0 deletions src/client/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { WebKitBrowser } from './webkitBrowser';
import { FirefoxBrowser } from './firefoxBrowser';
import { debugLogger } from '../utils/debugLogger';
import { SelectorsOwner } from './selectors';
import { Video } from './video';

class Root extends ChannelOwner<channels.Channel, {}> {
constructor(connection: Connection) {
Expand Down Expand Up @@ -207,6 +208,9 @@ export class Connection {
case 'Route':
result = new Route(parent, type, guid, initializer);
break;
case 'Video':
result = new Video(parent, type, guid, initializer);
break;
case 'Stream':
result = new Stream(parent, type, guid, initializer);
break;
Expand Down
1 change: 1 addition & 0 deletions src/client/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const Events = {
Load: 'load',
Popup: 'popup',
Worker: 'worker',
_VideoStarted: '_videostarted',
},

Worker: {
Expand Down
6 changes: 6 additions & 0 deletions src/client/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import * as util from 'util';
import { Size, URLMatch, Headers, LifecycleEvent, WaitForEventOptions, SelectOption, SelectOptionOptions, FilePayload, WaitForFunctionOptions } from './types';
import { evaluationScript, urlMatches } from './clientHelper';
import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } from '../utils/utils';
import { Video } from './video';

type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
width?: string | number,
Expand Down Expand Up @@ -122,6 +123,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
this._channel.on('response', ({ response }) => this.emit(Events.Page.Response, Response.from(response)));
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
this._channel.on('worker', ({ worker }) => this._onWorker(Worker.from(worker)));
this._channel.on('videoStarted', params => this._onVideoStarted(params));

if (this._browserContext._browserName === 'chromium') {
this.coverage = new ChromiumCoverage(this._channel);
Expand Down Expand Up @@ -175,6 +177,10 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
this.emit(Events.Page.Worker, worker);
}

private _onVideoStarted(params: channels.PageVideoStartedEvent): void {
this.emit(Events.Page._VideoStarted, Video.from(params.video));
}

_onClose() {
this._closed = true;
this._browserContext._pages.delete(this);
Expand Down
1 change: 1 addition & 0 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export type LaunchServerOptions = {
password?: string
},
downloadsPath?: string,
_videosPath?: string,
chromiumSandbox?: boolean,
port?: number,
logger?: Logger,
Expand Down
39 changes: 39 additions & 0 deletions src/client/video.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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 channels from '../protocol/channels';
import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';

export class Video extends ChannelOwner<channels.VideoChannel, channels.VideoInitializer> {
private _browser: Browser | undefined;

static from(channel: channels.VideoChannel): Video {
return (channel as any)._object;
}

constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.VideoInitializer) {
super(parent, type, guid, initializer);
this._browser = (parent as BrowserContext)._browser;
}

async path(): Promise<string> {
if (this._browser && this._browser._isRemote)
throw new Error(`Path is not available when using browserType.connect().`);
return (await this._channel.path()).value;
}
}
2 changes: 1 addition & 1 deletion src/dispatchers/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class DispatcherConnection {
onmessage = (message: object) => {};
private _validateParams: (type: string, method: string, params: any) => any;

async sendMessageToClient(guid: string, method: string, params: any, disallowDispatchers?: boolean): Promise<any> {
sendMessageToClient(guid: string, method: string, params: any, disallowDispatchers?: boolean) {
const allowDispatchers = !disallowDispatchers;
this.onmessage({ guid, method, params: this._replaceDispatchersWithGuids(params, allowDispatchers) });
}
Expand Down
4 changes: 3 additions & 1 deletion src/dispatchers/pageDispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { serializeResult, parseArgument } from './jsHandleDispatcher';
import { ElementHandleDispatcher, createHandle } from './elementHandlerDispatcher';
import { FileChooser } from '../server/fileChooser';
import { CRCoverage } from '../server/chromium/crCoverage';
import { VideoDispatcher } from './videoDispatcher';

export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> implements channels.PageChannel {
private _page: Page;
Expand All @@ -48,7 +49,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
page.on(Page.Events.Crash, () => this._dispatchEvent('crash'));
page.on(Page.Events.DOMContentLoaded, () => this._dispatchEvent('domcontentloaded'));
page.on(Page.Events.Dialog, dialog => this._dispatchEvent('dialog', { dialog: new DialogDispatcher(this._scope, dialog) }));
page.on(Page.Events.Download, dialog => this._dispatchEvent('download', { download: new DownloadDispatcher(this._scope, dialog) }));
page.on(Page.Events.Download, download => this._dispatchEvent('download', { download: new DownloadDispatcher(this._scope, download) }));
this._page.on(Page.Events.FileChooser, (fileChooser: FileChooser) => this._dispatchEvent('fileChooser', {
element: new ElementHandleDispatcher(this._scope, fileChooser.element()),
isMultiple: fileChooser.isMultiple()
Expand All @@ -65,6 +66,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageInitializer> i
}));
page.on(Page.Events.RequestFinished, request => this._dispatchEvent('requestFinished', { request: RequestDispatcher.from(scope, request) }));
page.on(Page.Events.Response, response => this._dispatchEvent('response', { response: new ResponseDispatcher(this._scope, response) }));
page.on(Page.Events.VideoStarted, screencast => this._dispatchEvent('videoStarted', { video: new VideoDispatcher(this._scope, screencast) }));
page.on(Page.Events.Worker, worker => this._dispatchEvent('worker', { worker: new WorkerDispatcher(this._scope, worker) }));
}

Expand Down
29 changes: 29 additions & 0 deletions src/dispatchers/videoDispatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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 channels from '../protocol/channels';
import { Video } from '../server/browserContext';
import { Dispatcher, DispatcherScope } from './dispatcher';

export class VideoDispatcher extends Dispatcher<Video, channels.VideoInitializer> implements channels.VideoChannel {
constructor(scope: DispatcherScope, screencast: Video) {
super(scope, screencast, 'Video', {});
}

async path(): Promise<channels.VideoPathResult> {
return { value: await this._object.path() };
}
}
27 changes: 27 additions & 0 deletions src/protocol/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export type BrowserTypeLaunchParams = {
password?: string,
},
downloadsPath?: string,
_videosPath?: string,
firefoxUserPrefs?: any,
chromiumSandbox?: boolean,
slowMo?: number,
Expand All @@ -190,6 +191,7 @@ export type BrowserTypeLaunchOptions = {
password?: string,
},
downloadsPath?: string,
_videosPath?: string,
firefoxUserPrefs?: any,
chromiumSandbox?: boolean,
slowMo?: number,
Expand Down Expand Up @@ -220,6 +222,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
password?: string,
},
downloadsPath?: string,
_videosPath?: string,
chromiumSandbox?: boolean,
slowMo?: number,
noDefaultViewport?: boolean,
Expand Down Expand Up @@ -276,6 +279,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
password?: string,
},
downloadsPath?: string,
_videosPath?: string,
chromiumSandbox?: boolean,
slowMo?: number,
noDefaultViewport?: boolean,
Expand Down Expand Up @@ -363,6 +367,10 @@ export type BrowserNewContextParams = {
hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference',
acceptDownloads?: boolean,
_recordVideos?: {
width: number,
height: number,
},
};
export type BrowserNewContextOptions = {
noDefaultViewport?: boolean,
Expand Down Expand Up @@ -396,6 +404,10 @@ export type BrowserNewContextOptions = {
hasTouch?: boolean,
colorScheme?: 'dark' | 'light' | 'no-preference',
acceptDownloads?: boolean,
_recordVideos?: {
width: number,
height: number,
},
};
export type BrowserNewContextResult = {
context: BrowserContextChannel,
Expand Down Expand Up @@ -645,6 +657,7 @@ export interface PageChannel extends Channel {
on(event: 'requestFinished', callback: (params: PageRequestFinishedEvent) => void): this;
on(event: 'response', callback: (params: PageResponseEvent) => void): this;
on(event: 'route', callback: (params: PageRouteEvent) => void): this;
on(event: 'videoStarted', callback: (params: PageVideoStartedEvent) => void): this;
on(event: 'worker', callback: (params: PageWorkerEvent) => void): this;
setDefaultNavigationTimeoutNoReply(params: PageSetDefaultNavigationTimeoutNoReplyParams): Promise<PageSetDefaultNavigationTimeoutNoReplyResult>;
setDefaultTimeoutNoReply(params: PageSetDefaultTimeoutNoReplyParams): Promise<PageSetDefaultTimeoutNoReplyResult>;
Expand Down Expand Up @@ -727,6 +740,9 @@ export type PageRouteEvent = {
route: RouteChannel,
request: RequestChannel,
};
export type PageVideoStartedEvent = {
video: VideoChannel,
};
export type PageWorkerEvent = {
worker: WorkerChannel,
};
Expand Down Expand Up @@ -2107,6 +2123,17 @@ export type DialogDismissParams = {};
export type DialogDismissOptions = {};
export type DialogDismissResult = void;

// ----------- Video -----------
export type VideoInitializer = {};
export interface VideoChannel extends Channel {
path(params?: VideoPathParams): Promise<VideoPathResult>;
}
export type VideoPathParams = {};
export type VideoPathOptions = {};
export type VideoPathResult = {
value: string,
};

// ----------- Download -----------
export type DownloadInitializer = {
url: string,
Expand Down
22 changes: 22 additions & 0 deletions src/protocol/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ BrowserType:
username: string?
password: string?
downloadsPath: string?
_videosPath: string?
firefoxUserPrefs: json?
chromiumSandbox: boolean?
slowMo: number?
Expand Down Expand Up @@ -250,6 +251,7 @@ BrowserType:
username: string?
password: string?
downloadsPath: string?
_videosPath: string?
chromiumSandbox: boolean?
slowMo: number?
noDefaultViewport: boolean?
Expand Down Expand Up @@ -357,6 +359,11 @@ Browser:
- light
- no-preference
acceptDownloads: boolean?
_recordVideos:
type: object?
properties:
width: number
height: number
returns:
context: BrowserContext

Expand Down Expand Up @@ -892,6 +899,10 @@ Page:
route: Route
request: Request

videoStarted:
parameters:
video: Video

worker:
parameters:
worker: Worker
Expand Down Expand Up @@ -1771,6 +1782,17 @@ Dialog:



Video:
type: interface

commands:

path:
returns:
value: string



Download:
type: interface

Expand Down
Loading

0 comments on commit 66985fc

Please sign in to comment.