feat: add --download-path option (#536)
* feat: add --download-path option Adds a `--download-path` flag (and `AGENT_BROWSER_DOWNLOAD_PATH` env / `downloadPath` config key) to set a default download directory for browser downloads. Without this, Playwright stores downloads in a temp directory that is deleted when the browser closes. The new option passes through to Playwright's `downloadsPath` on `launch()` and `launchPersistentContext()`. Fixes #507 * improvements * fixes * fixes
This commit is contained in:
+39
-1
@@ -16,7 +16,7 @@ import {
|
||||
} from 'playwright-core';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
import { existsSync, mkdirSync, rmSync, readFileSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, rmSync, readFileSync, statSync } from 'node:fs';
|
||||
import { writeFile, mkdir } from 'node:fs/promises';
|
||||
import type { LaunchCommand, TraceEvent } from './types.js';
|
||||
import { type RefMap, type EnhancedSnapshot, getEnhancedSnapshot, parseRef } from './snapshot.js';
|
||||
@@ -116,6 +116,7 @@ export class BrowserManager {
|
||||
private lastSnapshot: string = '';
|
||||
private scopedHeaderRoutes: Map<string, (route: Route) => Promise<void>> = new Map();
|
||||
private colorScheme: 'light' | 'dark' | 'no-preference' | null = null;
|
||||
private downloadPath: string | null = null;
|
||||
|
||||
/**
|
||||
* Set the persistent color scheme preference.
|
||||
@@ -1173,6 +1174,17 @@ export class BrowserManager {
|
||||
this.colorScheme = options.colorScheme;
|
||||
}
|
||||
|
||||
if (options.downloadPath) {
|
||||
this.downloadPath = options.downloadPath;
|
||||
}
|
||||
|
||||
if (this.downloadPath && (cdpEndpoint || options.autoConnect)) {
|
||||
const warning =
|
||||
"--download-path is ignored when connecting via CDP or auto-connect (downloads use the remote browser's configuration)";
|
||||
this.launchWarnings.push(warning);
|
||||
console.error(`[WARN] ${warning}`);
|
||||
}
|
||||
|
||||
if (cdpEndpoint) {
|
||||
await this.connectViaCDP(cdpEndpoint);
|
||||
return;
|
||||
@@ -1186,6 +1198,12 @@ export class BrowserManager {
|
||||
// Cloud browser providers require explicit opt-in via -p flag or AGENT_BROWSER_PROVIDER env var
|
||||
// -p flag takes precedence over env var
|
||||
const provider = options.provider ?? process.env.AGENT_BROWSER_PROVIDER;
|
||||
if (this.downloadPath && provider) {
|
||||
const warning =
|
||||
"--download-path is ignored when using a cloud provider (downloads use the remote browser's configuration)";
|
||||
this.launchWarnings.push(warning);
|
||||
console.error(`[WARN] ${warning}`);
|
||||
}
|
||||
if (provider === 'browserbase') {
|
||||
await this.connectToBrowserbase();
|
||||
return;
|
||||
@@ -1201,6 +1219,23 @@ export class BrowserManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.downloadPath) {
|
||||
const resolved = path.resolve(this.downloadPath);
|
||||
const stat = statSync(resolved, { throwIfNoEntry: false });
|
||||
if (stat && !stat.isDirectory()) {
|
||||
throw new Error(`Download path is not a directory: ${resolved}`);
|
||||
}
|
||||
if (!stat) {
|
||||
try {
|
||||
mkdirSync(resolved, { recursive: true });
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
throw new Error(`Cannot create download directory '${resolved}': ${msg}`);
|
||||
}
|
||||
}
|
||||
this.downloadPath = resolved;
|
||||
}
|
||||
|
||||
const browserType = options.browser ?? 'chromium';
|
||||
if (hasExtensions && browserType !== 'chromium') {
|
||||
throw new Error('Extensions are only supported in Chromium');
|
||||
@@ -1258,6 +1293,7 @@ export class BrowserManager {
|
||||
...(options.proxy && { proxy: options.proxy }),
|
||||
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
|
||||
...(this.colorScheme && { colorScheme: this.colorScheme }),
|
||||
...(this.downloadPath && { downloadsPath: this.downloadPath }),
|
||||
}
|
||||
);
|
||||
this.isPersistentContext = true;
|
||||
@@ -1275,6 +1311,7 @@ export class BrowserManager {
|
||||
...(options.proxy && { proxy: options.proxy }),
|
||||
ignoreHTTPSErrors: options.ignoreHTTPSErrors ?? false,
|
||||
...(this.colorScheme && { colorScheme: this.colorScheme }),
|
||||
...(this.downloadPath && { downloadsPath: this.downloadPath }),
|
||||
});
|
||||
this.isPersistentContext = true;
|
||||
} else {
|
||||
@@ -1283,6 +1320,7 @@ export class BrowserManager {
|
||||
headless: options.headless ?? true,
|
||||
executablePath: options.executablePath,
|
||||
args: baseArgs,
|
||||
...(this.downloadPath && { downloadsPath: this.downloadPath }),
|
||||
});
|
||||
this.cdpEndpoint = null;
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ const launchSchema = baseCommandSchema.extend({
|
||||
ignoreHTTPSErrors: z.boolean().optional(),
|
||||
allowFileAccess: z.boolean().optional(),
|
||||
colorScheme: z.enum(['light', 'dark', 'no-preference']).optional(),
|
||||
downloadPath: z.string().optional(),
|
||||
profile: z.string().optional(),
|
||||
storageState: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface LaunchCommand extends BaseCommand {
|
||||
ignoreHTTPSErrors?: boolean;
|
||||
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
||||
colorScheme?: 'light' | 'dark' | 'no-preference'; // Persistent color scheme override
|
||||
downloadPath?: string; // Directory for browser downloads (Playwright's downloadsPath)
|
||||
// Auto-load state file for session persistence
|
||||
autoStateFilePath?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user