chore: make tracing zero config (#6859)

This commit is contained in:
Pavel Feldman 2021-06-02 22:00:34 -07:00 committed by GitHub
parent 837ee08a53
commit b2143a951b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 121 additions and 113 deletions

View file

@ -7,41 +7,40 @@ Playwright script runs.
Start with specifying the folder traces will be stored in: Start with specifying the folder traces will be stored in:
```js ```js
const browser = await chromium.launch({ traceDir: 'traces' }); const browser = await chromium.launch();
const context = await browser.newContext(); const context = await browser.newContext();
await context.tracing.start({ name: 'trace', screenshots: true, snapshots: true }); await context.tracing.start({ screenshots: true, snapshots: true });
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://playwright.dev'); await page.goto('https://playwright.dev');
await context.tracing.stop({ path: 'trace.zip' }); await context.tracing.stop({ path: 'trace.zip' });
``` ```
```java ```java
Browser browser = chromium.launch(new BrowserType.LaunchOptions().setTraceDir("trace")); Browser browser = chromium.launch();
BrowserContext context = browser.newContext(); BrowserContext context = browser.newContext();
context.tracing.start(page, new Tracing.StartOptions() context.tracing.start(page, new Tracing.StartOptions()
.setName("trace")
.setScreenshots(true) .setScreenshots(true)
.setSnapshots(true); .setSnapshots(true);
Page page = context.newPage(); Page page = context.newPage();
page.goto("https://playwright.dev"); page.goto("https://playwright.dev");
context.tracing.stop(new Tracing.StopOptions() context.tracing.stop(new Tracing.StopOptions()
.setSaveAs(Paths.get("trace.zip"))); .setPath(Paths.get("trace.zip")));
``` ```
```python async ```python async
browser = await chromium.launch(traceDir='traces') browser = await chromium.launch()
context = await browser.new_context() context = await browser.new_context()
await context.tracing.start(name="trace", screenshots=True, snapshots=True) await context.tracing.start(screenshots=True, snapshots=True)
await page.goto("https://playwright.dev") await page.goto("https://playwright.dev")
await context.tracing.stop(save_as = "trace.zip") await context.tracing.stop(path = "trace.zip")
``` ```
```python sync ```python sync
browser = chromium.launch(traceDir='traces') browser = chromium.launch()
context = browser.new_context() context = browser.new_context()
context.tracing.start(name="trace", screenshots=True, snapshots=True) context.tracing.start(screenshots=True, snapshots=True)
page.goto("https://playwright.dev") page.goto("https://playwright.dev")
context.tracing.stop(save_as = "trace.zip") context.tracing.stop(path = "trace.zip")
``` ```
## async method: Tracing.start ## async method: Tracing.start
@ -49,43 +48,41 @@ context.tracing.stop(save_as = "trace.zip")
Start tracing. Start tracing.
```js ```js
await context.tracing.start({ name: 'trace', screenshots: true, snapshots: true }); await context.tracing.start({ screenshots: true, snapshots: true });
const page = await context.newPage(); const page = await context.newPage();
await page.goto('https://playwright.dev'); await page.goto('https://playwright.dev');
await context.tracing.stop(); await context.tracing.stop({ path: 'trace.zip' });
await context.tracing.export('trace.zip');
``` ```
```java ```java
context.tracing.start(page, new Tracing.StartOptions() context.tracing.start(page, new Tracing.StartOptions()
.setName("trace")
.setScreenshots(true) .setScreenshots(true)
.setSnapshots(true); .setSnapshots(true);
Page page = context.newPage(); Page page = context.newPage();
page.goto('https://playwright.dev'); page.goto('https://playwright.dev');
context.tracing.stop(); context.tracing.stop(new Tracing.StopOptions()
context.tracing.export(Paths.get("trace.zip"))) .setPath(Paths.get("trace.zip")));
``` ```
```python async ```python async
await context.tracing.start(name="trace", screenshots=True, snapshots=True) await context.tracing.start(name="trace", screenshots=True, snapshots=True)
await page.goto("https://playwright.dev") await page.goto("https://playwright.dev")
await context.tracing.stop() await context.tracing.stop()
await context.tracing.export("trace.zip") await context.tracing.stop(path = "trace.zip")
``` ```
```python sync ```python sync
context.tracing.start(name="trace", screenshots=True, snapshots=True) context.tracing.start(name="trace", screenshots=True, snapshots=True)
page.goto("https://playwright.dev") page.goto("https://playwright.dev")
context.tracing.stop() context.tracing.stop()
context.tracing.export("trace.zip") context.tracing.stop(path = "trace.zip")
``` ```
### option: Tracing.start.name ### option: Tracing.start.name
- `name` <[string]> - `name` <[string]>
If specified, the trace is going to be saved into the file with the If specified, the trace is going to be saved into the file with the
given name inside the [`option: traceDir`] folder specified in [`method: BrowserType.launch`]. given name inside the [`option: tracesDir`] folder specified in [`method: BrowserType.launch`].
### option: Tracing.start.screenshots ### option: Tracing.start.screenshots
- `screenshots` <[boolean]> - `screenshots` <[boolean]>

View file

@ -675,9 +675,9 @@ Logger sink for Playwright logging.
Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to
disable timeout. disable timeout.
## browser-option-tracedir ## browser-option-tracesdir
* langs: js, python, java * langs: js, python, java
- `traceDir` <[path]> - `tracesDir` <[path]>
If specified, traces are saved into this directory. If specified, traces are saved into this directory.
@ -708,4 +708,4 @@ Slows down Playwright operations by the specified amount of milliseconds. Useful
- %%-browser-option-ignoredefaultargs-%% - %%-browser-option-ignoredefaultargs-%%
- %%-browser-option-proxy-%% - %%-browser-option-proxy-%%
- %%-browser-option-timeout-%% - %%-browser-option-timeout-%%
- %%-browser-option-tracedir-%% - %%-browser-option-tracesdir-%%

View file

@ -107,10 +107,8 @@ class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.BrowserIni
} }
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> { async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> {
if (params.recordVideo) { if (params.recordVideo)
// TODO: we should create a separate temp directory or accept a launchServer parameter. params.recordVideo.dir = this._object.options.artifactsDir;
params.recordVideo.dir = this._object.options.downloadsPath!;
}
const context = await this._object.newContext(params); const context = await this._object.newContext(params);
this._contexts.add(context); this._contexts.add(context);
context._setSelectors(this._selectors); context._setSelectors(this._selectors);

View file

@ -238,7 +238,7 @@ export type BrowserTypeLaunchParams = {
password?: string, password?: string,
}, },
downloadsPath?: string, downloadsPath?: string,
traceDir?: string, tracesDir?: string,
chromiumSandbox?: boolean, chromiumSandbox?: boolean,
firefoxUserPrefs?: any, firefoxUserPrefs?: any,
slowMo?: number, slowMo?: number,
@ -263,7 +263,7 @@ export type BrowserTypeLaunchOptions = {
password?: string, password?: string,
}, },
downloadsPath?: string, downloadsPath?: string,
traceDir?: string, tracesDir?: string,
chromiumSandbox?: boolean, chromiumSandbox?: boolean,
firefoxUserPrefs?: any, firefoxUserPrefs?: any,
slowMo?: number, slowMo?: number,
@ -291,7 +291,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
password?: string, password?: string,
}, },
downloadsPath?: string, downloadsPath?: string,
traceDir?: string, tracesDir?: string,
chromiumSandbox?: boolean, chromiumSandbox?: boolean,
sdkLanguage: string, sdkLanguage: string,
noDefaultViewport?: boolean, noDefaultViewport?: boolean,
@ -362,7 +362,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
password?: string, password?: string,
}, },
downloadsPath?: string, downloadsPath?: string,
traceDir?: string, tracesDir?: string,
chromiumSandbox?: boolean, chromiumSandbox?: boolean,
noDefaultViewport?: boolean, noDefaultViewport?: boolean,
viewport?: { viewport?: {

View file

@ -249,7 +249,7 @@ LaunchOptions:
username: string? username: string?
password: string? password: string?
downloadsPath: string? downloadsPath: string?
traceDir: string? tracesDir: string?
chromiumSandbox: boolean? chromiumSandbox: boolean?

View file

@ -175,7 +175,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
password: tOptional(tString), password: tOptional(tString),
})), })),
downloadsPath: tOptional(tString), downloadsPath: tOptional(tString),
traceDir: tOptional(tString), tracesDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean), chromiumSandbox: tOptional(tBoolean),
firefoxUserPrefs: tOptional(tAny), firefoxUserPrefs: tOptional(tAny),
slowMo: tOptional(tNumber), slowMo: tOptional(tNumber),
@ -200,7 +200,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
password: tOptional(tString), password: tOptional(tString),
})), })),
downloadsPath: tOptional(tString), downloadsPath: tOptional(tString),
traceDir: tOptional(tString), tracesDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean), chromiumSandbox: tOptional(tBoolean),
sdkLanguage: tString, sdkLanguage: tString,
noDefaultViewport: tOptional(tBoolean), noDefaultViewport: tOptional(tBoolean),

