feat: add --allow-file-access flag for file:// URL support (#375)
* feat: add --allow-file-access flag for file:// URL support Adds the ability to open and interact with local files using file:// URLs. This enables use cases like viewing local PDFs, testing local HTML files, and allowing JavaScript to access other local files via XHR. The flag adds Chromium's --allow-file-access-from-files and --allow-file-access launch arguments. Only supported in Chromium browsers. Fixes #345 * fix: add cli_allow_file_access tracking to prevent spurious warning When --allow-file-access is set via AGENT_BROWSER_ALLOW_FILE_ACCESS env var (not CLI), don't warn about the flag being ignored when daemon is already running.
This commit is contained in:
@@ -339,6 +339,7 @@ The `-C` flag is useful for modern web apps that use custom clickable elements (
|
|||||||
| `--headed` | Show browser window (not headless) |
|
| `--headed` | Show browser window (not headless) |
|
||||||
| `--cdp <port>` | Connect via Chrome DevTools Protocol |
|
| `--cdp <port>` | Connect via Chrome DevTools Protocol |
|
||||||
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
|
| `--ignore-https-errors` | Ignore HTTPS certificate errors (useful for self-signed certs) |
|
||||||
|
| `--allow-file-access` | Allow file:// URLs to access local files (Chromium only) |
|
||||||
| `--debug` | Debug output |
|
| `--debug` | Debug output |
|
||||||
|
|
||||||
## Selectors
|
## Selectors
|
||||||
@@ -496,6 +497,27 @@ export async function handler() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Local Files
|
||||||
|
|
||||||
|
Open and interact with local files (PDFs, HTML, etc.) using `file://` URLs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable file access (required for JavaScript to access local files)
|
||||||
|
agent-browser --allow-file-access open file:///path/to/document.pdf
|
||||||
|
agent-browser --allow-file-access open file:///path/to/page.html
|
||||||
|
|
||||||
|
# Take screenshot of a local PDF
|
||||||
|
agent-browser --allow-file-access open file:///Users/me/report.pdf
|
||||||
|
agent-browser screenshot report.png
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--allow-file-access` flag adds Chromium flags (`--allow-file-access-from-files`, `--allow-file-access`) that allow `file://` URLs to:
|
||||||
|
- Load and render local files
|
||||||
|
- Access other local files via JavaScript (XHR, fetch)
|
||||||
|
- Load local resources (images, scripts, stylesheets)
|
||||||
|
|
||||||
|
**Note:** This flag only works with Chromium. For security, it's disabled by default.
|
||||||
|
|
||||||
## CDP Mode
|
## CDP Mode
|
||||||
|
|
||||||
Connect to an existing browser via Chrome DevTools Protocol:
|
Connect to an existing browser via Chrome DevTools Protocol:
|
||||||
|
|||||||
@@ -1427,6 +1427,7 @@ mod tests {
|
|||||||
user_agent: None,
|
user_agent: None,
|
||||||
provider: None,
|
provider: None,
|
||||||
ignore_https_errors: false,
|
ignore_https_errors: false,
|
||||||
|
allow_file_access: false,
|
||||||
device: None,
|
device: None,
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
cli_extensions: false,
|
cli_extensions: false,
|
||||||
@@ -1436,6 +1437,7 @@ mod tests {
|
|||||||
cli_user_agent: false,
|
cli_user_agent: false,
|
||||||
cli_proxy: false,
|
cli_proxy: false,
|
||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
|
cli_allow_file_access: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,6 +213,7 @@ pub fn ensure_daemon(
|
|||||||
proxy: Option<&str>,
|
proxy: Option<&str>,
|
||||||
proxy_bypass: Option<&str>,
|
proxy_bypass: Option<&str>,
|
||||||
ignore_https_errors: bool,
|
ignore_https_errors: bool,
|
||||||
|
allow_file_access: bool,
|
||||||
profile: Option<&str>,
|
profile: Option<&str>,
|
||||||
state: Option<&str>,
|
state: Option<&str>,
|
||||||
provider: Option<&str>,
|
provider: Option<&str>,
|
||||||
@@ -337,6 +338,10 @@ pub fn ensure_daemon(
|
|||||||
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
|
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allow_file_access {
|
||||||
|
cmd.env("AGENT_BROWSER_ALLOW_FILE_ACCESS", "1");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(prof) = profile {
|
if let Some(prof) = profile {
|
||||||
cmd.env("AGENT_BROWSER_PROFILE", prof);
|
cmd.env("AGENT_BROWSER_PROFILE", prof);
|
||||||
}
|
}
|
||||||
@@ -412,6 +417,10 @@ pub fn ensure_daemon(
|
|||||||
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
|
cmd.env("AGENT_BROWSER_IGNORE_HTTPS_ERRORS", "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if allow_file_access {
|
||||||
|
cmd.env("AGENT_BROWSER_ALLOW_FILE_ACCESS", "1");
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(prof) = profile {
|
if let Some(prof) = profile {
|
||||||
cmd.env("AGENT_BROWSER_PROFILE", prof);
|
cmd.env("AGENT_BROWSER_PROFILE", prof);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub struct Flags {
|
|||||||
pub user_agent: Option<String>,
|
pub user_agent: Option<String>,
|
||||||
pub provider: Option<String>,
|
pub provider: Option<String>,
|
||||||
pub ignore_https_errors: bool,
|
pub ignore_https_errors: bool,
|
||||||
|
pub allow_file_access: bool,
|
||||||
pub device: Option<String>,
|
pub device: Option<String>,
|
||||||
|
|
||||||
// Track which launch-time options were explicitly passed via CLI
|
// Track which launch-time options were explicitly passed via CLI
|
||||||
@@ -30,6 +31,7 @@ pub struct Flags {
|
|||||||
pub cli_user_agent: bool,
|
pub cli_user_agent: bool,
|
||||||
pub cli_proxy: bool,
|
pub cli_proxy: bool,
|
||||||
pub cli_proxy_bypass: bool,
|
pub cli_proxy_bypass: bool,
|
||||||
|
pub cli_allow_file_access: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_flags(args: &[String]) -> Flags {
|
pub fn parse_flags(args: &[String]) -> Flags {
|
||||||
@@ -61,6 +63,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(),
|
user_agent: env::var("AGENT_BROWSER_USER_AGENT").ok(),
|
||||||
provider: env::var("AGENT_BROWSER_PROVIDER").ok(),
|
provider: env::var("AGENT_BROWSER_PROVIDER").ok(),
|
||||||
ignore_https_errors: false,
|
ignore_https_errors: false,
|
||||||
|
allow_file_access: env::var("AGENT_BROWSER_ALLOW_FILE_ACCESS").is_ok(),
|
||||||
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(),
|
device: env::var("AGENT_BROWSER_IOS_DEVICE").ok(),
|
||||||
// Track CLI-passed flags (default false, set to true when flag is passed)
|
// Track CLI-passed flags (default false, set to true when flag is passed)
|
||||||
cli_executable_path: false,
|
cli_executable_path: false,
|
||||||
@@ -71,6 +74,7 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
cli_user_agent: false,
|
cli_user_agent: false,
|
||||||
cli_proxy: false,
|
cli_proxy: false,
|
||||||
cli_proxy_bypass: false,
|
cli_proxy_bypass: false,
|
||||||
|
cli_allow_file_access: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -161,6 +165,10 @@ pub fn parse_flags(args: &[String]) -> Flags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"--ignore-https-errors" => flags.ignore_https_errors = true,
|
"--ignore-https-errors" => flags.ignore_https_errors = true,
|
||||||
|
"--allow-file-access" => {
|
||||||
|
flags.allow_file_access = true;
|
||||||
|
flags.cli_allow_file_access = true;
|
||||||
|
}
|
||||||
"--device" => {
|
"--device" => {
|
||||||
if let Some(d) = args.get(i + 1) {
|
if let Some(d) = args.get(i + 1) {
|
||||||
flags.device = Some(d.clone());
|
flags.device = Some(d.clone());
|
||||||
@@ -185,6 +193,7 @@ pub fn clean_args(args: &[String]) -> Vec<String> {
|
|||||||
"--headed",
|
"--headed",
|
||||||
"--debug",
|
"--debug",
|
||||||
"--ignore-https-errors",
|
"--ignore-https-errors",
|
||||||
|
"--allow-file-access",
|
||||||
];
|
];
|
||||||
// Global flags that take a value (need to skip the next arg too)
|
// Global flags that take a value (need to skip the next arg too)
|
||||||
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
const GLOBAL_FLAGS_WITH_VALUE: &[&str] = &[
|
||||||
|
|||||||
+8
-1
@@ -205,6 +205,7 @@ fn main() {
|
|||||||
flags.proxy.as_deref(),
|
flags.proxy.as_deref(),
|
||||||
flags.proxy_bypass.as_deref(),
|
flags.proxy_bypass.as_deref(),
|
||||||
flags.ignore_https_errors,
|
flags.ignore_https_errors,
|
||||||
|
flags.allow_file_access,
|
||||||
flags.profile.as_deref(),
|
flags.profile.as_deref(),
|
||||||
flags.state.as_deref(),
|
flags.state.as_deref(),
|
||||||
flags.provider.as_deref(),
|
flags.provider.as_deref(),
|
||||||
@@ -267,6 +268,7 @@ fn main() {
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
flags.ignore_https_errors.then(|| "--ignore-https-errors"),
|
flags.ignore_https_errors.then(|| "--ignore-https-errors"),
|
||||||
|
flags.cli_allow_file_access.then(|| "--allow-file-access"),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -417,7 +419,8 @@ fn main() {
|
|||||||
|| flags.state.is_some()
|
|| flags.state.is_some()
|
||||||
|| flags.proxy.is_some()
|
|| flags.proxy.is_some()
|
||||||
|| flags.args.is_some()
|
|| flags.args.is_some()
|
||||||
|| flags.user_agent.is_some())
|
|| flags.user_agent.is_some()
|
||||||
|
|| flags.allow_file_access)
|
||||||
&& flags.cdp.is_none()
|
&& flags.cdp.is_none()
|
||||||
&& flags.provider.is_none()
|
&& flags.provider.is_none()
|
||||||
{
|
{
|
||||||
@@ -470,6 +473,10 @@ fn main() {
|
|||||||
launch_cmd["ignoreHTTPSErrors"] = json!(true);
|
launch_cmd["ignoreHTTPSErrors"] = json!(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.allow_file_access {
|
||||||
|
launch_cmd["allowFileAccess"] = json!(true);
|
||||||
|
}
|
||||||
|
|
||||||
match send_command(launch_cmd, &flags.session) {
|
match send_command(launch_cmd, &flags.session) {
|
||||||
Ok(resp) if !resp.success => {
|
Ok(resp) if !resp.success => {
|
||||||
// Launch command failed (e.g., invalid state file, profile error)
|
// Launch command failed (e.g., invalid state file, profile error)
|
||||||
|
|||||||
@@ -1771,6 +1771,7 @@ Options:
|
|||||||
--proxy-bypass <hosts> Bypass proxy for these hosts (or AGENT_BROWSER_PROXY_BYPASS)
|
--proxy-bypass <hosts> Bypass proxy for these hosts (or AGENT_BROWSER_PROXY_BYPASS)
|
||||||
e.g., --proxy-bypass "localhost,*.internal.com"
|
e.g., --proxy-bypass "localhost,*.internal.com"
|
||||||
--ignore-https-errors Ignore HTTPS certificate errors
|
--ignore-https-errors Ignore HTTPS certificate errors
|
||||||
|
--allow-file-access Allow file:// URLs to access local files (Chromium only)
|
||||||
-p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse
|
-p, --provider <name> Browser provider: ios, browserbase, kernel, browseruse
|
||||||
--device <name> iOS device name (e.g., "iPhone 15 Pro")
|
--device <name> iOS device name (e.g., "iPhone 15 Pro")
|
||||||
--json JSON output
|
--json JSON output
|
||||||
|
|||||||
@@ -120,6 +120,30 @@ agent-browser state load <path> # Load auth state`} />
|
|||||||
<CodeBlock code={`agent-browser back # Go back
|
<CodeBlock code={`agent-browser back # Go back
|
||||||
agent-browser forward # Go forward
|
agent-browser forward # Go forward
|
||||||
agent-browser reload # Reload page`} />
|
agent-browser reload # Reload page`} />
|
||||||
|
|
||||||
|
<h2>Global options</h2>
|
||||||
|
<CodeBlock code={`--session <name> # Isolated browser session
|
||||||
|
--profile <path> # Persistent browser profile directory
|
||||||
|
--headed # Show browser window (not headless)
|
||||||
|
--cdp <port> # Connect via Chrome DevTools Protocol
|
||||||
|
--executable-path <path> # Custom browser executable
|
||||||
|
--args <args> # Browser launch args (comma separated)
|
||||||
|
--user-agent <ua> # Custom User-Agent string
|
||||||
|
--proxy <url> # Proxy server URL
|
||||||
|
--headers <json> # HTTP headers scoped to URL's origin
|
||||||
|
--ignore-https-errors # Ignore HTTPS certificate errors
|
||||||
|
--allow-file-access # Allow file:// URLs to access local files (Chromium only)
|
||||||
|
--json # JSON output (for scripts)
|
||||||
|
--debug # Debug output`} />
|
||||||
|
|
||||||
|
<h2>Local files</h2>
|
||||||
|
<p>Open local files (PDFs, HTML) using <code>file://</code> URLs:</p>
|
||||||
|
<CodeBlock code={`agent-browser --allow-file-access open file:///path/to/document.pdf
|
||||||
|
agent-browser --allow-file-access open file:///path/to/page.html
|
||||||
|
agent-browser screenshot output.png`} />
|
||||||
|
<p>
|
||||||
|
The <code>--allow-file-access</code> flag enables JavaScript to access other local files. Chromium only.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ agent-browser highlight @e1 # Highlight element
|
|||||||
agent-browser record start demo.webm # Record session
|
agent-browser record start demo.webm # Record session
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Local Files (PDFs, HTML)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open local files with file:// URLs
|
||||||
|
agent-browser --allow-file-access open file:///path/to/document.pdf
|
||||||
|
agent-browser --allow-file-access open file:///path/to/page.html
|
||||||
|
agent-browser screenshot output.png
|
||||||
|
```
|
||||||
|
|
||||||
### iOS Simulator (Mobile Safari)
|
### iOS Simulator (Mobile Safari)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+21
-4
@@ -1082,18 +1082,35 @@ export class BrowserManager {
|
|||||||
throw new Error('Extensions are only supported in Chromium');
|
throw new Error('Extensions are only supported in Chromium');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowFileAccess is only supported in Chromium
|
||||||
|
if (options.allowFileAccess && browserType !== 'chromium') {
|
||||||
|
throw new Error('allowFileAccess is only supported in Chromium');
|
||||||
|
}
|
||||||
|
|
||||||
const launcher =
|
const launcher =
|
||||||
browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
|
browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
|
||||||
const viewport = options.viewport ?? { width: 1280, height: 720 };
|
const viewport = options.viewport ?? { width: 1280, height: 720 };
|
||||||
|
|
||||||
|
// Build base args array with file access flags if enabled
|
||||||
|
// --allow-file-access-from-files: allows file:// URLs to read other file:// URLs via XHR/fetch
|
||||||
|
// --allow-file-access: allows the browser to access local files in general
|
||||||
|
const fileAccessArgs = options.allowFileAccess
|
||||||
|
? ['--allow-file-access-from-files', '--allow-file-access']
|
||||||
|
: [];
|
||||||
|
const baseArgs = options.args
|
||||||
|
? [...fileAccessArgs, ...options.args]
|
||||||
|
: fileAccessArgs.length > 0
|
||||||
|
? fileAccessArgs
|
||||||
|
: undefined;
|
||||||
|
|
||||||
let context: BrowserContext;
|
let context: BrowserContext;
|
||||||
if (hasExtensions) {
|
if (hasExtensions) {
|
||||||
// Extensions require persistent context in a temp directory
|
// Extensions require persistent context in a temp directory
|
||||||
const extPaths = options.extensions!.join(',');
|
const extPaths = options.extensions!.join(',');
|
||||||
const session = process.env.AGENT_BROWSER_SESSION || 'default';
|
const session = process.env.AGENT_BROWSER_SESSION || 'default';
|
||||||
// Combine extension args with custom args
|
// Combine extension args with custom args and file access args
|
||||||
const extArgs = [`--disable-extensions-except=${extPaths}`, `--load-extension=${extPaths}`];
|
const extArgs = [`--disable-extensions-except=${extPaths}`, `--load-extension=${extPaths}`];
|
||||||
const allArgs = options.args ? [...extArgs, ...options.args] : extArgs;
|
const allArgs = baseArgs ? [...extArgs, ...baseArgs] : extArgs;
|
||||||
context = await launcher.launchPersistentContext(
|
context = await launcher.launchPersistentContext(
|
||||||
path.join(os.tmpdir(), `agent-browser-ext-${session}`),
|
path.join(os.tmpdir(), `agent-browser-ext-${session}`),
|
||||||
{
|
{
|
||||||
@@ -1115,7 +1132,7 @@ export class BrowserManager {
|
|||||||
context = await launcher.launchPersistentContext(profilePath, {
|
context = await launcher.launchPersistentContext(profilePath, {
|
||||||
headless: options.headless ?? true,
|
headless: options.headless ?? true,
|
||||||
executablePath: options.executablePath,
|
executablePath: options.executablePath,
|
||||||
args: options.args,
|
args: baseArgs,
|
||||||
viewport,
|
viewport,
|
||||||
extraHTTPHeaders: options.headers,
|
extraHTTPHeaders: options.headers,
|
||||||
userAgent: options.userAgent,
|
userAgent: options.userAgent,
|
||||||
@@ -1128,7 +1145,7 @@ export class BrowserManager {
|
|||||||
this.browser = await launcher.launch({
|
this.browser = await launcher.launch({
|
||||||
headless: options.headless ?? true,
|
headless: options.headless ?? true,
|
||||||
executablePath: options.executablePath,
|
executablePath: options.executablePath,
|
||||||
args: options.args,
|
args: baseArgs,
|
||||||
});
|
});
|
||||||
this.cdpEndpoint = null;
|
this.cdpEndpoint = null;
|
||||||
context = await this.browser.newContext({
|
context = await this.browser.newContext({
|
||||||
|
|||||||
@@ -311,6 +311,7 @@ export async function startDaemon(options?: {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
const ignoreHTTPSErrors = process.env.AGENT_BROWSER_IGNORE_HTTPS_ERRORS === '1';
|
||||||
|
const allowFileAccess = process.env.AGENT_BROWSER_ALLOW_FILE_ACCESS === '1';
|
||||||
await manager.launch({
|
await manager.launch({
|
||||||
id: 'auto',
|
id: 'auto',
|
||||||
action: 'launch' as const,
|
action: 'launch' as const,
|
||||||
@@ -323,6 +324,7 @@ export async function startDaemon(options?: {
|
|||||||
userAgent: process.env.AGENT_BROWSER_USER_AGENT,
|
userAgent: process.env.AGENT_BROWSER_USER_AGENT,
|
||||||
proxy,
|
proxy,
|
||||||
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
ignoreHTTPSErrors: ignoreHTTPSErrors,
|
||||||
|
allowFileAccess: allowFileAccess,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface LaunchCommand extends BaseCommand {
|
|||||||
userAgent?: string;
|
userAgent?: string;
|
||||||
provider?: string;
|
provider?: string;
|
||||||
ignoreHTTPSErrors?: boolean;
|
ignoreHTTPSErrors?: boolean;
|
||||||
|
allowFileAccess?: boolean; // Enable file:// URL access and cross-origin file requests
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NavigateCommand extends BaseCommand {
|
export interface NavigateCommand extends BaseCommand {
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
import { describe, it, expect, afterEach, beforeAll, afterAll } from 'vitest';
|
||||||
|
import { BrowserManager } from '../src/browser.js';
|
||||||
|
import { writeFileSync, unlinkSync } from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import os from 'node:os';
|
||||||
|
|
||||||
|
describe('File Access (Issue #345)', () => {
|
||||||
|
let browser: BrowserManager;
|
||||||
|
const testFilePath = path.join(os.tmpdir(), 'agent-browser-test-file.html');
|
||||||
|
const testFileUrl = `file://${testFilePath}`;
|
||||||
|
|
||||||
|
// Create test HTML file before tests
|
||||||
|
beforeAll(() => {
|
||||||
|
writeFileSync(
|
||||||
|
testFilePath,
|
||||||
|
'<html><body><h1>Test File Access</h1><p>This content was loaded from a local file.</p></body></html>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up test file after tests
|
||||||
|
afterAll(() => {
|
||||||
|
try {
|
||||||
|
unlinkSync(testFilePath);
|
||||||
|
} catch {
|
||||||
|
// Ignore if file doesn't exist
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (browser?.isLaunched()) {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('without allowFileAccess flag', () => {
|
||||||
|
it('should fail to load file:// URL content by default', async () => {
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({
|
||||||
|
headless: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = browser.getPage();
|
||||||
|
|
||||||
|
// Navigate to file:// URL - this should work for navigation
|
||||||
|
// but Chromium restricts what the page can do
|
||||||
|
await page.goto(testFileUrl);
|
||||||
|
|
||||||
|
// The page should load but let's verify the URL
|
||||||
|
const url = page.url();
|
||||||
|
expect(url).toBe(testFileUrl);
|
||||||
|
|
||||||
|
// Content should be accessible when navigating directly
|
||||||
|
const content = await page.content();
|
||||||
|
expect(content).toContain('Test File Access');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with allowFileAccess flag', () => {
|
||||||
|
it('should load file:// URL with allowFileAccess enabled', async () => {
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({
|
||||||
|
headless: true,
|
||||||
|
allowFileAccess: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto(testFileUrl);
|
||||||
|
|
||||||
|
// Verify the page loaded correctly
|
||||||
|
const url = page.url();
|
||||||
|
expect(url).toBe(testFileUrl);
|
||||||
|
|
||||||
|
// Verify content is accessible
|
||||||
|
const heading = await page.locator('h1').textContent();
|
||||||
|
expect(heading).toBe('Test File Access');
|
||||||
|
|
||||||
|
const paragraph = await page.locator('p').textContent();
|
||||||
|
expect(paragraph).toBe('This content was loaded from a local file.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow file:// URL to access other local files via XMLHttpRequest', async () => {
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({
|
||||||
|
headless: true,
|
||||||
|
allowFileAccess: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto(testFileUrl);
|
||||||
|
|
||||||
|
// With allowFileAccess, XMLHttpRequest to local files should work
|
||||||
|
// This is the key difference - without the flag, this would be blocked
|
||||||
|
const canAccessFiles = await page.evaluate(() => {
|
||||||
|
return new Promise<boolean>((resolve) => {
|
||||||
|
try {
|
||||||
|
// XMLHttpRequest is the traditional way to test --allow-file-access-from-files
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', window.location.href, true);
|
||||||
|
xhr.onload = () => resolve(xhr.status === 0 || xhr.status === 200);
|
||||||
|
xhr.onerror = () => resolve(false);
|
||||||
|
xhr.send();
|
||||||
|
} catch {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(canAccessFiles).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('combined with other options', () => {
|
||||||
|
it('should work with allowFileAccess and custom user-agent', async () => {
|
||||||
|
const customUA = 'FileAccessTestBot/1.0';
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({
|
||||||
|
headless: true,
|
||||||
|
allowFileAccess: true,
|
||||||
|
userAgent: customUA,
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto(testFileUrl);
|
||||||
|
|
||||||
|
// Verify file access works
|
||||||
|
const content = await page.locator('h1').textContent();
|
||||||
|
expect(content).toBe('Test File Access');
|
||||||
|
|
||||||
|
// Verify user-agent is set
|
||||||
|
const ua = await page.evaluate(() => navigator.userAgent);
|
||||||
|
expect(ua).toBe(customUA);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with allowFileAccess and custom args', async () => {
|
||||||
|
browser = new BrowserManager();
|
||||||
|
await browser.launch({
|
||||||
|
headless: true,
|
||||||
|
allowFileAccess: true,
|
||||||
|
args: ['--disable-blink-features=AutomationControlled'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.goto(testFileUrl);
|
||||||
|
|
||||||
|
// Verify file access works
|
||||||
|
const content = await page.locator('h1').textContent();
|
||||||
|
expect(content).toBe('Test File Access');
|
||||||
|
|
||||||
|
// Verify webdriver is hidden (from custom arg)
|
||||||
|
const webdriver = await page.evaluate(() => navigator.webdriver);
|
||||||
|
expect(webdriver).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user