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:
parent
46e124a933
commit
1c39689dd6
45
docs/api.md
45
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
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'vie
|
|||
viewport?: Size | null,
|
||||
extraHTTPHeaders?: Headers,
|
||||
logger?: Logger,
|
||||
videosPath?: string,
|
||||
videoSize?: Size,
|
||||
};
|
||||
|
||||
type LaunchOverrides = {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in New Issue