Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,11 @@ 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
disable timeout.

## browser-option-artifactsdir
- `artifactsDir` <[path]>

If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the browser closes.

## browser-option-tracesdir
- `tracesDir` <[path]>

Expand All @@ -1164,6 +1169,7 @@ Slows down Playwright operations by the specified amount of milliseconds. Useful

## shared-browser-options-list-v1.8
- %%-browser-option-args-%%
- %%-browser-option-artifactsdir-%%
- %%-browser-option-channel-%%
- %%-browser-option-chromiumsandbox-%%
- %%-browser-option-downloadspath-%%
Expand Down
21 changes: 21 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15302,6 +15302,13 @@ export interface BrowserType<Unused = {}> {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
Expand Down Expand Up @@ -15826,6 +15833,13 @@ export interface BrowserType<Unused = {}> {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* Browser distribution channel.
*
Expand Down Expand Up @@ -22228,6 +22242,13 @@ export interface LaunchOptions {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* Browser distribution channel.
*
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/cli/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export type RunServerOptions = {
maxConnections?: number,
browserProxyMode?: 'client' | 'tether',
ownedByTetherClient?: boolean,
artifactsDir?: string,
};

export async function runServer(options: RunServerOptions) {
Expand All @@ -78,8 +79,9 @@ export async function runServer(options: RunServerOptions) {
path = '/',
maxConnections = Infinity,
extension,
artifactsDir,
} = options;
const server = new PlaywrightServer({ mode: extension ? 'extension' : 'default', path, maxConnections });
const server = new PlaywrightServer({ mode: extension ? 'extension' : 'default', path, maxConnections, artifactsDir });
const wsEndpoint = await server.listen(port, host);
process.on('exit', () => server.close().catch(console.error));
console.log('Listening on ' + wsEndpoint);
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/cli/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,15 @@ program
.option('--path <path>', 'Endpoint Path', '/')
.option('--max-clients <maxClients>', 'Maximum clients')
.option('--mode <mode>', 'Server mode, either "default" or "extension"')
.option('--artifacts-dir <artifactsDir>', 'Artifacts directory')
.action(function(options) {
runServer({
port: options.port ? +options.port : undefined,
host: options.host,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
extension: options.mode === 'extension' || !!process.env.PW_EXTENSION_MODE,
artifactsDir: options.artifactsDir,
}).catch(logErrorAndExit);
});

Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ scheme.BrowserTypeLaunchParams = tObject({
})),
downloadsPath: tOptional(tString),
tracesDir: tOptional(tString),
artifactsDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean),
firefoxUserPrefs: tOptional(tAny),
cdpPort: tOptional(tInt),
Expand Down Expand Up @@ -555,6 +556,7 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
})),
downloadsPath: tOptional(tString),
tracesDir: tOptional(tString),
artifactsDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean),
firefoxUserPrefs: tOptional(tAny),
cdpPort: tOptional(tInt),
Expand Down
5 changes: 5 additions & 0 deletions packages/playwright-core/src/remote/playwrightServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ServerOptions = {
preLaunchedBrowser?: Browser;
preLaunchedAndroidDevice?: AndroidDevice;
preLaunchedSocksProxy?: SocksProxy;
artifactsDir?: string;
};

export class PlaywrightServer {
Expand Down Expand Up @@ -102,6 +103,8 @@ export class PlaywrightServer {
launchOptions.timeout = DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT;
} catch (e) {
}
if (this._options.artifactsDir)
launchOptions.artifactsDir = this._options.artifactsDir;

const isExtension = this._options.mode === 'extension';
const allowFSPaths = isExtension;
Expand Down Expand Up @@ -367,6 +370,7 @@ function filterLaunchOptions(options: LaunchOptionsWithTimeout, allowFSPaths: bo
slowMo: options.slowMo,
executablePath: (isUnderTest() || allowFSPaths) ? options.executablePath : undefined,
downloadsPath: allowFSPaths ? options.downloadsPath : undefined,
artifactsDir: (isUnderTest() || allowFSPaths) ? options.artifactsDir : undefined,
};
}

Expand All @@ -382,4 +386,5 @@ const optionsThatAllowBrowserReuse: (keyof LaunchOptionsWithTimeout)[] = [
'headless',
'timeout',
'tracesDir',
'artifactsDir',
];
9 changes: 7 additions & 2 deletions packages/playwright-core/src/server/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,13 @@ export abstract class BrowserType extends SdkObject {
executablePath = null,
} = options;
const tempDirectories: string[] = [];
const artifactsDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-artifacts-'));
tempDirectories.push(artifactsDir);
let artifactsDir: string;
if (options.artifactsDir) {
artifactsDir = options.artifactsDir;
} else {
artifactsDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'playwright-artifacts-'));
tempDirectories.push(artifactsDir);
}

