mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(screencast): add expreimental public API on context (#3766)
This commit is contained in:
parent
f6aab9e5bd
commit
66985fc5f6
19
docs/api.md
19
docs/api.md
@ -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.
|
||||
@ -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.
|
||||
@ -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)
|
||||
@ -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.
|
||||
@ -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`.
|
||||
@ -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.
|
||||
@ -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`.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
@ -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;
|
||||
|
||||
@ -50,6 +50,7 @@ export const Events = {
|
||||
Load: 'load',
|
||||
Popup: 'popup',
|
||||
Worker: 'worker',
|
||||
_VideoStarted: '_videostarted',
|
||||
},
|
||||
|
||||
Worker: {
|
||||
|
||||
@ -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,
|
||||
@ -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);
|
||||
@ -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);
|
||||
|
||||
@ -82,6 +82,7 @@ export type LaunchServerOptions = {
|
||||
password?: string
|
||||
},
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
chromiumSandbox?: boolean,
|
||||
port?: number,
|
||||
logger?: Logger,
|
||||
|
||||
39
src/client/video.ts
Normal file
39
src/client/video.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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) });
|
||||
}
|
||||
|
||||
@ -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;
|
||||
@ -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()
|
||||
@ -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) }));
|
||||
}
|
||||
|
||||
|
||||
29
src/dispatchers/videoDispatcher.ts
Normal file
29
src/dispatchers/videoDispatcher.ts
Normal file
@ -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() };
|
||||
}
|
||||
}
|
||||
@ -164,6 +164,7 @@ export type BrowserTypeLaunchParams = {
|
||||
password?: string,
|
||||
},
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
firefoxUserPrefs?: any,
|
||||
chromiumSandbox?: boolean,
|
||||
slowMo?: number,
|
||||
@ -190,6 +191,7 @@ export type BrowserTypeLaunchOptions = {
|
||||
password?: string,
|
||||
},
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
firefoxUserPrefs?: any,
|
||||
chromiumSandbox?: boolean,
|
||||
slowMo?: number,
|
||||
@ -220,6 +222,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||
password?: string,
|
||||
},
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
chromiumSandbox?: boolean,
|
||||
slowMo?: number,
|
||||
noDefaultViewport?: boolean,
|
||||
@ -276,6 +279,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||
password?: string,
|
||||
},
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
chromiumSandbox?: boolean,
|
||||
slowMo?: number,
|
||||
noDefaultViewport?: boolean,
|
||||
@ -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,
|
||||
@ -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,
|
||||
@ -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>;
|
||||
@ -727,6 +740,9 @@ export type PageRouteEvent = {
|
||||
route: RouteChannel,
|
||||
request: RequestChannel,
|
||||
};
|
||||
export type PageVideoStartedEvent = {
|
||||
video: VideoChannel,
|
||||
};
|
||||
export type PageWorkerEvent = {
|
||||
worker: WorkerChannel,
|
||||
};
|
||||
@ -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,
|
||||
|
||||
@ -212,6 +212,7 @@ BrowserType:
|
||||
username: string?
|
||||
password: string?
|
||||
downloadsPath: string?
|
||||
_videosPath: string?
|
||||
firefoxUserPrefs: json?
|
||||
chromiumSandbox: boolean?
|
||||
slowMo: number?
|
||||
@ -250,6 +251,7 @@ BrowserType:
|
||||
username: string?
|
||||
password: string?
|
||||
downloadsPath: string?
|
||||
_videosPath: string?
|
||||
chromiumSandbox: boolean?
|
||||
slowMo: number?
|
||||
noDefaultViewport: boolean?
|
||||
@ -357,6 +359,11 @@ Browser:
|
||||
- light
|
||||
- no-preference
|
||||
acceptDownloads: boolean?
|
||||
_recordVideos:
|
||||
type: object?
|
||||
properties:
|
||||
width: number
|
||||
height: number
|
||||
returns:
|
||||
context: BrowserContext
|
||||
|
||||
@ -892,6 +899,10 @@ Page:
|
||||
route: Route
|
||||
request: Request
|
||||
|
||||
videoStarted:
|
||||
parameters:
|
||||
video: Video
|
||||
|
||||
worker:
|
||||
parameters:
|
||||
worker: Worker
|
||||
@ -1771,6 +1782,17 @@ Dialog:
|
||||
|
||||
|
||||
|
||||
Video:
|
||||
type: interface
|
||||
|
||||
commands:
|
||||
|
||||
path:
|
||||
returns:
|
||||
value: string
|
||||
|
||||
|
||||
|
||||
Download:
|
||||
type: interface
|
||||
|
||||
|
||||
@ -118,6 +118,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
password: tOptional(tString),
|
||||
})),
|
||||
downloadsPath: tOptional(tString),
|
||||
_videosPath: tOptional(tString),
|
||||
firefoxUserPrefs: tOptional(tAny),
|
||||
chromiumSandbox: tOptional(tBoolean),
|
||||
slowMo: tOptional(tNumber),
|
||||
@ -145,6 +146,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
password: tOptional(tString),
|
||||
})),
|
||||
downloadsPath: tOptional(tString),
|
||||
_videosPath: tOptional(tString),
|
||||
chromiumSandbox: tOptional(tBoolean),
|
||||
slowMo: tOptional(tNumber),
|
||||
noDefaultViewport: tOptional(tBoolean),
|
||||
@ -212,6 +214,10 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
hasTouch: tOptional(tBoolean),
|
||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||
acceptDownloads: tOptional(tBoolean),
|
||||
_recordVideos: tOptional(tObject({
|
||||
width: tNumber,
|
||||
height: tNumber,
|
||||
})),
|
||||
});
|
||||
scheme.BrowserCrNewBrowserCDPSessionParams = tOptional(tObject({}));
|
||||
scheme.BrowserCrStartTracingParams = tObject({
|
||||
@ -799,6 +805,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
promptText: tOptional(tString),
|
||||
});
|
||||
scheme.DialogDismissParams = tOptional(tObject({}));
|
||||
scheme.VideoPathParams = tOptional(tObject({}));
|
||||
scheme.DownloadPathParams = tOptional(tObject({}));
|
||||
scheme.DownloadSaveAsParams = tObject({
|
||||
path: tString,
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import * as types from './types';
|
||||
import { BrowserContext, Screencast } from './browserContext';
|
||||
import { BrowserContext, Video } from './browserContext';
|
||||
import { Page } from './page';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Download } from './download';
|
||||
@ -32,6 +32,7 @@ export interface BrowserProcess {
|
||||
export type BrowserOptions = types.UIOptions & {
|
||||
name: string,
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
headful?: boolean,
|
||||
persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
|
||||
browserProcess: BrowserProcess,
|
||||
@ -47,7 +48,7 @@ export abstract class Browser extends EventEmitter {
|
||||
private _downloads = new Map<string, Download>();
|
||||
_defaultContext: BrowserContext | null = null;
|
||||
private _startedClosing = false;
|
||||
private readonly _idToScreencast = new Map<string, Screencast>();
|
||||
private readonly _idToVideo = new Map<string, Video>();
|
||||
|
||||
constructor(options: BrowserOptions) {
|
||||
super();
|
||||
@ -86,16 +87,16 @@ export abstract class Browser extends EventEmitter {
|
||||
this._downloads.delete(uuid);
|
||||
}
|
||||
|
||||
_screencastStarted(screencastId: string, file: string, page: Page) {
|
||||
const screencast = new Screencast(file, page);
|
||||
this._idToScreencast.set(screencastId, screencast);
|
||||
page._browserContext.emit(BrowserContext.Events.ScreencastStarted, screencast);
|
||||
_videoStarted(videoId: string, file: string): Video {
|
||||
const video = new Video(file);
|
||||
this._idToVideo.set(videoId, video);
|
||||
return video;
|
||||
}
|
||||
|
||||
_screencastFinished(screencastId: string) {
|
||||
const screencast = this._idToScreencast.get(screencastId);
|
||||
this._idToScreencast.delete(screencastId);
|
||||
screencast!._finishCallback();
|
||||
_videoFinished(videoId: string) {
|
||||
const video = this._idToVideo.get(videoId);
|
||||
this._idToVideo.delete(videoId);
|
||||
video!._finishCallback();
|
||||
}
|
||||
|
||||
_didClose() {
|
||||
|
||||
@ -15,33 +15,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { helper } from './helper';
|
||||
import * as network from './network';
|
||||
import * as path from 'path';
|
||||
import { Page, PageBinding } from './page';
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
import * as frames from './frames';
|
||||
import * as types from './types';
|
||||
import { Download } from './download';
|
||||
import { Browser } from './browser';
|
||||
import { EventEmitter } from 'events';
|
||||
import { TimeoutSettings } from '../utils/timeoutSettings';
|
||||
import { Browser } from './browser';
|
||||
import { Download } from './download';
|
||||
import * as frames from './frames';
|
||||
import { helper } from './helper';
|
||||
import { instrumentingAgents } from './instrumentation';
|
||||
import * as network from './network';
|
||||
import { Page, PageBinding } from './page';
|
||||
import { Progress } from './progress';
|
||||
import { Selectors, serverSelectors } from './selectors';
|
||||
import { instrumentingAgents } from './instrumentation';
|
||||
import * as types from './types';
|
||||
|
||||
export class Screencast {
|
||||
readonly page: Page;
|
||||
export class Video {
|
||||
private readonly _path: string;
|
||||
_finishCallback: () => void = () => {};
|
||||
private readonly _finishedPromise: Promise<void>;
|
||||
constructor(path: string, page: Page) {
|
||||
constructor(path: string) {
|
||||
this._path = path;
|
||||
this.page = page;
|
||||
this._finishedPromise = new Promise(fulfill => this._finishCallback = fulfill);
|
||||
}
|
||||
|
||||
async path(): Promise<string | null> {
|
||||
async path(): Promise<string> {
|
||||
await this._finishedPromise;
|
||||
return this._path;
|
||||
}
|
||||
@ -51,13 +47,11 @@ export abstract class BrowserContext extends EventEmitter {
|
||||
static Events = {
|
||||
Close: 'close',
|
||||
Page: 'page',
|
||||
ScreencastStarted: 'screencaststarted',
|
||||
};
|
||||
|
||||
readonly _timeoutSettings = new TimeoutSettings();
|
||||
readonly _pageBindings = new Map<string, PageBinding>();
|
||||
readonly _options: types.BrowserContextOptions;
|
||||
_screencastOptions: types.ContextScreencastOptions | null = null;
|
||||
_requestInterceptor?: network.RouteHandler;
|
||||
private _isPersistentContext: boolean;
|
||||
private _closedStatus: 'open' | 'closing' | 'closed' = 'open';
|
||||
@ -174,15 +168,6 @@ export abstract class BrowserContext extends EventEmitter {
|
||||
this._timeoutSettings.setDefaultTimeout(timeout);
|
||||
}
|
||||
|
||||
async _enableScreencast(options: types.ContextScreencastOptions) {
|
||||
this._screencastOptions = options;
|
||||
fs.mkdirSync(path.dirname(options.dir), {recursive: true});
|
||||
}
|
||||
|
||||
_disableScreencast() {
|
||||
this._screencastOptions = null;
|
||||
}
|
||||
|
||||
async _loadDefaultContext(progress: Progress) {
|
||||
if (!this.pages().length) {
|
||||
const waitForEvent = helper.waitForEvent(progress, this, BrowserContext.Events.Page);
|
||||
|
||||
@ -41,6 +41,7 @@ const mkdirAsync = util.promisify(fs.mkdir);
|
||||
const mkdtempAsync = util.promisify(fs.mkdtemp);
|
||||
const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
|
||||
const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-');
|
||||
const VIDEOS_FOLDER = path.join(os.tmpdir(), 'playwright_videos-');
|
||||
|
||||
type WebSocketNotPipe = { webSocketRegex: RegExp, stream: 'stdout' | 'stderr' };
|
||||
|
||||
@ -89,7 +90,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||
|
||||
async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, userDataDir?: string): Promise<Browser> {
|
||||
options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined;
|
||||
const { browserProcess, downloadsPath, transport } = await this._launchProcess(progress, options, !!persistent, userDataDir);
|
||||
const { browserProcess, downloadsPath, _videosPath, transport } = await this._launchProcess(progress, options, !!persistent, userDataDir);
|
||||
if ((options as any).__testHookBeforeCreateBrowser)
|
||||
await (options as any).__testHookBeforeCreateBrowser();
|
||||
const browserOptions: BrowserOptions = {
|
||||
@ -98,6 +99,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||
persistent,
|
||||
headful: !options.headless,
|
||||
downloadsPath,
|
||||
_videosPath,
|
||||
browserProcess,
|
||||
proxy: options.proxy,
|
||||
};
|
||||
@ -109,7 +111,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||
return browser;
|
||||
}
|
||||
|
||||
private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, downloadsPath: string, transport: ConnectionTransport }> {
|
||||
private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, downloadsPath: string, _videosPath: string, transport: ConnectionTransport }> {
|
||||
const {
|
||||
ignoreDefaultArgs,
|
||||
ignoreAllDefaultArgs,
|
||||
@ -123,14 +125,19 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||
const env = options.env ? envArrayToObject(options.env) : process.env;
|
||||
|
||||
const tempDirectories = [];
|
||||
let downloadsPath: string;
|
||||
if (options.downloadsPath) {
|
||||
downloadsPath = options.downloadsPath;
|
||||
await mkdirAsync(options.downloadsPath, { recursive: true });
|
||||
} else {
|
||||
downloadsPath = await mkdtempAsync(DOWNLOADS_FOLDER);
|
||||
tempDirectories.push(downloadsPath);
|
||||
}
|
||||
const ensurePath = async (tmpPrefix: string, pathFromOptions?: string) => {
|
||||
let dir;
|
||||
if (pathFromOptions) {
|
||||
dir = pathFromOptions;
|
||||
await mkdirAsync(pathFromOptions, { recursive: true });
|
||||
} else {
|
||||
dir = await mkdtempAsync(tmpPrefix);
|
||||
tempDirectories.push(dir);
|
||||
}
|
||||
return dir;
|
||||
};
|
||||
const downloadsPath = await ensurePath(DOWNLOADS_FOLDER, options.downloadsPath);
|
||||
const _videosPath = await ensurePath(VIDEOS_FOLDER, options._videosPath);
|
||||
|
||||
if (!userDataDir) {
|
||||
userDataDir = await mkdtempAsync(path.join(os.tmpdir(), `playwright_${this._name}dev_profile-`));
|
||||
@ -204,7 +211,7 @@ export abstract class BrowserTypeBase implements BrowserType {
|
||||
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
|
||||
transport = new PipeTransport(stdio[3], stdio[4]);
|
||||
}
|
||||
return { browserProcess, downloadsPath, transport };
|
||||
return { browserProcess, downloadsPath, _videosPath, transport };
|
||||
}
|
||||
|
||||
abstract _defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[];
|
||||
|
||||
@ -460,10 +460,10 @@ class FrameSession {
|
||||
promises.push(this._evaluateOnNewDocument(source));
|
||||
for (const source of this._crPage._page._evaluateOnNewDocumentSources)
|
||||
promises.push(this._evaluateOnNewDocument(source));
|
||||
if (this._crPage._browserContext._screencastOptions) {
|
||||
const contextOptions = this._crPage._browserContext._screencastOptions;
|
||||
if (this._crPage._browserContext._options._recordVideos) {
|
||||
const contextOptions = this._crPage._browserContext._options._recordVideos;
|
||||
const screencastId = createGuid();
|
||||
const outputFile = path.join(contextOptions.dir, screencastId + '.webm');
|
||||
const outputFile = path.join(this._crPage._browserContext._browser._options._videosPath!, screencastId + '.webm');
|
||||
const options = Object.assign({}, contextOptions, {outputFile});
|
||||
promises.push(this._startScreencast(screencastId, options));
|
||||
}
|
||||
@ -764,7 +764,11 @@ class FrameSession {
|
||||
this._screencastState = 'started';
|
||||
this._videoRecorder = videoRecorder;
|
||||
this._screencastId = screencastId;
|
||||
this._crPage._browserContext._browser._screencastStarted(screencastId, options.outputFile, this._page);
|
||||
const video = this._crPage._browserContext._browser._videoStarted(screencastId, options.outputFile);
|
||||
this._crPage.pageOrError().then(pageOrError => {
|
||||
if (pageOrError instanceof Page)
|
||||
pageOrError.emit(Page.Events.VideoStarted, video);
|
||||
}).catch(() => {});
|
||||
} catch (e) {
|
||||
videoRecorder.stop().catch(() => {});
|
||||
throw e;
|
||||
@ -783,7 +787,7 @@ class FrameSession {
|
||||
this._screencastId = null;
|
||||
this._screencastState = 'stopped';
|
||||
await recorder.stop().catch(() => {});
|
||||
this._crPage._browserContext._browser._screencastFinished(screencastId);
|
||||
this._crPage._browserContext._browser._videoFinished(screencastId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,10 +15,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from '../../utils/utils';
|
||||
import { Browser, BrowserOptions } from '../browser';
|
||||
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
|
||||
import { helper, RegisteredListener } from '../helper';
|
||||
import { assert } from '../../utils/utils';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding } from '../page';
|
||||
import { ConnectionTransport } from '../transport';
|
||||
@ -164,7 +164,7 @@ export class FFBrowser extends Browser {
|
||||
}
|
||||
|
||||
_onScreencastFinished(payload: Protocol.Browser.screencastFinishedPayload) {
|
||||
this._screencastFinished(payload.screencastId);
|
||||
this._videoFinished(payload.screencastId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,6 +222,14 @@ export class FFBrowserContext extends BrowserContext {
|
||||
promises.push(this.setOffline(this._options.offline));
|
||||
if (this._options.colorScheme)
|
||||
promises.push(this._browser._connection.send('Browser.setColorScheme', { browserContextId, colorScheme: this._options.colorScheme }));
|
||||
if (this._options._recordVideos) {
|
||||
await this._browser._connection.send('Browser.setScreencastOptions', {
|
||||
...this._options._recordVideos,
|
||||
dir: this._browser._options._videosPath!,
|
||||
browserContextId: this._browserContextId
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
@ -326,11 +334,6 @@ export class FFBrowserContext extends BrowserContext {
|
||||
await this._browser._connection.send('Browser.setRequestInterception', { browserContextId: this._browserContextId, enabled: !!this._requestInterceptor });
|
||||
}
|
||||
|
||||
async _enableScreencast(options: types.ContextScreencastOptions): Promise<void> {
|
||||
await super._enableScreencast(options);
|
||||
await this._browser._connection.send('Browser.setScreencastOptions', Object.assign({}, options, { browserContextId: this._browserContextId}));
|
||||
}
|
||||
|
||||
async _doClose() {
|
||||
assert(this._browserContextId);
|
||||
await this._browser._connection.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId });
|
||||
|
||||
@ -31,7 +31,7 @@ import { RawKeyboardImpl, RawMouseImpl } from './ffInput';
|
||||
import { FFNetworkManager } from './ffNetworkManager';
|
||||
import { Protocol } from './protocol';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import { Screencast } from '../browserContext';
|
||||
import { Video } from '../browserContext';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
@ -50,7 +50,7 @@ export class FFPage implements PageDelegate {
|
||||
private readonly _contextIdToContext: Map<string, dom.FrameExecutionContext>;
|
||||
private _eventListeners: RegisteredListener[];
|
||||
private _workers = new Map<string, { frameId: string, session: FFSession }>();
|
||||
private readonly _idToScreencast = new Map<string, Screencast>();
|
||||
private readonly _idToScreencast = new Map<string, Video>();
|
||||
|
||||
constructor(session: FFSession, browserContext: FFBrowserContext, opener: FFPage | null) {
|
||||
this._session = session;
|
||||
@ -258,7 +258,11 @@ export class FFPage implements PageDelegate {
|
||||
}
|
||||
|
||||
_onScreencastStarted(event: Protocol.Page.screencastStartedPayload) {
|
||||
this._browserContext._browser._screencastStarted(event.screencastId, event.file, this._page);
|
||||
const video = this._browserContext._browser._videoStarted(event.screencastId, event.file);
|
||||
this.pageOrError().then(pageOrError => {
|
||||
if (pageOrError instanceof Page)
|
||||
pageOrError.emit(Page.Events.VideoStarted, video);
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
async exposeBinding(binding: PageBinding) {
|
||||
|
||||
@ -114,6 +114,7 @@ export class Page extends EventEmitter {
|
||||
Load: 'load',
|
||||
Popup: 'popup',
|
||||
Worker: 'worker',
|
||||
VideoStarted: 'videostarted',
|
||||
};
|
||||
|
||||
private _closedState: 'open' | 'closing' | 'closed' = 'open';
|
||||
|
||||
@ -52,19 +52,12 @@ export type ScreenshotOptions = ElementScreenshotOptions & {
|
||||
clip?: Rect,
|
||||
};
|
||||
|
||||
export type ScreencastOptions = {
|
||||
export type PageScreencastOptions = {
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
|
||||
export type PageScreencastOptions = ScreencastOptions & {
|
||||
outputFile: string,
|
||||
};
|
||||
|
||||
export type ContextScreencastOptions = ScreencastOptions & {
|
||||
dir: string,
|
||||
};
|
||||
|
||||
export type URLMatch = string | RegExp | ((url: URL) => boolean);
|
||||
|
||||
export type Credentials = {
|
||||
@ -245,6 +238,10 @@ export type BrowserContextOptions = {
|
||||
hasTouch?: boolean,
|
||||
colorScheme?: ColorScheme,
|
||||
acceptDownloads?: boolean,
|
||||
_recordVideos?: {
|
||||
width: number,
|
||||
height: number
|
||||
}
|
||||
};
|
||||
|
||||
export type EnvArray = { name: string, value: string }[];
|
||||
@ -263,6 +260,7 @@ type LaunchOptionsBase = {
|
||||
devtools?: boolean,
|
||||
proxy?: ProxySettings,
|
||||
downloadsPath?: string,
|
||||
_videosPath?: string,
|
||||
chromiumSandbox?: boolean,
|
||||
slowMo?: number,
|
||||
};
|
||||
|
||||
@ -126,7 +126,7 @@ export class WKBrowser extends Browser {
|
||||
}
|
||||
|
||||
_onScreencastFinished(payload: Protocol.Playwright.screencastFinishedPayload) {
|
||||
this._screencastFinished(payload.screencastId);
|
||||
this._videoFinished(payload.screencastId);
|
||||
}
|
||||
|
||||
_onPageProxyCreated(event: Protocol.Playwright.pageProxyCreatedPayload) {
|
||||
|
||||
@ -15,28 +15,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as jpeg from 'jpeg-js';
|
||||
import * as path from 'path';
|
||||
import * as png from 'pngjs';
|
||||
import { assert, createGuid, debugAssert, headersArrayToObject } from '../../utils/utils';
|
||||
import * as accessibility from '../accessibility';
|
||||
import * as dialog from '../dialog';
|
||||
import * as dom from '../dom';
|
||||
import * as frames from '../frames';
|
||||
import { helper, RegisteredListener } from '../helper';
|
||||
import * as dom from '../dom';
|
||||
import { JSHandle } from '../javascript';
|
||||
import * as network from '../network';
|
||||
import { Page, PageBinding, PageDelegate } from '../page';
|
||||
import * as types from '../types';
|
||||
import { Protocol } from './protocol';
|
||||
import { getAccessibilityTree } from './wkAccessibility';
|
||||
import { WKBrowserContext } from './wkBrowser';
|
||||
import { WKSession } from './wkConnection';
|
||||
import { WKExecutionContext } from './wkExecutionContext';
|
||||
import { RawKeyboardImpl, RawMouseImpl } from './wkInput';
|
||||
import { WKInterceptableRequest } from './wkInterceptableRequest';
|
||||
import { WKWorkers } from './wkWorkers';
|
||||
import { Page, PageDelegate, PageBinding } from '../page';
|
||||
import * as path from 'path';
|
||||
import { Protocol } from './protocol';
|
||||
import * as dialog from '../dialog';
|
||||
import { RawMouseImpl, RawKeyboardImpl } from './wkInput';
|
||||
import * as types from '../types';
|
||||
import * as accessibility from '../accessibility';
|
||||
import { getAccessibilityTree } from './wkAccessibility';
|
||||
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||
import { WKBrowserContext } from './wkBrowser';
|
||||
import * as jpeg from 'jpeg-js';
|
||||
import * as png from 'pngjs';
|
||||
import { JSHandle } from '../javascript';
|
||||
import { assert, createGuid, debugAssert, headersArrayToObject } from '../../utils/utils';
|
||||
import { WKWorkers } from './wkWorkers';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
|
||||
@ -113,9 +113,9 @@ export class WKPage implements PageDelegate {
|
||||
for (const [key, value] of this._browserContext._permissions)
|
||||
this._grantPermissions(key, value);
|
||||
}
|
||||
if (this._browserContext._screencastOptions) {
|
||||
const contextOptions = this._browserContext._screencastOptions;
|
||||
const outputFile = path.join(contextOptions.dir, createGuid() + '.webm');
|
||||
if (this._browserContext._options._recordVideos) {
|
||||
const contextOptions = this._browserContext._options._recordVideos;
|
||||
const outputFile = path.join(this._browserContext._browser._options._videosPath!, createGuid() + '.webm');
|
||||
const options = Object.assign({}, contextOptions, {outputFile});
|
||||
promises.push(this.startScreencast(options));
|
||||
}
|
||||
@ -721,7 +721,11 @@ export class WKPage implements PageDelegate {
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
}) as any;
|
||||
this._browserContext._browser._screencastStarted(screencastId, options.outputFile, this._page);
|
||||
const video = this._browserContext._browser._videoStarted(screencastId, options.outputFile);
|
||||
this.pageOrError().then(pageOrError => {
|
||||
if (pageOrError instanceof Page)
|
||||
pageOrError.emit(Page.Events.VideoStarted, video);
|
||||
}).catch(() => {});
|
||||
} catch (e) {
|
||||
this._recordingVideoFile = null;
|
||||
throw e;
|
||||
|
||||
@ -22,7 +22,6 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { TestServer } from '../utils/testserver';
|
||||
|
||||
|
||||
declare global {
|
||||
interface TestState {
|
||||
videoPlayer: VideoPlayer;
|
||||
@ -244,19 +243,15 @@ describe('screencast', suite => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should sutomatically start/finish when new page is created/closed', test => {
|
||||
it('should automatically start/finish when new page is created/closed', test => {
|
||||
test.flaky(options.FIREFOX, 'Even slow is not slow enough');
|
||||
}, async ({browser, tmpDir, toImpl}) => {
|
||||
// Use server side of the context. All the code below also uses server side APIs.
|
||||
const context = toImpl(await browser.newContext());
|
||||
await context._enableScreencast({width: 320, height: 240, dir: tmpDir});
|
||||
expect(context._screencastOptions).toBeTruthy();
|
||||
|
||||
}, async ({browserType, tmpDir}) => {
|
||||
const browser = await browserType.launch({_videosPath: tmpDir});
|
||||
const context = await browser.newContext({_recordVideos: {width: 320, height: 240}});
|
||||
const [screencast, newPage] = await Promise.all([
|
||||
new Promise(resolve => context.on('screencaststarted', resolve)) as Promise<any>,
|
||||
new Promise<any>(r => context.on('page', page => page.on('_videostarted', r))),
|
||||
context.newPage(),
|
||||
]);
|
||||
expect(screencast.page === newPage).toBe(true);
|
||||
|
||||
const [videoFile] = await Promise.all([
|
||||
screencast.path(),
|
||||
@ -264,50 +259,48 @@ describe('screencast', suite => {
|
||||
]);
|
||||
expect(path.dirname(videoFile)).toBe(tmpDir);
|
||||
await context.close();
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should finish when contex closes', async ({browser, tmpDir, toImpl}) => {
|
||||
// Use server side of the context. All the code below also uses server side APIs.
|
||||
const context = toImpl(await browser.newContext());
|
||||
await context._enableScreencast({width: 320, height: 240, dir: tmpDir});
|
||||
expect(context._screencastOptions).toBeTruthy();
|
||||
it('should finish when contex closes', async ({browserType, tmpDir}) => {
|
||||
const browser = await browserType.launch({_videosPath: tmpDir});
|
||||
const context = await browser.newContext({_recordVideos: {width: 320, height: 240}});
|
||||
|
||||
const [screencast, newPage] = await Promise.all([
|
||||
new Promise(resolve => context.on('screencaststarted', resolve)) as Promise<any>,
|
||||
const [video] = await Promise.all([
|
||||
new Promise<any>(r => context.on('page', page => page.on('_videostarted', r))),
|
||||
context.newPage(),
|
||||
]);
|
||||
expect(screencast.page === newPage).toBe(true);
|
||||
|
||||
const [videoFile] = await Promise.all([
|
||||
screencast.path(),
|
||||
video.path(),
|
||||
context.close(),
|
||||
]);
|
||||
expect(path.dirname(videoFile)).toBe(tmpDir);
|
||||
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should fire start event for popups', async ({browser, tmpDir, server, toImpl}) => {
|
||||
// Use server side of the context. All the code below also uses server side APIs.
|
||||
const context = toImpl(await browser.newContext());
|
||||
await context._enableScreencast({width: 640, height: 480, dir: tmpDir});
|
||||
expect(context._screencastOptions).toBeTruthy();
|
||||
it('should fire start event for popups', async ({browserType, tmpDir, server}) => {
|
||||
const browser = await browserType.launch({_videosPath: tmpDir});
|
||||
const context = await browser.newContext({_recordVideos: {width: 320, height: 240}});
|
||||
|
||||
const [page] = await Promise.all([
|
||||
context.newPage(),
|
||||
new Promise(resolve => context.on('screencaststarted', resolve)) as Promise<any>,
|
||||
new Promise<any>(r => context.on('page', page => page.on('_videostarted', r))),
|
||||
]);
|
||||
await page.mainFrame().goto(server.EMPTY_PAGE);
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const [video, popup] = await Promise.all([
|
||||
new Promise<any>(r => context.on('page', page => page.on('_videostarted', r))),
|
||||
new Promise<Page>(resolve => context.on('page', resolve)),
|
||||
page.evaluate(() => { window.open('about:blank'); })
|
||||
]);
|
||||
const [videoFile] = await Promise.all([
|
||||
video.path(),
|
||||
popup.close()
|
||||
]);
|
||||
expect(path.dirname(videoFile)).toBe(tmpDir);
|
||||
|
||||
const [screencast, popup] = await Promise.all([
|
||||
new Promise(resolve => context.on('screencaststarted', resolve)) as Promise<any>,
|
||||
new Promise(resolve => context.on('page', resolve)) as Promise<any>,
|
||||
page.mainFrame()._evaluateExpression(() => {
|
||||
const win = window.open('about:blank');
|
||||
win.close();
|
||||
}, true)
|
||||
]);
|
||||
expect(screencast.page === popup).toBe(true);
|
||||
expect(path.dirname(await screencast.path())).toBe(tmpDir);
|
||||
await context.close();
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
it('should scale frames down to the requested size ', async ({page, videoPlayer, tmpDir, server, toImpl}) => {
|
||||
@ -350,4 +343,4 @@ describe('screencast', suite => {
|
||||
expectAll(pixels, almostRed);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user