diff --git a/docs/api.md b/docs/api.md index 9d5df84b99..a0af7c6fff 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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 diff --git a/docs/verification.md b/docs/verification.md index 272c23e66d..fe379b7e1f 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -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 }, + } }); ``` diff --git a/packages/installation-tests/screencast.js b/packages/installation-tests/screencast.js index ef99798d0b..2c6dd77a6f 100644 --- a/packages/installation-tests/screencast.js +++ b/packages/installation-tests/screencast.js @@ -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. diff --git a/src/browserServerImpl.ts b/src/browserServerImpl.ts index 9bd913f1dd..fd5307bb10 100644 --- a/src/browserServerImpl.ts +++ b/src/browserServerImpl.ts @@ -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; diff --git a/src/client/browser.ts b/src/client/browser.ts index f34705889e..bd1ba31b9d 100644 --- a/src/client/browser.ts +++ b/src/client/browser.ts @@ -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 { @@ -45,22 +43,14 @@ export class Browser extends ChannelOwner { - 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; }); } diff --git a/src/client/browserContext.ts b/src/client/browserContext.ts index aa8ecad23c..a6ddd15e57 100644 --- a/src/client/browserContext.ts +++ b/src/client/browserContext.ts @@ -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 { - 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 { @@ -193,7 +187,7 @@ export class RemoteBrowser extends ChannelOwner { diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index a644a1cac9..9b948044d9 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -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, diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index 7495918b65..318b79e053 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -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: diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index ce3ed1f704..5b0d3236ff 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -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), diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index b14bf5c08a..e0368260b5 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -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) { diff --git a/src/server/chromium/crPage.ts b/src/server/chromium/crPage.ts index 428ced5340..a4537d3f21 100644 --- a/src/server/chromium/crPage.ts +++ b/src/server/chromium/crPage.ts @@ -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, diff --git a/src/server/firefox/ffBrowser.ts b/src/server/firefox/ffBrowser.ts index 167838a4e7..db00b03a38 100644 --- a/src/server/firefox/ffBrowser.ts +++ b/src/server/firefox/ffBrowser.ts @@ -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 }); })); diff --git a/src/server/types.ts b/src/server/types.ts index fafb3b1723..ed4f9b7d47 100644 --- a/src/server/types.ts +++ b/src/server/types.ts @@ -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 diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index d508dadf08..83bf774890 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -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, diff --git a/test/browsertype-connect.spec.ts b/test/browsertype-connect.spec.ts index 2af2fae252..9a07456a53 100644 --- a/test/browsertype-connect.spec.ts +++ b/test/browsertype-connect.spec.ts @@ -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'); diff --git a/test/playwright.fixtures.ts b/test/playwright.fixtures.ts index 1138e226b6..6e4f103741 100644 --- a/test/playwright.fixtures.ts +++ b/test/playwright.fixtures.ts @@ -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({}); diff --git a/test/screencast.spec.ts b/test/screencast.spec.ts index 947fac4816..8e9c992f7d 100644 --- a/test/screencast.spec.ts +++ b/test/screencast.spec.ts @@ -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');