if (userDataDir) {
assert(path.isAbsolute(userDataDir), 'userDataDir must be an absolute path');
Expand Down
21 changes: 21 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15302,6 +15302,13 @@ export interface BrowserType<Unused = {}> {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto),
* [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route),
Expand Down Expand Up @@ -15826,6 +15833,13 @@ export interface BrowserType<Unused = {}> {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* Browser distribution channel.
*
Expand Down Expand Up @@ -22228,6 +22242,13 @@ export interface LaunchOptions {
*/
args?: Array<string>;

/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory
* is not cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the
* browser closes.
*/
artifactsDir?: string;

/**
* Browser distribution channel.
*
Expand Down
1 change: 1 addition & 0 deletions packages/playwright/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
handleSIGINT: false,
...launchOptions,
tracesDir: tracing().tracesDir(),
artifactsDir: tracing().artifactsDir(),
};
if (headless !== undefined)
options.headless = headless;
Expand Down
4 changes: 0 additions & 4 deletions packages/playwright/src/runner/workerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ export class WorkerHost extends ProcessHost {
};
}

artifactsDir() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did this change?

return this._params.artifactsDir;
}

async start() {
await fs.promises.mkdir(this._params.artifactsDir, { recursive: true });
return await this.startRunner(this._params, {
Expand Down
4 changes: 4 additions & 0 deletions packages/protocol/src/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,7 @@ export type BrowserTypeLaunchParams = {
},
downloadsPath?: string,
tracesDir?: string,
artifactsDir?: string,
chromiumSandbox?: boolean,
firefoxUserPrefs?: any,
cdpPort?: number,
Expand All @@ -940,6 +941,7 @@ export type BrowserTypeLaunchOptions = {
},
downloadsPath?: string,
tracesDir?: string,
artifactsDir?: string,
chromiumSandbox?: boolean,
firefoxUserPrefs?: any,
cdpPort?: number,
Expand Down Expand Up @@ -969,6 +971,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
},
downloadsPath?: string,
tracesDir?: string,
artifactsDir?: string,
chromiumSandbox?: boolean,
firefoxUserPrefs?: any,
cdpPort?: number,
Expand Down Expand Up @@ -1051,6 +1054,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
},
downloadsPath?: string,
tracesDir?: string,
artifactsDir?: string,
chromiumSandbox?: boolean,
firefoxUserPrefs?: any,
cdpPort?: number,
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ LaunchOptions:
password: string?
downloadsPath: string?
tracesDir: string?
artifactsDir: string?
chromiumSandbox: boolean?
firefoxUserPrefs: json?
cdpPort: int?
Expand Down
4 changes: 2 additions & 2 deletions tests/config/browserTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type BrowserTestWorkerFixtures = PageWorkerFixtures & {
};

interface StartRemoteServer {
(kind: 'run-server' | 'launchServer'): Promise<PlaywrightServer>;
(kind: 'run-server' | 'launchServer', options?: RemoteServerOptions): Promise<PlaywrightServer>;
(kind: 'launchServer', options?: RemoteServerOptions): Promise<RemoteServer>;
}

Expand Down Expand Up @@ -163,7 +163,7 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
server = remoteServer;
} else {
const runServer = new RunServer();
await runServer.start(childProcess);
await runServer.start(childProcess, { artifactsDir: options?.artifactsDir });
server = runServer;
}
return server;
Expand Down
11 changes: 8 additions & 3 deletions tests/config/remoteServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ export class RunServer implements PlaywrightServer {
private _process!: TestChildProcess;
_wsEndpoint!: string;

async start(childProcess: CommonFixtures['childProcess'], mode?: 'extension' | 'default', env?: NodeJS.ProcessEnv) {
async start(childProcess: CommonFixtures['childProcess'], options?: { mode?: 'extension' | 'default', env?: NodeJS.ProcessEnv, artifactsDir?: string }) {
const command = ['node', path.join(__dirname, '..', '..', 'packages', 'playwright-core', 'cli.js'), 'run-server'];
if (mode === 'extension')
if (options?.mode === 'extension')
command.push('--mode=extension');
if (options?.artifactsDir)
command.push(`--artifacts-dir=${options.artifactsDir}`);
this._process = childProcess({
command,
env: {
...process.env,
...env,
...options?.env,
},
});

Expand Down Expand Up @@ -69,6 +71,7 @@ export type RemoteServerOptions = {
url?: string;
startStopAndRunHttp?: boolean;
sharedBrowser?: boolean;
artifactsDir?: string;
};

export class RemoteServer implements PlaywrightServer {
Expand Down Expand Up @@ -97,6 +100,8 @@ export class RemoteServer implements PlaywrightServer {
};
if (remoteServerOptions.sharedBrowser)
(launchOptions as any)._sharedBrowser = true;
if (remoteServerOptions.artifactsDir)
launchOptions.artifactsDir = remoteServerOptions.artifactsDir;
const options = {
browserTypeName: browserType.name(),
channel,
Expand Down
23 changes: 22 additions & 1 deletion tests/library/browsertype-connect.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,27 @@ for (const kind of ['launchServer', 'run-server'] as const) {
expect(error.message).toContain('Path is not available when connecting remotely. Use saveAs() to save a local copy.');
});

test('should save videos to artifactsDir', async ({ connect, startRemoteServer }, testInfo) => {
const artifactsDir = testInfo.outputPath('artifacts');
const remoteServer = await startRemoteServer(kind, { artifactsDir });
const browser = await connect(remoteServer.wsEndpoint());
const localDir = testInfo.outputPath('random-dir');
const context = await browser.newContext({
recordVideo: { dir: localDir, size: { width: 320, height: 240 } },
});
const page = await context.newPage();
await page.evaluate(() => document.body.style.backgroundColor = 'red');
await rafraf(page, 100);
await context.close();

const savedAsPath = testInfo.outputPath('my-video.webm');
await page.video().saveAs(savedAsPath);
expect(fs.existsSync(savedAsPath)).toBeTruthy();
expect(fs.existsSync(localDir)).toBeFalsy();

await browser.close();
});

test('should be able to connect 20 times to a single server without warnings', async ({ connect, startRemoteServer, platform }) => {
test.skip(platform !== 'linux', 'Testing non-platform specific code');

Expand Down Expand Up @@ -1085,7 +1106,7 @@ test.describe('launchServer only', () => {

test('should refuse connecting when versions do not match', async ({ connect, childProcess }) => {
const server = new RunServer();
await server.start(childProcess, 'default', { PW_VERSION_OVERRIDE: '1.2.3' });
await server.start(childProcess, { mode: 'default', env: { PW_VERSION_OVERRIDE: '1.2.3' } });
const error = await connect(server.wsEndpoint()).catch(e => e);
await server.close();
expect(error.message).toContain('Playwright version mismatch');
Expand Down
19 changes: 18 additions & 1 deletion tests/library/download.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ it.describe('download event', () => {
});

it('should delete downloads on browser gone', async ({ server, browserType }) => {
const browser = await browserType.launch();
const browser = await browserType.launch({ artifactsDir: undefined });
const page = await browser.newPage();
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [download1] = await Promise.all([
Expand All @@ -396,6 +396,23 @@ it.describe('download event', () => {
expect(fs.existsSync(path.join(path1, '..'))).toBeFalsy();
});

it('should save downloads to artifactsDir', async ({ server, browserType }, testInfo) => {
const artifactsDir = testInfo.outputPath('artifacts');
const browser = await browserType.launch({ artifactsDir });
const page = await browser.newPage();
await page.setContent(`<a href="${server.PREFIX}/download">download</a>`);
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('a')
]);
const downloadPath = await download.path();
expect(downloadPath.startsWith(artifactsDir)).toBeTruthy();
expect(fs.existsSync(downloadPath)).toBeTruthy();
await browser.close();
// User-provided artifactsDir should not be cleaned up.
expect(fs.existsSync(artifactsDir)).toBeTruthy();
});

it('should close the context without awaiting the failed download', async ({ browser, server, httpsServer, browserName, headless }, testInfo) => {
it.skip(browserName !== 'chromium', 'Only Chromium downloads on alt-click');

Expand Down
2 changes: 1 addition & 1 deletion tests/playwright-test/playwright.reuse.browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const test = baseTest.extend<{ runServer: () => Promise<PlaywrightServer> }>({
let server: PlaywrightServer | undefined;
await use(async () => {
const runServer = new RunServer();
await runServer.start(childProcess, 'extension');
await runServer.start(childProcess, { mode: 'extension' });
server = runServer;
return server;
});
Expand Down
Loading