View file

@ -265,7 +265,9 @@ export class AndroidDevice extends SdkObject {
isChromium: true, isChromium: true,
slowMo: 0, slowMo: 0,
persistent: { ...options, noDefaultViewport: true }, persistent: { ...options, noDefaultViewport: true },
downloadsPath: undefined, artifactsDir: '',
downloadsPath: '',
tracesDir: '',
browserProcess: new ClankBrowserProcess(androidBrowser), browserProcess: new ClankBrowserProcess(androidBrowser),
proxy: options.proxy, proxy: options.proxy,
protocolLogger: helper.debugProtocolLogger(), protocolLogger: helper.debugProtocolLogger(),

View file

@ -42,8 +42,9 @@ export type BrowserOptions = PlaywrightOptions & {
name: string, name: string,
isChromium: boolean, isChromium: boolean,
channel?: string, channel?: string,
downloadsPath?: string, artifactsDir: string;
traceDir?: string, downloadsPath: string,
tracesDir: string,
headful?: boolean, headful?: boolean,
persistent?: types.BrowserContextOptions, // Undefined means no persistent context. persistent?: types.BrowserContextOptions, // Undefined means no persistent context.
browserProcess: BrowserProcess, browserProcess: BrowserProcess,

View file

@ -36,7 +36,7 @@ import { CallMetadata, SdkObject } from './instrumentation';
const mkdirAsync = util.promisify(fs.mkdir); const mkdirAsync = util.promisify(fs.mkdir);
const mkdtempAsync = util.promisify(fs.mkdtemp); const mkdtempAsync = util.promisify(fs.mkdtemp);
const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err))); const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-'); const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export abstract class BrowserType extends SdkObject { export abstract class BrowserType extends SdkObject {
private _name: registry.BrowserName; private _name: registry.BrowserName;
@ -97,7 +97,7 @@ export abstract class BrowserType extends SdkObject {
async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise<Browser> { async _innerLaunch(progress: Progress, options: types.LaunchOptions, persistent: types.BrowserContextOptions | undefined, protocolLogger: types.ProtocolLogger, userDataDir?: string): Promise<Browser> {
options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined; options.proxy = options.proxy ? normalizeProxySettings(options.proxy) : undefined;
const browserLogsCollector = new RecentLogsCollector(); const browserLogsCollector = new RecentLogsCollector();
const { browserProcess, downloadsPath, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, userDataDir); const { browserProcess, artifactsDir, transport } = await this._launchProcess(progress, options, !!persistent, browserLogsCollector, userDataDir);
if ((options as any).__testHookBeforeCreateBrowser) if ((options as any).__testHookBeforeCreateBrowser)
await (options as any).__testHookBeforeCreateBrowser(); await (options as any).__testHookBeforeCreateBrowser();
const browserOptions: BrowserOptions = { const browserOptions: BrowserOptions = {
@ -108,14 +108,15 @@ export abstract class BrowserType extends SdkObject {
slowMo: options.slowMo, slowMo: options.slowMo,
persistent, persistent,
headful: !options.headless, headful: !options.headless,
downloadsPath, artifactsDir,
downloadsPath: (options.downloadsPath || artifactsDir)!,
tracesDir: (options.tracesDir || artifactsDir)!,
browserProcess, browserProcess,
customExecutablePath: options.executablePath, customExecutablePath: options.executablePath,
proxy: options.proxy, proxy: options.proxy,
protocolLogger, protocolLogger,
browserLogsCollector, browserLogsCollector,
wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined, wsEndpoint: options.useWebSocket ? (transport as WebSocketTransport).wsEndpoint : undefined,
traceDir: options.traceDir,
}; };
if (persistent) if (persistent)
validateBrowserContextOptions(persistent, browserOptions); validateBrowserContextOptions(persistent, browserOptions);
@ -127,7 +128,7 @@ export abstract class BrowserType extends SdkObject {
return browser; return browser;
} }
private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, browserLogsCollector: RecentLogsCollector, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, downloadsPath: string, transport: ConnectionTransport }> { private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, browserLogsCollector: RecentLogsCollector, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, artifactsDir: string, transport: ConnectionTransport }> {
const { const {
ignoreDefaultArgs, ignoreDefaultArgs,
ignoreAllDefaultArgs, ignoreAllDefaultArgs,
@ -141,19 +142,13 @@ export abstract class BrowserType extends SdkObject {
const env = options.env ? envArrayToObject(options.env) : process.env; const env = options.env ? envArrayToObject(options.env) : process.env;
const tempDirectories = []; const tempDirectories = [];
const ensurePath = async (tmpPrefix: string, pathFromOptions?: string) => { if (options.downloadsPath)
let dir; await mkdirAsync(options.downloadsPath, { recursive: true });
if (pathFromOptions) { if (options.tracesDir)
dir = pathFromOptions; await mkdirAsync(options.tracesDir, { recursive: true });
await mkdirAsync(pathFromOptions, { recursive: true });
} else { const artifactsDir = await mkdtempAsync(ARTIFACTS_FOLDER);
dir = await mkdtempAsync(tmpPrefix); tempDirectories.push(artifactsDir);
tempDirectories.push(dir);
}
return dir;
};
// TODO: add downloadsPath to newContext().
const downloadsPath = await ensurePath(DOWNLOADS_FOLDER, options.downloadsPath);
if (!userDataDir) { if (!userDataDir) {
userDataDir = await mkdtempAsync(path.join(os.tmpdir(), `playwright_${this._name}dev_profile-`)); userDataDir = await mkdtempAsync(path.join(os.tmpdir(), `playwright_${this._name}dev_profile-`));
@ -252,7 +247,7 @@ export abstract class BrowserType extends SdkObject {
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
transport = new PipeTransport(stdio[3], stdio[4]); transport = new PipeTransport(stdio[3], stdio[4]);
} }
return { browserProcess, downloadsPath, transport }; return { browserProcess, artifactsDir, transport };
} }
async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number): Promise<Browser> { async connectOverCDP(metadata: CallMetadata, endpointURL: string, options: { slowMo?: number, sdkLanguage: string }, timeout?: number): Promise<Browser> {

View file

@ -15,7 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import fs from 'fs';
import os from 'os';
import path from 'path'; import path from 'path';
import util from 'util';
import { CRBrowser } from './crBrowser'; import { CRBrowser } from './crBrowser';
import { Env } from '../processLauncher'; import { Env } from '../processLauncher';
import { kBrowserCloseMessageId } from './crConnection'; import { kBrowserCloseMessageId } from './crConnection';
@ -25,7 +28,7 @@ import { ConnectionTransport, ProtocolRequest, WebSocketTransport } from '../tra
import { CRDevTools } from './crDevTools'; import { CRDevTools } from './crDevTools';
import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser'; import { BrowserOptions, BrowserProcess, PlaywrightOptions } from '../browser';
import * as types from '../types'; import * as types from '../types';
import { debugMode, headersArrayToObject } from '../../utils/utils'; import { debugMode, headersArrayToObject, removeFolders } from '../../utils/utils';
import { RecentLogsCollector } from '../../utils/debugLogger'; import { RecentLogsCollector } from '../../utils/debugLogger';
import { ProgressController } from '../progress'; import { ProgressController } from '../progress';
import { TimeoutSettings } from '../../utils/timeoutSettings'; import { TimeoutSettings } from '../../utils/timeoutSettings';
@ -34,6 +37,9 @@ import { CallMetadata } from '../instrumentation';
import { findChromiumChannel } from './findChromiumChannel'; import { findChromiumChannel } from './findChromiumChannel';
import http from 'http'; import http from 'http';
const mkdtempAsync = util.promisify(fs.mkdtemp);
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export class Chromium extends BrowserType { export class Chromium extends BrowserType {
private _devtools: CRDevTools | undefined; private _devtools: CRDevTools | undefined;
@ -58,12 +64,17 @@ export class Chromium extends BrowserType {
let headersMap: { [key: string]: string; } | undefined; let headersMap: { [key: string]: string; } | undefined;
if (options.headers) if (options.headers)
headersMap = headersArrayToObject(options.headers, false); headersMap = headersArrayToObject(options.headers, false);
const artifactsDir = await mkdtempAsync(ARTIFACTS_FOLDER);
const chromeTransport = await WebSocketTransport.connect(progress, await urlToWSEndpoint(endpointURL), headersMap); const chromeTransport = await WebSocketTransport.connect(progress, await urlToWSEndpoint(endpointURL), headersMap);
const browserProcess: BrowserProcess = { const browserProcess: BrowserProcess = {
close: async () => { close: async () => {
await removeFolders([ artifactsDir ]);
await chromeTransport.closeAndWait(); await chromeTransport.closeAndWait();
}, },
kill: async () => { kill: async () => {
await removeFolders([ artifactsDir ]);
await chromeTransport.closeAndWait(); await chromeTransport.closeAndWait();
} }
}; };
@ -76,6 +87,9 @@ export class Chromium extends BrowserType {
browserProcess, browserProcess,
protocolLogger: helper.debugProtocolLogger(), protocolLogger: helper.debugProtocolLogger(),
browserLogsCollector, browserLogsCollector,
artifactsDir,
downloadsPath: artifactsDir,
tracesDir: artifactsDir
}; };
return await CRBrowser.connect(chromeTransport, browserOptions); return await CRBrowser.connect(chromeTransport, browserOptions);
}, TimeoutSettings.timeout({timeout})); }, TimeoutSettings.timeout({timeout}));

View file

@ -279,7 +279,7 @@ export class CRBrowserContext extends BrowserContext {
async _initialize() { async _initialize() {
assert(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this)); assert(!Array.from(this._browser._crPages.values()).some(page => page._browserContext === this));
const promises: Promise<any>[] = [ super._initialize() ]; const promises: Promise<any>[] = [ super._initialize() ];
if (this._browser.options.downloadsPath) { if (this._browser.options.name !== 'electron') {
promises.push(this._browser._session.send('Browser.setDownloadBehavior', { promises.push(this._browser._session.send('Browser.setDownloadBehavior', {
behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny', behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny',
browserContextId: this._browserContextId, browserContextId: this._browserContextId,

View file

@ -14,7 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import * as os from 'os'; import fs from 'fs';
import os from 'os';
import path from 'path';
import util from 'util';
import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser'; import { CRBrowser, CRBrowserContext } from '../chromium/crBrowser';
import { CRConnection, CRSession } from '../chromium/crConnection'; import { CRConnection, CRSession } from '../chromium/crConnection';
import { CRExecutionContext } from '../chromium/crExecutionContext'; import { CRExecutionContext } from '../chromium/crExecutionContext';
@ -34,6 +37,9 @@ import { RecentLogsCollector } from '../../utils/debugLogger';
import { internalCallMetadata, SdkObject } from '../instrumentation'; import { internalCallMetadata, SdkObject } from '../instrumentation';
import * as channels from '../../protocol/channels'; import * as channels from '../../protocol/channels';
const mkdtempAsync = util.promisify(fs.mkdtemp);
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
export class ElectronApplication extends SdkObject { export class ElectronApplication extends SdkObject {
static Events = { static Events = {
Close: 'close', Close: 'close',
@ -119,6 +125,8 @@ export class Electron extends SdkObject {
electronArguments.push('--no-sandbox'); electronArguments.push('--no-sandbox');
} }
const artifactsDir = await mkdtempAsync(ARTIFACTS_FOLDER);
const browserLogsCollector = new RecentLogsCollector(); const browserLogsCollector = new RecentLogsCollector();
const { launchedProcess, gracefullyClose, kill } = await launchProcess({ const { launchedProcess, gracefullyClose, kill } = await launchProcess({
executablePath: options.executablePath || require('electron/index.js'), executablePath: options.executablePath || require('electron/index.js'),
@ -130,7 +138,7 @@ export class Electron extends SdkObject {
}, },
stdio: 'pipe', stdio: 'pipe',
cwd: options.cwd, cwd: options.cwd,
tempDirectories: [], tempDirectories: [ artifactsDir ],
attemptToGracefullyClose: () => app!.close(), attemptToGracefullyClose: () => app!.close(),
handleSIGINT: true, handleSIGINT: true,
handleSIGTERM: true, handleSIGTERM: true,
@ -174,6 +182,9 @@ export class Electron extends SdkObject {
browserProcess, browserProcess,
protocolLogger: helper.debugProtocolLogger(), protocolLogger: helper.debugProtocolLogger(),
browserLogsCollector, browserLogsCollector,
artifactsDir,
downloadsPath: artifactsDir,
tracesDir: artifactsDir,
}; };
const browser = await CRBrowser.connect(chromeTransport, browserOptions); const browser = await CRBrowser.connect(chromeTransport, browserOptions);
app = new ElectronApplication(this, browser, nodeConnection); app = new ElectronApplication(this, browser, nodeConnection);

View file

@ -158,15 +158,13 @@ export class FFBrowserContext extends BrowserContext {
assert(!this._ffPages().length); assert(!this._ffPages().length);
const browserContextId = this._browserContextId; const browserContextId = this._browserContextId;
const promises: Promise<any>[] = [ super._initialize() ]; const promises: Promise<any>[] = [ super._initialize() ];
if (this._browser.options.downloadsPath) { promises.push(this._browser._connection.send('Browser.setDownloadOptions', {
promises.push(this._browser._connection.send('Browser.setDownloadOptions', { browserContextId,
browserContextId, downloadOptions: {
downloadOptions: { behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel',
behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel', downloadsDir: this._browser.options.downloadsPath,
downloadsDir: this._browser.options.downloadsPath, },
}, }));
}));
}
if (this._options.viewport) { if (this._options.viewport) {
const viewport = { const viewport = {
viewportSize: { width: this._options.viewport.width, height: this._options.viewport.height }, viewportSize: { width: this._options.viewport.width, height: this._options.viewport.height },

View file

@ -49,6 +49,10 @@ export class TraceSnapshotter extends EventEmitter implements SnapshotterDelegat
await this._snapshotter.start(); await this._snapshotter.start();
} }
async stop(): Promise<void> {
await this._snapshotter.stop();
}
async dispose() { async dispose() {
this._snapshotter.dispose(); this._snapshotter.dispose();
await this._writeArtifactChain; await this._writeArtifactChain;

View file

@ -48,23 +48,23 @@ export class Tracing implements InstrumentationListener {
private _resourcesDir: string; private _resourcesDir: string;
private _sha1s: string[] = []; private _sha1s: string[] = [];
private _started = false; private _started = false;
private _traceDir: string | undefined; private _tracesDir: string | undefined;
constructor(context: BrowserContext) { constructor(context: BrowserContext) {
this._context = context; this._context = context;
this._traceDir = context._browser.options.traceDir; this._tracesDir = context._browser.options.tracesDir;
this._resourcesDir = path.join(this._traceDir || '', 'resources'); this._resourcesDir = path.join(this._tracesDir, 'resources');
this._snapshotter = new TraceSnapshotter(this._context, this._resourcesDir, traceEvent => this._appendTraceEvent(traceEvent)); this._snapshotter = new TraceSnapshotter(this._context, this._resourcesDir, traceEvent => this._appendTraceEvent(traceEvent));
} }
async start(options: TracerOptions): Promise<void> { async start(options: TracerOptions): Promise<void> {
// context + page must be the first events added, this method can't have awaits before them. // context + page must be the first events added, this method can't have awaits before them.
if (!this._traceDir) if (!this._tracesDir)
throw new Error('Tracing directory is not specified when launching the browser'); throw new Error('Tracing directory is not specified when launching the browser');
if (this._started) if (this._started)
throw new Error('Tracing has already been started'); throw new Error('Tracing has already been started');
this._started = true; this._started = true;
this._traceFile = path.join(this._traceDir, (options.name || createGuid()) + '.trace'); this._traceFile = path.join(this._tracesDir, (options.name || createGuid()) + '.trace');
this._appendEventChain = mkdirIfNeeded(this._traceFile); this._appendEventChain = mkdirIfNeeded(this._traceFile);
const event: trace.ContextCreatedTraceEvent = { const event: trace.ContextCreatedTraceEvent = {
@ -91,6 +91,7 @@ export class Tracing implements InstrumentationListener {
if (!this._started) if (!this._started)
return; return;
this._started = false; this._started = false;
await this._snapshotter.stop();
this._context.instrumentation.removeListener(this); this._context.instrumentation.removeListener(this);
helper.removeEventListeners(this._eventListeners); helper.removeEventListeners(this._eventListeners);
for (const { sdkObject, metadata } of this._pendingCalls.values()) for (const { sdkObject, metadata } of this._pendingCalls.values())

View file

@ -33,9 +33,9 @@ export class TraceViewer {
private _server: HttpServer; private _server: HttpServer;
private _browserName: string; private _browserName: string;
constructor(traceDir: string, browserName: string) { constructor(tracesDir: string, browserName: string) {
this._browserName = browserName; this._browserName = browserName;
const resourcesDir = path.join(traceDir, 'resources'); const resourcesDir = path.join(tracesDir, 'resources');
// Served by TraceServer // Served by TraceServer
// - "/tracemodel" - json with trace model. // - "/tracemodel" - json with trace model.
@ -51,9 +51,9 @@ export class TraceViewer {
// - "/snapshot/pageId/..." - actual snapshot html. // - "/snapshot/pageId/..." - actual snapshot html.
// - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources // - "/snapshot/service-worker.js" - service worker that intercepts snapshot resources
// and translates them into "/resources/<resourceId>". // and translates them into "/resources/<resourceId>".
const actionTraces = fs.readdirSync(traceDir).filter(name => name.endsWith('.trace')); const actionTraces = fs.readdirSync(tracesDir).filter(name => name.endsWith('.trace'));
const debugNames = actionTraces.map(name => { const debugNames = actionTraces.map(name => {
const tracePrefix = path.join(traceDir, name.substring(0, name.indexOf('.trace'))); const tracePrefix = path.join(tracesDir, name.substring(0, name.indexOf('.trace')));
return path.basename(tracePrefix); return path.basename(tracePrefix);
}); });
@ -71,7 +71,7 @@ export class TraceViewer {
const traceModelHandler: ServerRouteHandler = (request, response) => { const traceModelHandler: ServerRouteHandler = (request, response) => {
const debugName = request.url!.substring('/context/'.length); const debugName = request.url!.substring('/context/'.length);
const tracePrefix = path.join(traceDir, debugName); const tracePrefix = path.join(tracesDir, debugName);
snapshotStorage.clear(); snapshotStorage.clear();
response.statusCode = 200; response.statusCode = 200;
response.setHeader('Content-Type', 'application/json'); response.setHeader('Content-Type', 'application/json');

View file

@ -274,7 +274,7 @@ type LaunchOptionsBase = {
chromiumSandbox?: boolean, chromiumSandbox?: boolean,
slowMo?: number, slowMo?: number,
useWebSocket?: boolean, useWebSocket?: boolean,
traceDir?: string, tracesDir?: string,
}; };
export type LaunchOptions = LaunchOptionsBase & { export type LaunchOptions = LaunchOptionsBase & {
firefoxUserPrefs?: { [key: string]: string | number | boolean }, firefoxUserPrefs?: { [key: string]: string | number | boolean },

View file

@ -213,13 +213,11 @@ export class WKBrowserContext extends BrowserContext {
assert(!this._wkPages().length); assert(!this._wkPages().length);
const browserContextId = this._browserContextId; const browserContextId = this._browserContextId;
const promises: Promise<any>[] = [ super._initialize() ]; const promises: Promise<any>[] = [ super._initialize() ];
if (this._browser.options.downloadsPath) { promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', {
promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', { behavior: this._options.acceptDownloads ? 'allow' : 'deny',
behavior: this._options.acceptDownloads ? 'allow' : 'deny', downloadPath: this._browser.options.downloadsPath,
downloadPath: this._browser.options.downloadsPath, browserContextId
browserContextId }));
}));
}
if (this._options.ignoreHTTPSErrors) if (this._options.ignoreHTTPSErrors)
promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true })); promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true }));
if (this._options.locale) if (this._options.locale)

View file

@ -27,7 +27,7 @@ import { baseTest, CommonWorkerFixtures } from './baseTest';
const mkdtempAsync = util.promisify(fs.mkdtemp); const mkdtempAsync = util.promisify(fs.mkdtemp);
type PlaywrightWorkerOptions = { type PlaywrightWorkerOptions = {
traceDir: LaunchOptions['traceDir']; tracesDir: LaunchOptions['tracesDir'];
executablePath: LaunchOptions['executablePath']; executablePath: LaunchOptions['executablePath'];
proxy: LaunchOptions['proxy']; proxy: LaunchOptions['proxy'];
args: LaunchOptions['args']; args: LaunchOptions['args'];
@ -53,7 +53,7 @@ type PlaywrightTestFixtures = {
export type PlaywrightOptions = PlaywrightWorkerOptions & PlaywrightTestOptions; export type PlaywrightOptions = PlaywrightWorkerOptions & PlaywrightTestOptions;
export const playwrightFixtures: folio.Fixtures<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures, {}, CommonWorkerFixtures> = { export const playwrightFixtures: folio.Fixtures<PlaywrightTestOptions & PlaywrightTestFixtures, PlaywrightWorkerOptions & PlaywrightWorkerFixtures, {}, CommonWorkerFixtures> = {
traceDir: [ undefined, { scope: 'worker' } ], tracesDir: [ undefined, { scope: 'worker' } ],
executablePath: [ undefined, { scope: 'worker' } ], executablePath: [ undefined, { scope: 'worker' } ],
proxy: [ undefined, { scope: 'worker' } ], proxy: [ undefined, { scope: 'worker' } ],
args: [ undefined, { scope: 'worker' } ], args: [ undefined, { scope: 'worker' } ],
@ -63,12 +63,12 @@ export const playwrightFixtures: folio.Fixtures<PlaywrightTestOptions & Playwrig
await run(playwright[browserName]); await run(playwright[browserName]);
}, { scope: 'worker' } ], }, { scope: 'worker' } ],
browserOptions: [async ({ headless, channel, executablePath, traceDir, proxy, args }, run) => { browserOptions: [async ({ headless, channel, executablePath, tracesDir, proxy, args }, run) => {
await run({ await run({
headless, headless,
channel, channel,
executablePath, executablePath,
traceDir, tracesDir,
proxy, proxy,
args, args,
handleSIGINT: false, handleSIGINT: false,

View file

@ -78,7 +78,7 @@ for (const browserName of browserNames) {
channel, channel,
video, video,
executablePath, executablePath,
traceDir: process.env.PWTRACE ? path.join(outputDir, 'trace') : undefined, tracesDir: process.env.PWTRACE ? path.join(outputDir, 'trace') : undefined,
coverageName: browserName, coverageName: browserName,
}, },
define: { test: pageTest, fixtures: pageFixtures }, define: { test: pageTest, fixtures: pageFixtures },

View file

@ -50,7 +50,7 @@ export class RemoteServer {
args: browserOptions.args, args: browserOptions.args,
headless: browserOptions.headless, headless: browserOptions.headless,
channel: browserOptions.channel, channel: browserOptions.channel,
traceDir: browserOptions.traceDir, tracesDir: browserOptions.tracesDir,
handleSIGINT: true, handleSIGINT: true,
handleSIGTERM: true, handleSIGTERM: true,
handleSIGHUP: true, handleSIGHUP: true,

View file

@ -14,20 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
import path from 'path';
import { expect, contextTest as test, browserTest } from './config/browserTest'; import { expect, contextTest as test, browserTest } from './config/browserTest';
import yauzl from 'yauzl'; import yauzl from 'yauzl';
import removeFolder from 'rimraf';
import jpeg from 'jpeg-js'; import jpeg from 'jpeg-js';
const traceDir = path.join(__dirname, '..', 'test-results', 'trace-' + process.env.FOLIO_WORKER_INDEX);
test.use({ traceDir });
test.beforeEach(async ({ browserName, headless }) => {
test.fixme(browserName === 'chromium' && !headless, 'Chromium screencast on headed has a min width issue');
await new Promise(f => removeFolder(traceDir, f));
});
test('should collect trace', async ({ context, page, server, browserName }, testInfo) => { test('should collect trace', async ({ context, page, server, browserName }, testInfo) => {
await context.tracing.start({ name: 'test', screenshots: true, snapshots: true }); await context.tracing.start({ name: 'test', screenshots: true, snapshots: true });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);

19
types/types.d.ts vendored
View file

@ -7235,7 +7235,7 @@ export interface BrowserType<Unused = {}> {
/** /**
* If specified, traces are saved into this directory. * If specified, traces are saved into this directory.
*/ */
traceDir?: string; tracesDir?: string;
/** /**
* Specific user agent to use in this context. * Specific user agent to use in this context.
@ -7424,7 +7424,7 @@ export interface BrowserType<Unused = {}> {
/** /**
* If specified, traces are saved into this directory. * If specified, traces are saved into this directory.
*/ */
traceDir?: string; tracesDir?: string;
}): Promise<BrowserServer>; }): Promise<BrowserServer>;
/** /**
@ -10604,9 +10604,9 @@ export interface Touchscreen {
* Start with specifying the folder traces will be stored in: * Start with specifying the folder traces will be stored in:
* *
* ```js * ```js
* const browser = await chromium.launch({ traceDir: 'traces' }); * const browser = await chromium.launch();
* const context = await browser.newContext(); * const context = await browser.newContext();
* await context.tracing.start({ name: 'trace', screenshots: true, snapshots: true }); * await context.tracing.start({ screenshots: true, snapshots: true });
* const page = await context.newPage(); * const page = await context.newPage();
* await page.goto('https://playwright.dev'); * await page.goto('https://playwright.dev');
* await context.tracing.stop({ path: 'trace.zip' }); * await context.tracing.stop({ path: 'trace.zip' });
@ -10618,19 +10618,18 @@ export interface Tracing {
* Start tracing. * Start tracing.
* *
* ```js * ```js
* await context.tracing.start({ name: 'trace', screenshots: true, snapshots: true }); * await context.tracing.start({ screenshots: true, snapshots: true });
* const page = await context.newPage(); * const page = await context.newPage();
* await page.goto('https://playwright.dev'); * await page.goto('https://playwright.dev');
* await context.tracing.stop(); * await context.tracing.stop({ path: 'trace.zip' });
* await context.tracing.export('trace.zip');
* ``` * ```
* *
* @param options * @param options
*/ */
start(options?: { start(options?: {
/** /**
* If specified, the trace is going to be saved into the file with the given name inside the `traceDir` folder specified in * If specified, the trace is going to be saved into the file with the given name inside the `tracesDir` folder specified
* [browserType.launch([options])](https://playwright.dev/docs/api/class-browsertype#browsertypelaunchoptions). * in [browserType.launch([options])](https://playwright.dev/docs/api/class-browsertype#browsertypelaunchoptions).
*/ */
name?: string; name?: string;
@ -11343,7 +11342,7 @@ export interface LaunchOptions {
/** /**
* If specified, traces are saved into this directory. * If specified, traces are saved into this directory.
*/ */
traceDir?: string; tracesDir?: string;
} }
export interface ConnectOverCDPOptions { export interface ConnectOverCDPOptions {