api(videos): introduce a single recordVideo option bag (#4309)

api(videos): introduce a single recordVideo option bag

Currently contains `dir` and `size`, previously known as
`videosPath` and `videoSize`.
This commit is contained in:
Dmitry Gozman 2020-11-02 19:42:05 -08:00 committed by GitHub
parent 46e124a933
commit 1c39689dd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 217 additions and 135 deletions

View File

@ -228,13 +228,18 @@ Indicates that the browser is connected.
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `videosPath` <[string]> Enables video recording for all pages to `videosPath` folder. If not specified, videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `videoSize` <[Object]> Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of the page will be scaled down if necessary to fit specified size.
- `videosPath` <[string]> **NOTE** Use `recordVideo` instead, it takes precedence over `videosPath`. Enables video recording for all pages to `videosPath` directory. If not specified, videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `videoSize` <[Object]> **NOTE** Use `recordVideo` instead, it takes precedence over `videoSize`. Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. 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.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `har.path` file. If not specified, the HAR is not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for the HAR to be saved.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for the HAR to be saved.
- `omitContent` <[boolean]> Optional setting to control whether to omit request content from the HAR. Defaults to `false`.
- `path` <[string]> Path on the filesystem to write the HAR file to.
- `recordVideo` <[Object]> Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `dir` <[string]> Path to the directory to put videos into.
- `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the 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.
@ -282,13 +287,18 @@ Creates a new browser context. It won't share cookies/cache with other browser c
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `videosPath` <[string]> Enables video recording for all pages to `videosPath` folder. If not specified, videos are not recorded. Make sure to await [`page.close`](#pagecloseoptions) for videos to be saved.
- `videoSize` <[Object]> Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of the page will be scaled down if necessary to fit specified size.
- `videosPath` <[string]> **NOTE** Use `recordVideo` instead, it takes precedence over `videosPath`. Enables video recording for all pages to `videosPath` directory. If not specified, videos are not recorded. Make sure to await [`page.close`](#pagecloseoptions) for videos to be saved.
- `videoSize` <[Object]> **NOTE** Use `recordVideo` instead, it takes precedence over `videoSize`. Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. 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.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `har.path` file. If not specified, the HAR is not recorded. Make sure to await [`page.close`](#pagecontext) for the HAR to be saved.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await [`page.close`](#pagecloseoptions) for the HAR to be saved.
- `omitContent` <[boolean]> Optional setting to control whether to omit request content from the HAR. Defaults to `false`.
- `path` <[string]> Path on the filesystem to write the HAR file to.
- `recordVideo` <[Object]> Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await [`page.close`](#pagecloseoptions) for videos to be saved.
- `dir` <[string]> Path to the directory to put videos into.
- `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the 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.
@ -411,7 +421,7 @@ Math.random = () => 42;
```
```js
// In your playwright script, assuming the preload.js file is in same folder.
// In your playwright script, assuming the preload.js file is in same directory.
await browserContext.addInitScript({
path: 'preload.js'
});
@ -1042,7 +1052,7 @@ An example of overriding `Math.random` before the page loads:
// preload.js
Math.random = () => 42;
// In your playwright script, assuming the preload.js file is in same folder
// In your playwright script, assuming the preload.js file is in same directory
const preloadFile = fs.readFileSync('./preload.js', 'utf8');
await page.addInitScript(preloadFile);
```
@ -4418,7 +4428,7 @@ This methods attaches Playwright to an existing browser instance.
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `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.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is deleted when browser is closed.
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `false`.
- `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`.
@ -4460,7 +4470,7 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
- `password` <[string]> Optional password to use if HTTP proxy requires authentication.
- `acceptDownloads` <[boolean]> Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is deleted when browser is closed.
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. Defaults to `true`.
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.
@ -4493,13 +4503,18 @@ 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]> Enables video recording for all pages to `videosPath` folder. If not specified, videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `videoSize` <[Object]> Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of the page will be scaled down if necessary to fit specified size.
- `videosPath` <[string]> **NOTE** Use `recordVideo` instead, it takes precedence over `videosPath`. Enables video recording for all pages to `videosPath` directory. If not specified, videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `videoSize` <[Object]> **NOTE** Use `recordVideo` instead, it takes precedence over `videoSize`. Specifies dimensions of the automatically recorded video. Can only be used if `videosPath` is set. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. 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.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all the pages into `har.path` file. If not specified, HAR is not recorded. Make sure to await [`page.close`](#pagecontext) for HAR to be saved.
- `recordHar` <[Object]> Enables [HAR](http://www.softwareishard.com/blog/har-12-spec) recording for all the pages into `recordHar.path` file. If not specified, HAR is not recorded. Make sure to await [`page.close`](#pagecontext) for HAR to be saved.
- `omitContent` <[boolean]> Optional setting to control whether to omit request content from the HAR. Defaults to false.
- `path` <[string]> Path on the filesystem to write the HAR file to.
- `recordVideo` <[Object]> Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await [`browserContext.close`](#browsercontextclose) for videos to be saved.
- `dir` <[string]> Path to the directory to put videos into.
- `size` <[Object]> Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport`. If `viewport` is not configured explicitly the video size defaults to 1280x720. Actual picture of each page will be scaled down if necessary to fit the 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.
@ -4516,7 +4531,7 @@ Launches browser that uses persistent storage located at `userDataDir` and retur
- `bypass` <[string]> Optional coma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`.
- `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.
- `downloadsPath` <[string]> If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory 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`.
@ -4900,7 +4915,7 @@ If Playwright doesn't find them in the environment, a lowercased variant of thes
- `PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST` - host to specify Chromium downloads
- `PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST` - host to specify Firefox downloads
- `PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST` - host to specify Webkit downloads
- `PLAYWRIGHT_BROWSERS_PATH` - specify a shared folder that playwright will use to download browsers and to look for browsers when launching browser instances.
- `PLAYWRIGHT_BROWSERS_PATH` - specify a shared directory that playwright will use to download browsers and to look for browsers when launching browser instances.
- `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` - set to non-empty value to skip browser downloads altogether.
```sh

View File

@ -16,19 +16,21 @@ Playwright can record videos for all pages in a [browser context](core-concepts.
```js
// With browser.newContext()
const context = await browser.newContext({ videosPath: 'videos/' });
const context = await browser.newContext({ recordVideo: { dir: 'videos/' } });
// Make sure to await close, so that videos are saved.
await context.close();
// With browser.newPage()
const page = await browser.newPage({ videosPath: 'videos/' });
const page = await browser.newPage({ recordVideo: { dir: 'videos/' } });
// Make sure to await close, so that videos are saved.
await page.close();
// [Optional] Specify video size; defaults to viewport size
const context = await browser.newContext({
videosPath: 'videos/',
videoSize: { width: 800, height: 600 }
recordVideo: {
dir: 'videos/',
size: { width: 800, height: 600 },
}
});
```

View File

@ -34,8 +34,7 @@ const fs = require('fs');
try {
const browser = await playwright[browserType].launch({});
const context = await browser.newContext({
videosPath: __dirname,
videoSize: {width: 320, height: 240},
recordVideo: { dir: __dirname, size: {width: 320, height: 240} },
});
await context.newPage();
// Wait fo 1 second to actually record something.

View File

@ -148,9 +148,9 @@ class ConnectedBrowser extends BrowserDispatcher {
}
async newContext(params: channels.BrowserNewContextParams): Promise<{ context: channels.BrowserContextChannel }> {
if (params.videosPath) {
if (params.recordVideo) {
// TODO: we should create a separate temp directory or accept a launchServer parameter.
params.videosPath = this._object._options.downloadsPath;
params.recordVideo.dir = this._object._options.downloadsPath!;
}
const result = await super.newContext(params);
const dispatcher = result.context as BrowserContextDispatcher;

View File

@ -15,13 +15,11 @@
*/
import * as channels from '../protocol/channels';
import { BrowserContext } from './browserContext';
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
import { Page } from './page';
import { ChannelOwner } from './channelOwner';
import { Events } from './events';
import { BrowserContextOptions } from './types';
import { validateHeaders } from './network';
import { headersObjectToArray } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors';
export class Browser extends ChannelOwner<channels.BrowserChannel, channels.BrowserInitializer> {
@ -45,22 +43,14 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
}
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
const logger = options.logger;
return this._wrapApiCall('browser.newContext', async () => {
if (this._isRemote && options._tracePath)
throw new Error(`"_tracePath" is not supported in connected browser`);
if (options.extraHTTPHeaders)
validateHeaders(options.extraHTTPHeaders);
const contextOptions: channels.BrowserNewContextParams = {
...options,
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
};
const contextOptions = validateBrowserContextOptions(options);
const context = BrowserContext.from((await this._channel.newContext(contextOptions)).context);
context._options = contextOptions;
this._contexts.add(context);
context._logger = logger || this._logger;
context._logger = options.logger || this._logger;
return context;
});
}

View File

@ -24,7 +24,7 @@ import { Browser } from './browser';
import { Events } from './events';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { Waiter } from './waiter';
import { URLMatch, Headers, WaitForEventOptions } from './types';
import { URLMatch, Headers, WaitForEventOptions, BrowserContextOptions } from './types';
import { isUnderTest, headersObjectToArray } from '../utils/utils';
import { isSafeCloseError } from '../utils/errors';
@ -238,3 +238,23 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
}
}
export function validateBrowserContextOptions(options: BrowserContextOptions): channels.BrowserNewContextOptions {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
network.validateHeaders(options.extraHTTPHeaders);
const contextOptions: channels.BrowserNewContextParams = {
...options,
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
};
if (!contextOptions.recordVideo && options.videosPath) {
contextOptions.recordVideo = {
dir: options.videosPath,
size: options.videoSize
};
}
return contextOptions;
}

View File

@ -16,7 +16,7 @@
import * as channels from '../protocol/channels';
import { Browser } from './browser';
import { BrowserContext } from './browserContext';
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
import { ChannelOwner } from './channelOwner';
import { LaunchOptions, LaunchServerOptions, ConnectOptions, LaunchPersistentContextOptions } from './types';
import * as WebSocket from 'ws';
@ -28,8 +28,7 @@ import { Events } from './events';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { ChildProcess } from 'child_process';
import { envObjectToArray } from './clientHelper';
import { validateHeaders } from './network';
import { assert, makeWaitForNextTask, headersObjectToArray, mkdirIfNeeded } from '../utils/utils';
import { assert, makeWaitForNextTask, mkdirIfNeeded } from '../utils/utils';
import { SelectorsOwner, sharedSelectors } from './selectors';
import { kBrowserClosedError } from '../utils/errors';
import { Stream } from './stream';
@ -91,27 +90,22 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
}
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
const logger = options.logger;
return this._wrapApiCall('browserType.launchPersistentContext', async () => {
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
if (options.extraHTTPHeaders)
validateHeaders(options.extraHTTPHeaders);
const contextOptions = validateBrowserContextOptions(options);
const persistentOptions: channels.BrowserTypeLaunchPersistentContextParams = {
...options,
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
...contextOptions,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? envObjectToArray(options.env) : undefined,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
userDataDir,
};
const result = await this._channel.launchPersistentContext(persistentOptions);
const context = BrowserContext.from(result.context);
context._options = persistentOptions;
context._logger = logger;
context._options = contextOptions;
context._logger = options.logger;
return context;
}, logger);
}, options.logger);
}
async connect(options: ConnectOptions): Promise<Browser> {
@ -193,7 +187,7 @@ export class RemoteBrowser extends ChannelOwner<channels.RemoteBrowserChannel, c
}
private async _onVideo(context: BrowserContext, stream: Stream, relativePath: string) {
const videoFile = path.join(context._options.videosPath!, relativePath);
const videoFile = path.join(context._options.recordVideo!.dir, relativePath);
await mkdirIfNeeded(videoFile);
stream.stream().pipe(fs.createWriteStream(videoFile));
}

View File

@ -243,7 +243,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
video(): Video | null {
if (this._video)
return this._video;
if (!this._browserContext._options.videosPath)
if (!this._browserContext._options.recordVideo)
return null;
this._video = new Video(this);
// In case of persistent profile, we already have it.

View File

@ -44,6 +44,8 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
viewport?: Size | null,
extraHTTPHeaders?: Headers,
logger?: Logger,
videosPath?: string,
videoSize?: Size,
};
type LaunchOverrides = {

View File

@ -28,7 +28,7 @@ export class Video {
}
_setRelativePath(relativePath: string) {
this._pathCallback!(path.join(this._page.context()._options.videosPath!, relativePath));
this._pathCallback!(path.join(this._page.context()._options.recordVideo!.dir, relativePath));
}
path(): Promise<string> {

View File

@ -265,10 +265,12 @@ export type BrowserTypeLaunchPersistentContextParams = {
acceptDownloads?: boolean,
_traceResourcesPath?: string,
_tracePath?: string,
videosPath?: string,
videoSize?: {
width: number,
height: number,
recordVideo?: {
dir: string,
size?: {
width: number,
height: number,
},
},
};
export type BrowserTypeLaunchPersistentContextOptions = {
@ -328,10 +330,12 @@ export type BrowserTypeLaunchPersistentContextOptions = {
acceptDownloads?: boolean,
_traceResourcesPath?: string,
_tracePath?: string,
videosPath?: string,
videoSize?: {
width: number,
height: number,
recordVideo?: {
dir: string,
size?: {
width: number,
height: number,
},
},
};
export type BrowserTypeLaunchPersistentContextResult = {
@ -389,10 +393,12 @@ export type BrowserNewContextParams = {
acceptDownloads?: boolean,
_traceResourcesPath?: string,
_tracePath?: string,
videosPath?: string,
videoSize?: {
width: number,
height: number,
recordVideo?: {
dir: string,
size?: {
width: number,
height: number,
},
},
recordHar?: {
omitContent?: boolean,
@ -439,10 +445,12 @@ export type BrowserNewContextOptions = {
acceptDownloads?: boolean,
_traceResourcesPath?: string,
_tracePath?: string,
videosPath?: string,
videoSize?: {
width: number,
height: number,
recordVideo?: {
dir: string,
size?: {
width: number,
height: number,
},
},
recordHar?: {
omitContent?: boolean,

View File

@ -316,12 +316,15 @@ BrowserType:
acceptDownloads: boolean?
_traceResourcesPath: string?
_tracePath: string?
videosPath: string?
videoSize:
recordVideo:
type: object?
properties:
width: number
height: number
dir: string
size:
type: object?
properties:
width: number
height: number
returns:
context: BrowserContext
@ -385,12 +388,15 @@ Browser:
acceptDownloads: boolean?
_traceResourcesPath: string?
_tracePath: string?
videosPath: string?
videoSize:
recordVideo:
type: object?
properties:
width: number
height: number
dir: string
size:
type: object?
properties:
width: number
height: number
recordHar:
type: object?
properties:

View File

@ -183,10 +183,12 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
acceptDownloads: tOptional(tBoolean),
_traceResourcesPath: tOptional(tString),
_tracePath: tOptional(tString),
videosPath: tOptional(tString),
videoSize: tOptional(tObject({
width: tNumber,
height: tNumber,
recordVideo: tOptional(tObject({
dir: tString,
size: tOptional(tObject({
width: tNumber,
height: tNumber,
})),
})),
});
scheme.BrowserCloseParams = tOptional(tObject({}));
@ -224,10 +226,12 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
acceptDownloads: tOptional(tBoolean),
_traceResourcesPath: tOptional(tString),
_tracePath: tOptional(tString),
videosPath: tOptional(tString),
videoSize: tOptional(tObject({
width: tNumber,
height: tNumber,
recordVideo: tOptional(tObject({
dir: tString,
size: tOptional(tObject({
width: tNumber,
height: tNumber,
})),
})),
recordHar: tOptional(tObject({
omitContent: tOptional(tBoolean),

View File

@ -42,7 +42,7 @@ export class Video {
constructor(context: BrowserContext, videoId: string, p: string) {
this._videoId = videoId;
this._path = p;
this._relativePath = path.relative(context._options.videosPath!, p);
this._relativePath = path.relative(context._options.recordVideo!.dir, p);
this._context = context;
this._finishedPromise = new Promise(fulfill => this._finishCallback = fulfill);
}
@ -133,8 +133,8 @@ export abstract class BrowserContext extends EventEmitter {
}
async _ensureVideosPath() {
if (this._options.videosPath)
await mkdirIfNeeded(path.join(this._options.videosPath, 'dummy'));
if (this._options.recordVideo)
await mkdirIfNeeded(path.join(this._options.recordVideo.dir, 'dummy'));
}
_browserClosed() {
@ -328,8 +328,6 @@ export function validateBrowserContextOptions(options: types.BrowserContextOptio
options.proxy = normalizeProxySettings(options.proxy);
}
verifyGeolocation(options.geolocation);
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
}
export function verifyGeolocation(geolocation?: types.Geolocation) {

View File

@ -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._isMainFrame() && this._crPage._browserContext._options.videosPath) {
const size = this._crPage._browserContext._options.videoSize || this._crPage._browserContext._options.viewport || { width: 1280, height: 720 };
if (this._isMainFrame() && this._crPage._browserContext._options.recordVideo) {
const size = this._crPage._browserContext._options.recordVideo.size || this._crPage._browserContext._options.viewport || { width: 1280, height: 720 };
const screencastId = createGuid();
const outputFile = path.join(this._crPage._browserContext._options.videosPath, screencastId + '.webm');
const outputFile = path.join(this._crPage._browserContext._options.recordVideo.dir, screencastId + '.webm');
promises.push(this._crPage._browserContext._ensureVideosPath().then(() => {
return this._startScreencast(screencastId, {
...size,

View File

@ -202,12 +202,12 @@ 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.videosPath) {
const size = this._options.videoSize || this._options.viewport || { width: 1280, height: 720 };
if (this._options.recordVideo) {
const size = this._options.recordVideo.size || this._options.viewport || { width: 1280, height: 720 };
promises.push(this._ensureVideosPath().then(() => {
return this._browser._connection.send('Browser.setScreencastOptions', {
...size,
dir: this._options.videosPath!,
dir: this._options.recordVideo!.dir,
browserContextId: this._browserContextId
});
}));

View File

@ -238,8 +238,10 @@ export type BrowserContextOptions = {
hasTouch?: boolean,
colorScheme?: ColorScheme,
acceptDownloads?: boolean,
videosPath?: string,
videoSize?: Size,
recordVideo?: {
dir: string,
size?: Size,
},
recordHar?: {
omitContent?: boolean,
path: string

View File

@ -115,9 +115,9 @@ export class WKPage implements PageDelegate {
for (const [key, value] of this._browserContext._permissions)
this._grantPermissions(key, value);
}
if (this._browserContext._options.videosPath) {
const size = this._browserContext._options.videoSize || this._browserContext._options.viewport || { width: 1280, height: 720 };
const outputFile = path.join(this._browserContext._options.videosPath, createGuid() + '.webm');
if (this._browserContext._options.recordVideo) {
const size = this._browserContext._options.recordVideo.size || this._browserContext._options.viewport || { width: 1280, height: 720 };
const outputFile = path.join(this._browserContext._options.recordVideo.dir, createGuid() + '.webm');
promises.push(this._browserContext._ensureVideosPath().then(() => {
return this.startScreencast({
...size,

View File

@ -240,8 +240,7 @@ describe('connect', (suite, { wire }) => {
const remote = await browserType.connect({ wsEndpoint: remoteServer.wsEndpoint() });
const videosPath = testInfo.outputPath();
const context = await remote.newContext({
videosPath,
videoSize: { width: 320, height: 240 },
recordVideo: { dir: videosPath, size: { width: 320, height: 240 } },
});
const page = await context.newPage();
await page.evaluate(() => document.body.style.backgroundColor = 'red');

View File

@ -146,7 +146,7 @@ fixtures.isLinux.init(async ({ platform }, run) => {
fixtures.contextOptions.init(async ({ video, testInfo }, run) => {
if (video) {
await run({
videosPath: testInfo.outputPath(''),
recordVideo: { dir: testInfo.outputPath('') },
});
} else {
await run({});

View File

@ -143,7 +143,7 @@ describe('screencast', suite => {
expect(error.message).toContain('"videoSize" option requires "videosPath" to be specified');
});
it('should capture static page', async ({browser, testInfo}) => {
it('should work with old options', async ({browser, testInfo}) => {
const videosPath = testInfo.outputPath('');
const size = { width: 450, height: 240 };
const context = await browser.newContext({
@ -164,6 +164,35 @@ describe('screencast', suite => {
expect(videoPlayer.videoWidth).toBe(450);
expect(videoPlayer.videoHeight).toBe(240);
});
it('should throw without recordVideo.dir', async ({ browser }) => {
const error = await browser.newContext({ recordVideo: {} as any }).catch(e => e);
expect(error.message).toContain('recordVideo.dir: expected string, got undefined');
});
it('should capture static page', async ({browser, testInfo}) => {
const size = { width: 450, height: 240 };
const context = await browser.newContext({
recordVideo: {
dir: testInfo.outputPath(''),
size
},
viewport: size,
});
const page = await context.newPage();
await page.evaluate(() => document.body.style.backgroundColor = 'red');
await new Promise(r => setTimeout(r, 1000));
await context.close();
const videoFile = await page.video().path();
const videoPlayer = new VideoPlayer(videoFile);
const duration = videoPlayer.duration;
expect(duration).toBeGreaterThan(0);
expect(videoPlayer.videoWidth).toBe(450);
expect(videoPlayer.videoHeight).toBe(240);
{
const pixels = videoPlayer.seekLastFrame().data;
@ -179,9 +208,11 @@ describe('screencast', suite => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: videosPath,
size
},
viewport: size,
videoSize: size
});
const page = await context.newPage();
await page.evaluate(() => document.body.style.backgroundColor = 'red');
@ -195,9 +226,11 @@ describe('screencast', suite => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: videosPath,
size
},
viewport: size,
videoSize: size
});
const page = await context.newPage();
const path = await page.video()!.path();
@ -210,9 +243,11 @@ describe('screencast', suite => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: videosPath,
size
},
viewport: size,
videoSize: size
});
const page = await context.newPage();
const [popup] = await Promise.all([
@ -226,10 +261,11 @@ describe('screencast', suite => {
});
it('should capture navigation', async ({browser, server, testInfo}) => {
const videosPath = testInfo.outputPath('');
const context = await browser.newContext({
videosPath,
videoSize: { width: 1280, height: 720 }
recordVideo: {
dir: testInfo.outputPath(''),
size: { width: 1280, height: 720 }
},
});
const page = await context.newPage();
@ -255,15 +291,16 @@ describe('screencast', suite => {
}
});
it('should capture css transformation', (test, { browserName, platform, headful }) => {
it('should capture css transformation', (test, { headful }) => {
test.fixme(headful, 'Fails on headful');
}, async ({browser, server, testInfo}) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
// Set viewport equal to screencast frame size to avoid scaling.
const context = await browser.newContext({
videosPath,
videoSize: size,
recordVideo: {
dir: testInfo.outputPath(''),
size,
},
viewport: size,
});
const page = await context.newPage();
@ -286,8 +323,10 @@ describe('screencast', suite => {
it('should work for popups', async ({browser, testInfo, server}) => {
const videosPath = testInfo.outputPath('');
const context = await browser.newContext({
videosPath,
videoSize: { width: 320, height: 240 }
recordVideo: {
dir: videosPath,
size: { width: 320, height: 240 }
},
});
const page = await context.newPage();
@ -306,12 +345,13 @@ describe('screencast', suite => {
it('should scale frames down to the requested size ', (test, parameters) => {
test.fixme(parameters.headful, 'Fails on headful');
}, async ({browser, testInfo, server}) => {
const videosPath = testInfo.outputPath('');
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: testInfo.outputPath(''),
// Set size to 1/2 of the viewport.
size: { width: 320, height: 240 },
},
viewport: {width: 640, height: 480},
// Set size to 1/2 of the viewport.
videoSize: { width: 320, height: 240 },
});
const page = await context.newPage();
@ -351,10 +391,11 @@ describe('screencast', suite => {
});
it('should use viewport as default size', async ({browser, testInfo}) => {
const videosPath = testInfo.outputPath('');
const size = {width: 800, height: 600};
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: testInfo.outputPath(''),
},
viewport: size,
});
@ -364,14 +405,15 @@ describe('screencast', suite => {
const videoFile = await page.video().path();
const videoPlayer = new VideoPlayer(videoFile);
expect(await videoPlayer.videoWidth).toBe(size.width);
expect(await videoPlayer.videoHeight).toBe(size.height);
expect(videoPlayer.videoWidth).toBe(size.width);
expect(videoPlayer.videoHeight).toBe(size.height);
});
it('should be 1280x720 by default', async ({browser, testInfo}) => {
const videosPath = testInfo.outputPath('');
const context = await browser.newContext({
videosPath,
recordVideo: {
dir: testInfo.outputPath(''),
},
});
const page = await context.newPage();
@ -380,17 +422,18 @@ describe('screencast', suite => {
const videoFile = await page.video().path();
const videoPlayer = new VideoPlayer(videoFile);
expect(await videoPlayer.videoWidth).toBe(1280);
expect(await videoPlayer.videoHeight).toBe(720);
expect(videoPlayer.videoWidth).toBe(1280);
expect(videoPlayer.videoHeight).toBe(720);
});
it('should capture static page in persistent context', async ({launchPersistent, testInfo}) => {
const videosPath = testInfo.outputPath('');
const size = { width: 320, height: 240 };
const { context, page } = await launchPersistent({
videosPath,
recordVideo: {
dir: testInfo.outputPath(''),
size,
},
viewport: size,
videoSize: size
});
await page.evaluate(() => document.body.style.backgroundColor = 'red');