From 74d548488b6242cc359bbac7a04e42c49076ee4b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 26 Mar 2026 11:11:02 -0700 Subject: [PATCH] feat(electron): add artifactsDir option to electron.launch() Similar to browserType.launch(), allow specifying an artifacts directory for Electron so that downloads, traces, videos etc. are saved to a user-controlled location that is not cleaned up on close. --- docs/src/api/class-electron.md | 3 +++ packages/playwright-client/types/types.d.ts | 7 +++++ .../playwright-core/src/client/electron.ts | 1 + .../playwright-core/src/protocol/validator.ts | 1 + .../src/server/electron/electron.ts | 11 ++++++-- packages/playwright-core/types/types.d.ts | 7 +++++ packages/protocol/src/channels.d.ts | 2 ++ packages/protocol/src/protocol.yml | 1 + tests/electron/electron-app.spec.ts | 26 +++++++++++++++++++ 9 files changed, 57 insertions(+), 2 deletions(-) diff --git a/docs/src/api/class-electron.md b/docs/src/api/class-electron.md index 90c41de84e810..1df55a214a252 100644 --- a/docs/src/api/class-electron.md +++ b/docs/src/api/class-electron.md @@ -140,5 +140,8 @@ Maximum time in milliseconds to wait for the application to start. Defaults to ` ### option: Electron.launch.tracesDir = %%-browser-option-tracesdir-%% * since: v1.36 +### option: Electron.launch.artifactsDir = %%-browser-option-artifactsdir-%% +* since: v1.59 + ### option: Electron.launch.chromiumSandbox = %%-browser-option-chromiumsandbox-%% * since: v1.59 diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 190426946d921..f5a08b52df02c 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -19814,6 +19814,13 @@ export interface Electron { */ args?: Array; + /** + * 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; + /** * Toggles bypassing page's Content-Security-Policy. Defaults to `false`. */ diff --git a/packages/playwright-core/src/client/electron.ts b/packages/playwright-core/src/client/electron.ts index ed6505e24dc3b..2399aa61ff1ce 100644 --- a/packages/playwright-core/src/client/electron.ts +++ b/packages/playwright-core/src/client/electron.ts @@ -61,6 +61,7 @@ export class Electron extends ChannelOwner implements ...await prepareBrowserContextParams(this._platform, options), env: envObjectToArray(options.env ? options.env : this._platform.env), tracesDir: options.tracesDir, + artifactsDir: options.artifactsDir, timeout: new TimeoutSettings(this._platform).launchTimeout(options), }; const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index f4b82348d0728..2096f53d978c1 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -2689,6 +2689,7 @@ scheme.ElectronLaunchParams = tObject({ strictSelectors: tOptional(tBoolean), timezoneId: tOptional(tString), tracesDir: tOptional(tString), + artifactsDir: tOptional(tString), selectorEngines: tOptional(tArray(tType('SelectorEngine'))), testIdAttributeName: tOptional(tString), }); diff --git a/packages/playwright-core/src/server/electron/electron.ts b/packages/playwright-core/src/server/electron/electron.ts index 9df97f097a9f4..173ca898d07ea 100644 --- a/packages/playwright-core/src/server/electron/electron.ts +++ b/packages/playwright-core/src/server/electron/electron.ts @@ -164,7 +164,14 @@ export class Electron extends SdkObject { electronArguments.unshift('--no-sandbox'); } - const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER)); + let artifactsDir: string; + const tempDirectories: string[] = []; + if (options.artifactsDir) { + artifactsDir = options.artifactsDir; + } else { + artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER)); + tempDirectories.push(artifactsDir); + } const browserLogsCollector = new RecentLogsCollector(); const env = options.env ? envArrayToObject(options.env) : process.env; @@ -216,7 +223,7 @@ export class Electron extends SdkObject { shell, stdio: 'pipe', cwd: options.cwd, - tempDirectories: [artifactsDir], + tempDirectories, attemptToGracefullyClose: () => app!.close(), handleSIGINT: true, handleSIGTERM: true, diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 190426946d921..f5a08b52df02c 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -19814,6 +19814,13 @@ export interface Electron { */ args?: Array; + /** + * 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; + /** * Toggles bypassing page's Content-Security-Policy. Defaults to `false`. */ diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index 429702549667b..811a2d79a4d95 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -4686,6 +4686,7 @@ export type ElectronLaunchParams = { strictSelectors?: boolean, timezoneId?: string, tracesDir?: string, + artifactsDir?: string, selectorEngines?: SelectorEngine[], testIdAttributeName?: string, }; @@ -4723,6 +4724,7 @@ export type ElectronLaunchOptions = { strictSelectors?: boolean, timezoneId?: string, tracesDir?: string, + artifactsDir?: string, selectorEngines?: SelectorEngine[], testIdAttributeName?: string, }; diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index e732a24c8dcbd..cf7c671f23a5a 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -4184,6 +4184,7 @@ Electron: strictSelectors: boolean? timezoneId: string? tracesDir: string? + artifactsDir: string? selectorEngines: type: array? items: SelectorEngine diff --git a/tests/electron/electron-app.spec.ts b/tests/electron/electron-app.spec.ts index a4393b53d146b..7fa8f8950af86 100644 --- a/tests/electron/electron-app.spec.ts +++ b/tests/electron/electron-app.spec.ts @@ -339,3 +339,29 @@ test('should report downloads', async ({ launchElectronApp, electronMajorVersion expect(fs.readFileSync(path).toString()).toBe('Hello world'); await app.close(); }); + +test('should save downloads to artifactsDir', async ({ launchElectronApp, electronMajorVersion, server }, testInfo) => { + server.setRoute('/download', (req, res) => { + res.setHeader('Content-Type', 'application/octet-stream'); + res.setHeader('Content-Disposition', 'attachment'); + res.end(`Hello world`); + }); + + const artifactsDir = testInfo.outputPath('artifacts'); + const app = await launchElectronApp('electron-window-app.js', [], { + acceptDownloads: true, + artifactsDir, + }); + const window = await app.firstWindow(); + await window.setContent(`download`); + const [download] = await Promise.all([ + window.waitForEvent('download'), + window.click('a') + ]); + const downloadPath = await download.path(); + expect(downloadPath.startsWith(artifactsDir)).toBeTruthy(); + expect(fs.existsSync(downloadPath)).toBeTruthy(); + await app.close(); + // User-provided artifactsDir should not be cleaned up. + expect(fs.existsSync(artifactsDir)).toBeTruthy(); +});