mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
api(trace): introduce artifacts options (#3914)
api(trace): introduce artifacts options This introduces launch({ artifactsPath }) and newContext({ relativeArtifactsPath, recordTrace }) options. - artifactsPath option controls the directory where all artifacts go. If not passed, artifacts are not collected. - relativeArtifactsPath can be used to put context-specific artifacts into a subfolder. If not passed, shared artifactsPath is used. - recordTrace controls trace recording. We also expose trace types under playwright/types/trace.d.ts. In the follow up: - videos will be put into artifactsPath; - downloads will be put into artifactsPath, or keep using existing downloadsPath when artifactsPath is not specified.
This commit is contained in:
parent
bff9fb21ec
commit
0ade6af689
@ -220,10 +220,12 @@ Indicates that the browser is connected.
|
|||||||
- `password` <[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`'.
|
- `colorScheme` <"light"|"dark"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See [page.emulateMedia(options)](#pageemulatemediaoptions) for more details. Defaults to '`light`'.
|
||||||
- `logger` <[Logger]> Logger sink for Playwright logging.
|
- `logger` <[Logger]> Logger sink for Playwright logging.
|
||||||
|
- `relativeArtifactsPath` <[string]> Specifies a folder for artifacts like downloads, videos and traces, relative to `artifactsPath` from [`browserType.launch`](#browsertypelaunchoptions). Defaults to `.`.
|
||||||
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for new pages.
|
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for new pages.
|
||||||
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `width` <[number]> Video frame width.
|
||||||
- `height` <[number]> Video frame height.
|
- `height` <[number]> Video frame height.
|
||||||
|
- `recordTrace` <[boolean]> Enables trace recording to the `relativeArtifactsPath` folder.
|
||||||
- returns: <[Promise]<[BrowserContext]>>
|
- returns: <[Promise]<[BrowserContext]>>
|
||||||
|
|
||||||
Creates a new browser context. It won't share cookies/cache with other browser contexts.
|
Creates a new browser context. It won't share cookies/cache with other browser contexts.
|
||||||
@ -266,10 +268,12 @@ Creates a new browser context. It won't share cookies/cache with other browser c
|
|||||||
- `password` <[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`'.
|
- `colorScheme` <"light"|"dark"|"no-preference"> Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See [page.emulateMedia(options)](#pageemulatemediaoptions) for more details. Defaults to '`light`'.
|
||||||
- `logger` <[Logger]> Logger sink for Playwright logging.
|
- `logger` <[Logger]> Logger sink for Playwright logging.
|
||||||
|
- `relativeArtifactsPath` <[string]> Specifies a folder for artifacts like downloads, videos and traces, relative to `artifactsPath` from [`browserType.launch`](#browsertypelaunchoptions). Defaults to `.`.
|
||||||
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for the new page.
|
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for the new page.
|
||||||
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `width` <[number]> Video frame width.
|
||||||
- `height` <[number]> Video frame height.
|
- `height` <[number]> Video frame height.
|
||||||
|
- `recordTrace` <[boolean]> Enables trace recording to the `relativeArtifactsPath` folder.
|
||||||
- returns: <[Promise]<[Page]>>
|
- returns: <[Promise]<[Page]>>
|
||||||
|
|
||||||
Creates a new page in a new browser context. Closing this page will close the context as well.
|
Creates a new page in a new browser context. Closing this page will close the context as well.
|
||||||
@ -4200,6 +4204,7 @@ This methods attaches Playwright to an existing browser instance.
|
|||||||
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
|
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
|
||||||
- `password` <[string]> Optional password 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 folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
|
- `artifactsPath` <[string]> Specifies a folder for various artifacts like downloads, videos and traces. If not specified, artifacts are not collected.
|
||||||
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
|
- `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).
|
- `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).
|
||||||
@ -4243,6 +4248,7 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
|
|||||||
- `password` <[string]> Optional password 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.
|
- `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 folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
|
- `artifactsPath` <[string]> Specifies a folder for various artifacts like downloads, videos and traces. If not specified, artifacts are not collected.
|
||||||
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
|
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
|
||||||
- `handleSIGINT` <[boolean]> Close the browser process on Ctrl-C. 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`.
|
- `handleSIGTERM` <[boolean]> Close the browser process on SIGTERM. Defaults to `true`.
|
||||||
@ -4275,11 +4281,13 @@ const browser = await chromium.launch({ // Or 'firefox' or 'webkit'.
|
|||||||
- `username` <[string]>
|
- `username` <[string]>
|
||||||
- `password` <[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`'.
|
- `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`'.
|
||||||
|
- `relativeArtifactsPath` <[string]> Specifies a folder for artifacts like downloads, videos and traces, relative to `artifactsPath`. Defaults to `.`.
|
||||||
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for new pages.
|
- `_recordVideos` <[boolean]> **experimental** Enables automatic video recording for new pages.
|
||||||
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `_videoSize` <[Object]> **experimental** Specifies dimensions of the automatically recorded video. Can only be used if `_recordVideos` is true. 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.
|
- `width` <[number]> Video frame width.
|
||||||
- `height` <[number]> Video frame height.
|
- `height` <[number]> Video frame height.
|
||||||
|
- `recordTrace` <[boolean]> Enables trace recording to the `relativeArtifactsPath` folder.
|
||||||
- returns: <[Promise]<[BrowserContext]>> Promise that resolves to the persistent browser context instance.
|
- 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.
|
Launches browser that uses persistent storage located at `userDataDir` and returns the only context. Closing this context will automatically close the browser.
|
||||||
@ -4297,6 +4305,7 @@ Launches browser that uses persistent storage located at `userDataDir` and retur
|
|||||||
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
|
- `username` <[string]> Optional username to use if HTTP proxy requires authentication.
|
||||||
- `password` <[string]> Optional password 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 folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
|
- `artifactsPath` <[string]> Specifies a folder for various artifacts like downloads, videos and traces. If not specified, artifacts are not collected.
|
||||||
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
- `_videosPath` <[string]> **experimental** If specified, recorded videos are saved into this folder. Otherwise, temporary folder is created and is deleted when browser is closed.
|
||||||
- `chromiumSandbox` <[boolean]> Enable Chromium sandboxing. Defaults to `true`.
|
- `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).
|
- `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).
|
||||||
|
@ -82,6 +82,7 @@ export type LaunchServerOptions = {
|
|||||||
password?: string
|
password?: string
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
artifactsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
port?: number,
|
port?: number,
|
||||||
|
@ -21,9 +21,11 @@ import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher';
|
|||||||
import { Connection } from './client/connection';
|
import { Connection } from './client/connection';
|
||||||
import { BrowserServerLauncherImpl } from './browserServerImpl';
|
import { BrowserServerLauncherImpl } from './browserServerImpl';
|
||||||
import { installDebugController } from './debug/debugController';
|
import { installDebugController } from './debug/debugController';
|
||||||
|
import { installTracer } from './trace/tracer';
|
||||||
|
|
||||||
export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI {
|
export function setupInProcess(playwright: PlaywrightImpl): PlaywrightAPI {
|
||||||
installDebugController();
|
installDebugController();
|
||||||
|
installTracer();
|
||||||
|
|
||||||
const clientConnection = new Connection();
|
const clientConnection = new Connection();
|
||||||
const dispatcherConnection = new DispatcherConnection();
|
const dispatcherConnection = new DispatcherConnection();
|
||||||
|
@ -168,6 +168,7 @@ export type BrowserTypeLaunchParams = {
|
|||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
artifactsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
firefoxUserPrefs?: any,
|
firefoxUserPrefs?: any,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
@ -195,6 +196,7 @@ export type BrowserTypeLaunchOptions = {
|
|||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
artifactsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
firefoxUserPrefs?: any,
|
firefoxUserPrefs?: any,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
@ -226,6 +228,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
|||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
artifactsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
@ -260,6 +263,8 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
|||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'light' | 'dark' | 'no-preference',
|
colorScheme?: 'light' | 'dark' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
|
relativeArtifactsPath?: string,
|
||||||
|
recordTrace?: boolean,
|
||||||
};
|
};
|
||||||
export type BrowserTypeLaunchPersistentContextOptions = {
|
export type BrowserTypeLaunchPersistentContextOptions = {
|
||||||
executablePath?: string,
|
executablePath?: string,
|
||||||
@ -283,6 +288,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
|||||||
password?: string,
|
password?: string,
|
||||||
},
|
},
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
|
artifactsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
slowMo?: number,
|
slowMo?: number,
|
||||||
@ -317,6 +323,8 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
|||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'light' | 'dark' | 'no-preference',
|
colorScheme?: 'light' | 'dark' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
|
relativeArtifactsPath?: string,
|
||||||
|
recordTrace?: boolean,
|
||||||
};
|
};
|
||||||
export type BrowserTypeLaunchPersistentContextResult = {
|
export type BrowserTypeLaunchPersistentContextResult = {
|
||||||
context: BrowserContextChannel,
|
context: BrowserContextChannel,
|
||||||
@ -371,6 +379,8 @@ export type BrowserNewContextParams = {
|
|||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
|
relativeArtifactsPath?: string,
|
||||||
|
recordTrace?: boolean,
|
||||||
_recordVideos?: boolean,
|
_recordVideos?: boolean,
|
||||||
_videoSize?: {
|
_videoSize?: {
|
||||||
width: number,
|
width: number,
|
||||||
@ -409,6 +419,8 @@ export type BrowserNewContextOptions = {
|
|||||||
hasTouch?: boolean,
|
hasTouch?: boolean,
|
||||||
colorScheme?: 'dark' | 'light' | 'no-preference',
|
colorScheme?: 'dark' | 'light' | 'no-preference',
|
||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
|
relativeArtifactsPath?: string,
|
||||||
|
recordTrace?: boolean,
|
||||||
_recordVideos?: boolean,
|
_recordVideos?: boolean,
|
||||||
_videoSize?: {
|
_videoSize?: {
|
||||||
width: number,
|
width: number,
|
||||||
|
@ -220,6 +220,7 @@ BrowserType:
|
|||||||
username: string?
|
username: string?
|
||||||
password: string?
|
password: string?
|
||||||
downloadsPath: string?
|
downloadsPath: string?
|
||||||
|
artifactsPath: string?
|
||||||
_videosPath: string?
|
_videosPath: string?
|
||||||
firefoxUserPrefs: json?
|
firefoxUserPrefs: json?
|
||||||
chromiumSandbox: boolean?
|
chromiumSandbox: boolean?
|
||||||
@ -259,6 +260,7 @@ BrowserType:
|
|||||||
username: string?
|
username: string?
|
||||||
password: string?
|
password: string?
|
||||||
downloadsPath: string?
|
downloadsPath: string?
|
||||||
|
artifactsPath: string?
|
||||||
_videosPath: string?
|
_videosPath: string?
|
||||||
chromiumSandbox: boolean?
|
chromiumSandbox: boolean?
|
||||||
slowMo: number?
|
slowMo: number?
|
||||||
@ -306,6 +308,8 @@ BrowserType:
|
|||||||
- dark
|
- dark
|
||||||
- no-preference
|
- no-preference
|
||||||
acceptDownloads: boolean?
|
acceptDownloads: boolean?
|
||||||
|
relativeArtifactsPath: string?
|
||||||
|
recordTrace: boolean?
|
||||||
returns:
|
returns:
|
||||||
context: BrowserContext
|
context: BrowserContext
|
||||||
|
|
||||||
@ -367,6 +371,8 @@ Browser:
|
|||||||
- light
|
- light
|
||||||
- no-preference
|
- no-preference
|
||||||
acceptDownloads: boolean?
|
acceptDownloads: boolean?
|
||||||
|
relativeArtifactsPath: string?
|
||||||
|
recordTrace: boolean?
|
||||||
_recordVideos: boolean?
|
_recordVideos: boolean?
|
||||||
_videoSize:
|
_videoSize:
|
||||||
type: object?
|
type: object?
|
||||||
|
@ -121,6 +121,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
password: tOptional(tString),
|
password: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
downloadsPath: tOptional(tString),
|
downloadsPath: tOptional(tString),
|
||||||
|
artifactsPath: tOptional(tString),
|
||||||
_videosPath: tOptional(tString),
|
_videosPath: tOptional(tString),
|
||||||
firefoxUserPrefs: tOptional(tAny),
|
firefoxUserPrefs: tOptional(tAny),
|
||||||
chromiumSandbox: tOptional(tBoolean),
|
chromiumSandbox: tOptional(tBoolean),
|
||||||
@ -149,6 +150,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
password: tOptional(tString),
|
password: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
downloadsPath: tOptional(tString),
|
downloadsPath: tOptional(tString),
|
||||||
|
artifactsPath: tOptional(tString),
|
||||||
_videosPath: tOptional(tString),
|
_videosPath: tOptional(tString),
|
||||||
chromiumSandbox: tOptional(tBoolean),
|
chromiumSandbox: tOptional(tBoolean),
|
||||||
slowMo: tOptional(tNumber),
|
slowMo: tOptional(tNumber),
|
||||||
@ -183,6 +185,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
hasTouch: tOptional(tBoolean),
|
hasTouch: tOptional(tBoolean),
|
||||||
colorScheme: tOptional(tEnum(['light', 'dark', 'no-preference'])),
|
colorScheme: tOptional(tEnum(['light', 'dark', 'no-preference'])),
|
||||||
acceptDownloads: tOptional(tBoolean),
|
acceptDownloads: tOptional(tBoolean),
|
||||||
|
relativeArtifactsPath: tOptional(tString),
|
||||||
|
recordTrace: tOptional(tBoolean),
|
||||||
});
|
});
|
||||||
scheme.BrowserCloseParams = tOptional(tObject({}));
|
scheme.BrowserCloseParams = tOptional(tObject({}));
|
||||||
scheme.BrowserNewContextParams = tObject({
|
scheme.BrowserNewContextParams = tObject({
|
||||||
@ -217,6 +221,8 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
|||||||
hasTouch: tOptional(tBoolean),
|
hasTouch: tOptional(tBoolean),
|
||||||
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
colorScheme: tOptional(tEnum(['dark', 'light', 'no-preference'])),
|
||||||
acceptDownloads: tOptional(tBoolean),
|
acceptDownloads: tOptional(tBoolean),
|
||||||
|
relativeArtifactsPath: tOptional(tString),
|
||||||
|
recordTrace: tOptional(tBoolean),
|
||||||
_recordVideos: tOptional(tBoolean),
|
_recordVideos: tOptional(tBoolean),
|
||||||
_videoSize: tOptional(tObject({
|
_videoSize: tOptional(tObject({
|
||||||
width: tNumber,
|
width: tNumber,
|
||||||
|
@ -21,8 +21,10 @@ import { PlaywrightDispatcher } from './dispatchers/playwrightDispatcher';
|
|||||||
import { Electron } from './server/electron/electron';
|
import { Electron } from './server/electron/electron';
|
||||||
import { gracefullyCloseAll } from './server/processLauncher';
|
import { gracefullyCloseAll } from './server/processLauncher';
|
||||||
import { installDebugController } from './debug/debugController';
|
import { installDebugController } from './debug/debugController';
|
||||||
|
import { installTracer } from './trace/tracer';
|
||||||
|
|
||||||
installDebugController();
|
installDebugController();
|
||||||
|
installTracer();
|
||||||
|
|
||||||
const dispatcherConnection = new DispatcherConnection();
|
const dispatcherConnection = new DispatcherConnection();
|
||||||
const transport = new Transport(process.stdout, process.stdin);
|
const transport = new Transport(process.stdout, process.stdin);
|
||||||
|
@ -32,6 +32,7 @@ export interface BrowserProcess {
|
|||||||
|
|
||||||
export type BrowserOptions = types.UIOptions & {
|
export type BrowserOptions = types.UIOptions & {
|
||||||
name: string,
|
name: string,
|
||||||
|
artifactsPath?: string,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
headful?: boolean,
|
headful?: boolean,
|
||||||
|
@ -27,6 +27,7 @@ import { Page, PageBinding } from './page';
|
|||||||
import { Progress, ProgressController, ProgressResult } from './progress';
|
import { Progress, ProgressController, ProgressResult } from './progress';
|
||||||
import { Selectors, serverSelectors } from './selectors';
|
import { Selectors, serverSelectors } from './selectors';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
export class Video {
|
export class Video {
|
||||||
private readonly _path: string;
|
private readonly _path: string;
|
||||||
@ -92,6 +93,7 @@ export abstract class BrowserContext extends EventEmitter {
|
|||||||
readonly _browserContextId: string | undefined;
|
readonly _browserContextId: string | undefined;
|
||||||
private _selectors?: Selectors;
|
private _selectors?: Selectors;
|
||||||
readonly _actionListeners = new Set<ActionListener>();
|
readonly _actionListeners = new Set<ActionListener>();
|
||||||
|
readonly _artifactsPath?: string;
|
||||||
|
|
||||||
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
|
||||||
super();
|
super();
|
||||||
@ -99,6 +101,11 @@ export abstract class BrowserContext extends EventEmitter {
|
|||||||
this._options = options;
|
this._options = options;
|
||||||
this._browserContextId = browserContextId;
|
this._browserContextId = browserContextId;
|
||||||
this._isPersistentContext = !browserContextId;
|
this._isPersistentContext = !browserContextId;
|
||||||
|
if (browser._options.artifactsPath) {
|
||||||
|
this._artifactsPath = browser._options.artifactsPath;
|
||||||
|
if (options.relativeArtifactsPath)
|
||||||
|
this._artifactsPath = path.join(this._artifactsPath, options.relativeArtifactsPath);
|
||||||
|
}
|
||||||
this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
|
this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +96,7 @@ export abstract class BrowserType {
|
|||||||
slowMo: options.slowMo,
|
slowMo: options.slowMo,
|
||||||
persistent,
|
persistent,
|
||||||
headful: !options.headless,
|
headful: !options.headless,
|
||||||
|
artifactsPath: options.artifactsPath,
|
||||||
downloadsPath,
|
downloadsPath,
|
||||||
_videosPath,
|
_videosPath,
|
||||||
browserProcess,
|
browserProcess,
|
||||||
@ -134,6 +135,7 @@ export abstract class BrowserType {
|
|||||||
}
|
}
|
||||||
return dir;
|
return dir;
|
||||||
};
|
};
|
||||||
|
// TODO: use artifactsPath for downloads and videos.
|
||||||
const downloadsPath = await ensurePath(DOWNLOADS_FOLDER, options.downloadsPath);
|
const downloadsPath = await ensurePath(DOWNLOADS_FOLDER, options.downloadsPath);
|
||||||
const _videosPath = await ensurePath(VIDEOS_FOLDER, options._videosPath);
|
const _videosPath = await ensurePath(VIDEOS_FOLDER, options._videosPath);
|
||||||
|
|
||||||
|
@ -240,6 +240,8 @@ export type BrowserContextOptions = {
|
|||||||
acceptDownloads?: boolean,
|
acceptDownloads?: boolean,
|
||||||
_recordVideos?: boolean,
|
_recordVideos?: boolean,
|
||||||
_videoSize?: Size,
|
_videoSize?: Size,
|
||||||
|
recordTrace?: boolean,
|
||||||
|
relativeArtifactsPath?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EnvArray = { name: string, value: string }[];
|
export type EnvArray = { name: string, value: string }[];
|
||||||
@ -257,6 +259,7 @@ type LaunchOptionsBase = {
|
|||||||
headless?: boolean,
|
headless?: boolean,
|
||||||
devtools?: boolean,
|
devtools?: boolean,
|
||||||
proxy?: ProxySettings,
|
proxy?: ProxySettings,
|
||||||
|
artifactsPath?: string,
|
||||||
downloadsPath?: string,
|
downloadsPath?: string,
|
||||||
_videosPath?: string,
|
_videosPath?: string,
|
||||||
chromiumSandbox?: boolean,
|
chromiumSandbox?: boolean,
|
||||||
|
@ -26,6 +26,7 @@ import * as types from '../server/types';
|
|||||||
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
import { SnapshotData, takeSnapshotInFrame } from './snapshotterInjected';
|
||||||
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
import { assert, calculateSha1, createGuid } from '../utils/utils';
|
||||||
import { ElementHandle } from '../server/dom';
|
import { ElementHandle } from '../server/dom';
|
||||||
|
import { FrameSnapshot, PageSnapshot } from './traceTypes';
|
||||||
|
|
||||||
export type SnapshotterResource = {
|
export type SnapshotterResource = {
|
||||||
pageId: string,
|
pageId: string,
|
||||||
@ -41,18 +42,6 @@ export type SnapshotterBlob = {
|
|||||||
sha1: string,
|
sha1: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FrameSnapshot = {
|
|
||||||
frameId: string,
|
|
||||||
url: string,
|
|
||||||
html: string,
|
|
||||||
resourceOverrides: { url: string, sha1: string }[],
|
|
||||||
};
|
|
||||||
export type PageSnapshot = {
|
|
||||||
viewportSize?: { width: number, height: number },
|
|
||||||
// First frame is the main frame.
|
|
||||||
frames: FrameSnapshot[],
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface SnapshotterDelegate {
|
export interface SnapshotterDelegate {
|
||||||
onBlob(blob: SnapshotterBlob): void;
|
onBlob(blob: SnapshotterBlob): void;
|
||||||
onResource(resource: SnapshotterResource): void;
|
onResource(resource: SnapshotterResource): void;
|
||||||
|
@ -69,3 +69,25 @@ export type ActionTraceEvent = {
|
|||||||
stack?: string,
|
stack?: string,
|
||||||
error?: string,
|
error?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TraceEvent =
|
||||||
|
ContextCreatedTraceEvent |
|
||||||
|
ContextDestroyedTraceEvent |
|
||||||
|
PageCreatedTraceEvent |
|
||||||
|
PageDestroyedTraceEvent |
|
||||||
|
NetworkResourceTraceEvent |
|
||||||
|
ActionTraceEvent;
|
||||||
|
|
||||||
|
|
||||||
|
export type FrameSnapshot = {
|
||||||
|
frameId: string,
|
||||||
|
url: string,
|
||||||
|
html: string,
|
||||||
|
resourceOverrides: { url: string, sha1: string }[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PageSnapshot = {
|
||||||
|
viewportSize?: { width: number, height: number },
|
||||||
|
// First frame is the main frame.
|
||||||
|
frames: FrameSnapshot[],
|
||||||
|
};
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ActionListener, ActionMetadata, BrowserContext } from '../server/browserContext';
|
import { ActionListener, ActionMetadata, BrowserContext, ContextListener, contextListeners } from '../server/browserContext';
|
||||||
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
import type { SnapshotterResource as SnapshotterResource, SnapshotterBlob, SnapshotterDelegate } from './snapshotter';
|
||||||
import { ContextCreatedTraceEvent, ContextDestroyedTraceEvent, NetworkResourceTraceEvent, ActionTraceEvent, PageCreatedTraceEvent, PageDestroyedTraceEvent } from './traceTypes';
|
import { ContextCreatedTraceEvent, ContextDestroyedTraceEvent, NetworkResourceTraceEvent, ActionTraceEvent, PageCreatedTraceEvent, PageDestroyedTraceEvent } from './traceTypes';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
@ -23,7 +23,6 @@ import * as fs from 'fs';
|
|||||||
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../utils/utils';
|
import { calculateSha1, createGuid, mkdirIfNeeded, monotonicTime } from '../utils/utils';
|
||||||
import { Page } from '../server/page';
|
import { Page } from '../server/page';
|
||||||
import { Snapshotter } from './snapshotter';
|
import { Snapshotter } from './snapshotter';
|
||||||
import * as types from '../server/types';
|
|
||||||
import { ElementHandle } from '../server/dom';
|
import { ElementHandle } from '../server/dom';
|
||||||
import { helper, RegisteredListener } from '../server/helper';
|
import { helper, RegisteredListener } from '../server/helper';
|
||||||
import { DEFAULT_TIMEOUT } from '../utils/timeoutSettings';
|
import { DEFAULT_TIMEOUT } from '../utils/timeoutSettings';
|
||||||
@ -33,36 +32,35 @@ const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
|||||||
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
const fsAppendFileAsync = util.promisify(fs.appendFile.bind(fs));
|
||||||
const fsAccessAsync = util.promisify(fs.access.bind(fs));
|
const fsAccessAsync = util.promisify(fs.access.bind(fs));
|
||||||
|
|
||||||
// TODO: merge Trace and ContextTracer.
|
export function installTracer() {
|
||||||
export class Tracer implements ActionListener {
|
contextListeners.add(new Tracer());
|
||||||
private _context: BrowserContext;
|
}
|
||||||
private _contextTracer: ContextTracer;
|
|
||||||
|
|
||||||
constructor(context: BrowserContext, traceStorageDir: string, traceFile: string) {
|
class Tracer implements ContextListener {
|
||||||
this._context = context;
|
private _contextTracers = new Map<BrowserContext, ContextTracer>();
|
||||||
this._contextTracer = new ContextTracer(context, traceStorageDir, traceFile);
|
|
||||||
this._context._actionListeners.add(this);
|
async onContextCreated(context: BrowserContext): Promise<void> {
|
||||||
|
if (!context._options.recordTrace)
|
||||||
|
return;
|
||||||
|
if (!context._artifactsPath)
|
||||||
|
throw new Error(`"recordTrace" option requires "artifactsPath" to be specified`);
|
||||||
|
const traceStorageDir = path.join(context._browser._options.artifactsPath!, '.playwright-shared');
|
||||||
|
const traceFile = path.join(context._artifactsPath, 'playwright.trace');
|
||||||
|
const contextTracer = new ContextTracer(context, traceStorageDir, traceFile);
|
||||||
|
this._contextTracers.set(context, contextTracer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
async onContextDestroyed(context: BrowserContext): Promise<void> {
|
||||||
await this._contextTracer.captureSnapshot(page, options);
|
const contextTracer = this._contextTracers.get(context);
|
||||||
}
|
if (contextTracer) {
|
||||||
|
await contextTracer.dispose().catch(e => {});
|
||||||
async dispose(): Promise<void> {
|
this._contextTracers.delete(context);
|
||||||
this._context._actionListeners.delete(this);
|
|
||||||
await this._contextTracer.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onAfterAction(result: ProgressResult, metadata: ActionMetadata): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this._contextTracer.recordAction(result, metadata);
|
|
||||||
} catch (e) {
|
|
||||||
// Do not throw from instrumentation.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContextTracer implements SnapshotterDelegate {
|
class ContextTracer implements SnapshotterDelegate, ActionListener {
|
||||||
|
private _context: BrowserContext;
|
||||||
private _contextId: string;
|
private _contextId: string;
|
||||||
private _traceStoragePromise: Promise<string>;
|
private _traceStoragePromise: Promise<string>;
|
||||||
private _appendEventChain: Promise<string>;
|
private _appendEventChain: Promise<string>;
|
||||||
@ -73,6 +71,7 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
private _pageToId = new Map<Page, string>();
|
private _pageToId = new Map<Page, string>();
|
||||||
|
|
||||||
constructor(context: BrowserContext, traceStorageDir: string, traceFile: string) {
|
constructor(context: BrowserContext, traceStorageDir: string, traceFile: string) {
|
||||||
|
this._context = context;
|
||||||
this._contextId = 'context@' + createGuid();
|
this._contextId = 'context@' + createGuid();
|
||||||
this._traceStoragePromise = mkdirIfNeeded(path.join(traceStorageDir, 'sha1')).then(() => traceStorageDir);
|
this._traceStoragePromise = mkdirIfNeeded(path.join(traceStorageDir, 'sha1')).then(() => traceStorageDir);
|
||||||
this._appendEventChain = mkdirIfNeeded(traceFile).then(() => traceFile);
|
this._appendEventChain = mkdirIfNeeded(traceFile).then(() => traceFile);
|
||||||
@ -90,6 +89,7 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(context, BrowserContext.Events.Page, this._onPage.bind(this)),
|
helper.addEventListener(context, BrowserContext.Events.Page, this._onPage.bind(this)),
|
||||||
];
|
];
|
||||||
|
this._context._actionListeners.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onBlob(blob: SnapshotterBlob): void {
|
onBlob(blob: SnapshotterBlob): void {
|
||||||
@ -114,38 +114,26 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
return this._pageToId.get(page)!;
|
return this._pageToId.get(page)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async captureSnapshot(page: Page, options: types.TimeoutOptions & { label?: string } = {}): Promise<void> {
|
async onAfterAction(result: ProgressResult, metadata: ActionMetadata): Promise<void> {
|
||||||
const snapshot = await this._takeSnapshot(page, undefined, options.timeout);
|
try {
|
||||||
if (!snapshot)
|
const snapshot = await this._takeSnapshot(metadata.page, typeof metadata.target === 'string' ? undefined : metadata.target);
|
||||||
return;
|
const event: ActionTraceEvent = {
|
||||||
const event: ActionTraceEvent = {
|
type: 'action',
|
||||||
type: 'action',
|
contextId: this._contextId,
|
||||||
contextId: this._contextId,
|
pageId: this._pageToId.get(metadata.page),
|
||||||
action: 'snapshot',
|
action: metadata.type,
|
||||||
pageId: this._pageToId.get(page),
|
selector: typeof metadata.target === 'string' ? metadata.target : undefined,
|
||||||
label: options.label || 'snapshot',
|
value: metadata.value,
|
||||||
snapshot,
|
snapshot,
|
||||||
};
|
startTime: result.startTime,
|
||||||
this._appendTraceEvent(event);
|
endTime: result.endTime,
|
||||||
}
|
stack: metadata.stack,
|
||||||
|
logs: result.logs.slice(),
|
||||||
async recordAction(result: ProgressResult, metadata: ActionMetadata) {
|
error: result.error ? result.error.stack : undefined,
|
||||||
const snapshot = await this._takeSnapshot(metadata.page, typeof metadata.target === 'string' ? undefined : metadata.target);
|
};
|
||||||
const event: ActionTraceEvent = {
|
this._appendTraceEvent(event);
|
||||||
type: 'action',
|
} catch (e) {
|
||||||
contextId: this._contextId,
|
}
|
||||||
pageId: this._pageToId.get(metadata.page),
|
|
||||||
action: metadata.type,
|
|
||||||
selector: typeof metadata.target === 'string' ? metadata.target : undefined,
|
|
||||||
value: metadata.value,
|
|
||||||
snapshot,
|
|
||||||
startTime: result.startTime,
|
|
||||||
endTime: result.endTime,
|
|
||||||
stack: metadata.stack,
|
|
||||||
logs: result.logs.slice(),
|
|
||||||
error: result.error ? result.error.stack : undefined,
|
|
||||||
};
|
|
||||||
this._appendTraceEvent(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onPage(page: Page) {
|
private _onPage(page: Page) {
|
||||||
@ -190,6 +178,7 @@ class ContextTracer implements SnapshotterDelegate {
|
|||||||
|
|
||||||
async dispose() {
|
async dispose() {
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
|
this._context._actionListeners.delete(this);
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
this._pageToId.clear();
|
this._pageToId.clear();
|
||||||
this._snapshotter.dispose();
|
this._snapshotter.dispose();
|
||||||
|
@ -18,7 +18,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import childProcess from 'child_process';
|
import childProcess from 'child_process';
|
||||||
import type { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServer } from '../index';
|
import type { LaunchOptions, BrowserType, Browser, BrowserContext, Page, BrowserServer, BrowserContextOptions } from '../index';
|
||||||
import { TestServer } from '../utils/testserver';
|
import { TestServer } from '../utils/testserver';
|
||||||
import { Connection } from '../lib/client/connection';
|
import { Connection } from '../lib/client/connection';
|
||||||
import { Transport } from '../lib/protocol/transport';
|
import { Transport } from '../lib/protocol/transport';
|
||||||
@ -88,8 +88,7 @@ export const options = {
|
|||||||
HEADLESS: !!valueFromEnv('HEADLESS', true),
|
HEADLESS: !!valueFromEnv('HEADLESS', true),
|
||||||
WIRE: !!process.env.PWWIRE,
|
WIRE: !!process.env.PWWIRE,
|
||||||
SLOW_MO: valueFromEnv('SLOW_MO', 0),
|
SLOW_MO: valueFromEnv('SLOW_MO', 0),
|
||||||
// Tracing is currently not implemented under wire.
|
TRACING: valueFromEnv('TRACING', false),
|
||||||
TRACING: valueFromEnv('TRACING', false) && !process.env.PWWIRE,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defineWorkerFixture('httpService', async ({parallelIndex}, test) => {
|
defineWorkerFixture('httpService', async ({parallelIndex}, test) => {
|
||||||
@ -121,16 +120,16 @@ const getExecutablePath = browserName => {
|
|||||||
return process.env.WKPATH;
|
return process.env.WKPATH;
|
||||||
};
|
};
|
||||||
|
|
||||||
defineWorkerFixture('defaultBrowserOptions', async ({browserName}, test) => {
|
defineWorkerFixture('defaultBrowserOptions', async ({browserName}, runTest, config) => {
|
||||||
const executablePath = getExecutablePath(browserName);
|
const executablePath = getExecutablePath(browserName);
|
||||||
|
|
||||||
if (executablePath)
|
if (executablePath)
|
||||||
console.error(`Using executable at ${executablePath}`);
|
console.error(`Using executable at ${executablePath}`);
|
||||||
await test({
|
await runTest({
|
||||||
handleSIGINT: false,
|
handleSIGINT: false,
|
||||||
slowMo: options.SLOW_MO,
|
slowMo: options.SLOW_MO,
|
||||||
headless: options.HEADLESS,
|
headless: options.HEADLESS,
|
||||||
executablePath
|
executablePath,
|
||||||
|
artifactsPath: config.outputDir,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -237,27 +236,20 @@ defineWorkerFixture('golden', async ({browserName}, test) => {
|
|||||||
await test(p => path.join(browserName, p));
|
await test(p => path.join(browserName, p));
|
||||||
});
|
});
|
||||||
|
|
||||||
defineTestFixture('context', async ({browser, toImpl}, runTest, info) => {
|
defineTestFixture('context', async ({browser}, runTest, info) => {
|
||||||
const context = await browser.newContext();
|
const { test, config } = info;
|
||||||
|
const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, '');
|
||||||
if (options.TRACING) {
|
const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_');
|
||||||
const { test, config } = info;
|
const contextOptions: BrowserContextOptions = {
|
||||||
const traceStorageDir = path.join(config.outputDir, 'trace-storage');
|
relativeArtifactsPath: path.join(relativePath, sanitizedTitle),
|
||||||
const relativePath = path.relative(config.testDir, test.file).replace(/\.spec\.[jt]s/, '');
|
recordTrace: !!options.TRACING,
|
||||||
const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_');
|
};
|
||||||
const traceFile = path.join(config.outputDir, relativePath, sanitizedTitle + '.trace');
|
const context = await browser.newContext(contextOptions);
|
||||||
const tracerFactory = require('../lib/trace/tracer').Tracer;
|
|
||||||
(context as any).__tracer = new tracerFactory(toImpl(context), traceStorageDir, traceFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
await runTest(context);
|
await runTest(context);
|
||||||
await context.close();
|
await context.close();
|
||||||
|
|
||||||
if ((context as any).__tracer)
|
|
||||||
await (context as any).__tracer.dispose();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
defineTestFixture('page', async ({context, playwright, toImpl}, runTest, info) => {
|
defineTestFixture('page', async ({context}, runTest, info) => {
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await runTest(page);
|
await runTest(page);
|
||||||
const { test, config, result } = info;
|
const { test, config, result } = info;
|
||||||
@ -266,8 +258,6 @@ defineTestFixture('page', async ({context, playwright, toImpl}, runTest, info) =
|
|||||||
const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_');
|
const sanitizedTitle = test.title.replace(/[^\w\d]+/g, '_');
|
||||||
const assetPath = path.join(config.outputDir, relativePath, sanitizedTitle) + '-failed.png';
|
const assetPath = path.join(config.outputDir, relativePath, sanitizedTitle) + '-failed.png';
|
||||||
await page.screenshot({ timeout: 5000, path: assetPath });
|
await page.screenshot({ timeout: 5000, path: assetPath });
|
||||||
if ((playwright as any).__tracer)
|
|
||||||
await (playwright as any).__tracer.captureSnapshot(toImpl(page), { timeout: 5000, label: 'Test Failed' });
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { it, options } from './playwright.fixtures';
|
|
||||||
|
|
||||||
it('should not throw', (test, parameters) => {
|
|
||||||
test.skip(!options.TRACING);
|
|
||||||
}, async ({page, server, context, toImpl}) => {
|
|
||||||
await page.goto(server.PREFIX + '/snapshot/snapshot-with-css.html');
|
|
||||||
await (context as any).__tracer.captureSnapshot(toImpl(page), { label: 'snapshot' });
|
|
||||||
});
|
|
65
test/trace.spec.ts
Normal file
65
test/trace.spec.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { it, expect } from './playwright.fixtures';
|
||||||
|
import type * as trace from '../types/trace';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
it('should record trace', async ({browserType, defaultBrowserOptions, server, tmpDir}) => {
|
||||||
|
const browser = await browserType.launch({
|
||||||
|
...defaultBrowserOptions,
|
||||||
|
artifactsPath: tmpDir,
|
||||||
|
});
|
||||||
|
const context = await browser.newContext({ recordTrace: true });
|
||||||
|
const page = await context.newPage();
|
||||||
|
const url = server.PREFIX + '/snapshot/snapshot-with-css.html';
|
||||||
|
await page.goto(url);
|
||||||
|
await context.close();
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
const traceFile = path.join(tmpDir, 'playwright.trace');
|
||||||
|
const traceFileContent = await fs.promises.readFile(traceFile, 'utf8');
|
||||||
|
const traceEvents = traceFileContent.split('\n').filter(line => !!line).map(line => JSON.parse(line)) as trace.TraceEvent[];
|
||||||
|
|
||||||
|
const contextEvent = traceEvents.find(event => event.type === 'context-created') as trace.ContextCreatedTraceEvent;
|
||||||
|
expect(contextEvent).toBeTruthy();
|
||||||
|
const contextId = contextEvent.contextId;
|
||||||
|
|
||||||
|
const pageEvent = traceEvents.find(event => event.type === 'page-created') as trace.PageCreatedTraceEvent;
|
||||||
|
expect(pageEvent).toBeTruthy();
|
||||||
|
expect(pageEvent.contextId).toBe(contextId);
|
||||||
|
const pageId = pageEvent.pageId;
|
||||||
|
|
||||||
|
const gotoEvent = traceEvents.find(event => event.type === 'action' && event.action === 'goto') as trace.ActionTraceEvent;
|
||||||
|
expect(gotoEvent).toBeTruthy();
|
||||||
|
expect(gotoEvent.contextId).toBe(contextId);
|
||||||
|
expect(gotoEvent.pageId).toBe(pageId);
|
||||||
|
expect(gotoEvent.value).toBe(url);
|
||||||
|
|
||||||
|
expect(gotoEvent.snapshot).toBeTruthy();
|
||||||
|
expect(fs.existsSync(path.join(tmpDir, '.playwright-shared', gotoEvent.snapshot!.sha1))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should require artifactsPath', async ({browserType, defaultBrowserOptions}) => {
|
||||||
|
const browser = await browserType.launch({
|
||||||
|
...defaultBrowserOptions,
|
||||||
|
artifactsPath: undefined,
|
||||||
|
});
|
||||||
|
const error = await browser.newContext({ recordTrace: true }).catch(e => e);
|
||||||
|
expect(error.message).toContain('"recordTrace" option requires "artifactsPath" to be specified');
|
||||||
|
await browser.close();
|
||||||
|
});
|
@ -32,6 +32,7 @@ let documentation;
|
|||||||
if (!fs.existsSync(typesDir))
|
if (!fs.existsSync(typesDir))
|
||||||
fs.mkdirSync(typesDir)
|
fs.mkdirSync(typesDir)
|
||||||
fs.writeFileSync(path.join(typesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'src', 'server', 'chromium', 'protocol.ts')), 'utf8');
|
fs.writeFileSync(path.join(typesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'src', 'server', 'chromium', 'protocol.ts')), 'utf8');
|
||||||
|
fs.writeFileSync(path.join(typesDir, 'trace.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'src', 'trace', 'traceTypes.ts')), 'utf8');
|
||||||
const browser = await chromium.launch();
|
const browser = await chromium.launch();
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
const api = await Source.readFile(path.join(PROJECT_DIR, 'docs', 'api.md'));
|
const api = await Source.readFile(path.join(PROJECT_DIR, 'docs', 'api.md'));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user