fix: resolve 3 protocol bugs, improve CLI and snapshot code quality (#487)
## Summary - Fix `allowFileAccess` being silently stripped from launch commands by adding it to the Zod schema in `protocol.ts` (the `--allow-file-access` CLI flag was not reaching the browser) - Fix `trace stop` requiring a path argument despite help text documenting it as optional -- now works with or without a path - Fix `addscript`/`addstyle` silently succeeding when neither `content` nor `url` is provided -- now returns a validation error - Replace hardcoded ANSI escape code with `color::error_indicator()` in `main.rs` to respect `NO_COLOR` - Fix double-parse pattern and add descriptive expect messages in `commands.rs` - Fix incomplete string escaping in `snapshot.ts` `buildSelector` (use `JSON.stringify` instead of manual quote escaping) - Simplify redundant ternary in `snapshot.ts` cursor-interactive role assignment - Sync docs changelog with CHANGELOG.md (v0.8.1 through v0.10.0)
This commit is contained in:
+31
-10
@@ -345,10 +345,8 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
|
|
||||||
// Default: selector or timeout
|
// Default: selector or timeout
|
||||||
if let Some(arg) = rest.first() {
|
if let Some(arg) = rest.first() {
|
||||||
if arg.parse::<u64>().is_ok() {
|
if let Ok(timeout) = arg.parse::<u64>() {
|
||||||
Ok(
|
Ok(json!({ "id": id, "action": "wait", "timeout": timeout }))
|
||||||
json!({ "id": id, "action": "wait", "timeout": arg.parse::<u64>().unwrap() }),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Ok(json!({ "id": id, "action": "wait", "selector": arg }))
|
Ok(json!({ "id": id, "action": "wait", "selector": arg }))
|
||||||
}
|
}
|
||||||
@@ -684,7 +682,8 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
Ok(cmd)
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
Some(n) if n.parse::<i32>().is_ok() => {
|
Some(n) if n.parse::<i32>().is_ok() => {
|
||||||
Ok(json!({ "id": id, "action": "tab_switch", "index": n.parse::<i32>().unwrap() }))
|
let index = n.parse::<i32>().expect("already checked parse succeeds");
|
||||||
|
Ok(json!({ "id": id, "action": "tab_switch", "index": index }))
|
||||||
}
|
}
|
||||||
_ => Ok(json!({ "id": id, "action": "tab_list" })),
|
_ => Ok(json!({ "id": id, "action": "tab_list" })),
|
||||||
},
|
},
|
||||||
@@ -746,11 +745,11 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
|
|||||||
match rest.first().copied() {
|
match rest.first().copied() {
|
||||||
Some("start") => Ok(json!({ "id": id, "action": "trace_start" })),
|
Some("start") => Ok(json!({ "id": id, "action": "trace_start" })),
|
||||||
Some("stop") => {
|
Some("stop") => {
|
||||||
let path = rest.get(1).ok_or_else(|| ParseError::MissingArguments {
|
let mut cmd = json!({ "id": id, "action": "trace_stop" });
|
||||||
context: "trace stop".to_string(),
|
if let Some(path) = rest.get(1) {
|
||||||
usage: "trace stop <path>",
|
cmd["path"] = json!(path);
|
||||||
})?;
|
}
|
||||||
Ok(json!({ "id": id, "action": "trace_stop", "path": path }))
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
Some(sub) => Err(ParseError::UnknownSubcommand {
|
Some(sub) => Err(ParseError::UnknownSubcommand {
|
||||||
subcommand: sub.to_string(),
|
subcommand: sub.to_string(),
|
||||||
@@ -2572,4 +2571,26 @@ mod tests {
|
|||||||
assert_eq!(cmd["action"], "launch");
|
assert_eq!(cmd["action"], "launch");
|
||||||
assert_eq!(cmd["cdpPort"], 1);
|
assert_eq!(cmd["cdpPort"], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Trace Tests ===
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trace_start() {
|
||||||
|
let cmd = parse_command(&args("trace start"), &default_flags()).unwrap();
|
||||||
|
assert_eq!(cmd["action"], "trace_start");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trace_stop_with_path() {
|
||||||
|
let cmd = parse_command(&args("trace stop ./trace.zip"), &default_flags()).unwrap();
|
||||||
|
assert_eq!(cmd["action"], "trace_stop");
|
||||||
|
assert_eq!(cmd["path"], "./trace.zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trace_stop_without_path() {
|
||||||
|
let cmd = parse_command(&args("trace stop"), &default_flags()).unwrap();
|
||||||
|
assert_eq!(cmd["action"], "trace_stop");
|
||||||
|
assert!(cmd.get("path").is_none() || cmd["path"].is_null());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -470,7 +470,7 @@ fn main() {
|
|||||||
if flags.json {
|
if flags.json {
|
||||||
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
println!(r#"{{"success":false,"error":"{}"}}"#, msg);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("\x1b[31m✗\x1b[0m {}", msg);
|
eprintln!("{} {}", color::error_indicator(), msg);
|
||||||
}
|
}
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -344,6 +344,11 @@ pub fn print_response(resp: &Response, json_mode: bool, action: Option<&str>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Trace stop without path
|
||||||
|
if data.get("traceStopped").is_some() {
|
||||||
|
println!("{} Trace stopped", color::success_indicator());
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Path-based operations (screenshot/pdf/trace/har/download/state/video)
|
// Path-based operations (screenshot/pdf/trace/har/download/state/video)
|
||||||
if let Some(path) = data.get("path").and_then(|v| v.as_str()) {
|
if let Some(path) = data.get("path").and_then(|v| v.as_str()) {
|
||||||
match action.unwrap_or("") {
|
match action.unwrap_or("") {
|
||||||
|
|||||||
@@ -2,6 +2,210 @@ export const metadata = { title: "Changelog" }
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.10.0
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **Session persistence** - Automatic save/restore of cookies and localStorage across browser restarts using `--session-name` flag
|
||||||
|
- **Encrypted state** - Optional AES-256-GCM encryption for saved session state data
|
||||||
|
- **State management commands** - New commands for listing, showing, renaming, clearing, and cleaning up session state files
|
||||||
|
- **New tab on click** - Added `--new-tab` option for click commands to open links in new tabs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Persist session state
|
||||||
|
agent-browser --session-name myapp open https://example.com
|
||||||
|
|
||||||
|
# Manage saved states
|
||||||
|
agent-browser state list
|
||||||
|
agent-browser state show myapp
|
||||||
|
agent-browser state clear myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.9.4
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed all Clippy lint warnings in the Rust CLI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.9.3
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Added support for custom executable path in CLI browser launch options
|
||||||
|
- Documentation site UI improvements including a new chat component with sheet-based interface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.9.2
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Migrated documentation site to MDX for improved content authoring
|
||||||
|
- Added AI-powered docs chat feature
|
||||||
|
- Updated README with Homebrew installation instructions for macOS users
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.9.1
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **`--allow-file-access` flag** - Enable opening and interacting with local `file://` URLs (PDFs, HTML files) by passing Chromium flags that allow JavaScript access to local files
|
||||||
|
- **`-C`/`--cursor` flag for snapshots** - Include cursor-interactive elements like divs with onclick handlers or `cursor:pointer` styles
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser --allow-file-access open file:///path/to/document.pdf
|
||||||
|
agent-browser snapshot -C
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.9.0
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">February 2026</p>
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **iOS Simulator support** - Mobile Safari testing via Appium with real device and simulator support
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List available iOS simulators
|
||||||
|
agent-browser device list
|
||||||
|
|
||||||
|
# Launch on iOS device
|
||||||
|
agent-browser -p ios --device "iPhone 16 Pro" open https://example.com
|
||||||
|
|
||||||
|
# Touch interactions
|
||||||
|
agent-browser tap @e1
|
||||||
|
agent-browser swipe up
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.10
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Added `--stdin` flag for eval command to read JavaScript from stdin, enabling heredoc usage for multiline scripts
|
||||||
|
- Fixed binary permission issues on macOS/Linux when postinstall scripts don't run
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.9
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Added `--stdin` flag for eval command to read JavaScript from stdin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.8
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Added base64 encoding support for the eval command with `-b`/`--base64` flag to avoid shell escaping issues
|
||||||
|
- Updated documentation with AI agent setup instructions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.7
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed browser launch options not being passed correctly when using persistent profiles
|
||||||
|
- Added pre-flight checks for socket path length limits and directory write permissions
|
||||||
|
- Improved error handling to properly exit with failure status when browser launch fails
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.6
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Improved daemon connection reliability with automatic retry logic for transient errors
|
||||||
|
- CLI now cleans up stale socket and PID files before starting a new daemon
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.5
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed version synchronization to automatically update Cargo.lock alongside Cargo.toml during releases
|
||||||
|
- Made the CLI binary executable in the npm package
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.4
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed "Daemon not found" error when running through AI agents by resolving symlinks in the executable path
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.3
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Replaced shell-based CLI wrappers with a cross-platform Node.js wrapper to enable npx support on Windows
|
||||||
|
- Added postinstall logic to patch npm bin entry on global installs for zero-overhead native binary invocation
|
||||||
|
- Added CI tests to verify global installation across all platforms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.2
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed the Windows CMD wrapper to use the native binary directly instead of routing through Node.js
|
||||||
|
- Added retry logic to CI install command for transient browser installation failures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.8.1
|
||||||
|
|
||||||
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Improved release workflow to validate binary file sizes and ensure binaries are executable after npm install
|
||||||
|
- Updated documentation site with a new mobile navigation system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v0.8.0
|
## v0.8.0
|
||||||
|
|
||||||
<p className="text-[#888] text-sm">January 2026</p>
|
<p className="text-[#888] text-sm">January 2026</p>
|
||||||
|
|||||||
+4
-1
@@ -1442,7 +1442,10 @@ async function handleTraceStop(
|
|||||||
browser: BrowserManager
|
browser: BrowserManager
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
await browser.stopTracing(command.path);
|
await browser.stopTracing(command.path);
|
||||||
return successResponse(command.id, { path: command.path });
|
return successResponse(
|
||||||
|
command.id,
|
||||||
|
command.path ? { path: command.path } : { traceStopped: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleHarStart(
|
async function handleHarStart(
|
||||||
|
|||||||
+2
-2
@@ -678,10 +678,10 @@ export class BrowserManager {
|
|||||||
/**
|
/**
|
||||||
* Stop tracing and save
|
* Stop tracing and save
|
||||||
*/
|
*/
|
||||||
async stopTracing(path: string): Promise<void> {
|
async stopTracing(path?: string): Promise<void> {
|
||||||
const context = this.contexts[0];
|
const context = this.contexts[0];
|
||||||
if (context) {
|
if (context) {
|
||||||
await context.tracing.stop({ path });
|
await context.tracing.stop(path ? { path } : undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -582,6 +582,22 @@ describe('parseCommand', () => {
|
|||||||
const result = parseCommand(cmd({ id: '1', action: 'launch', ignoreHTTPSErrors: 'true' }));
|
const result = parseCommand(cmd({ id: '1', action: 'launch', ignoreHTTPSErrors: 'true' }));
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should parse launch with allowFileAccess true', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'launch', allowFileAccess: true }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.allowFileAccess).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse launch with allowFileAccess false', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'launch', allowFileAccess: false }));
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
if (result.success) {
|
||||||
|
expect(result.command.allowFileAccess).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mouse actions', () => {
|
describe('mouse actions', () => {
|
||||||
@@ -1108,6 +1124,46 @@ describe('parseCommand', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('addscript and addstyle', () => {
|
||||||
|
it('should parse addscript with content', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'addscript', content: 'console.log("hi")' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse addscript with url', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'addscript', url: 'https://example.com/script.js' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject addscript with neither content nor url', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'addscript' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse addstyle with content', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'addstyle', content: 'body { color: red }' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse addstyle with url', () => {
|
||||||
|
const result = parseCommand(
|
||||||
|
cmd({ id: '1', action: 'addstyle', url: 'https://example.com/style.css' })
|
||||||
|
);
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject addstyle with neither content nor url', () => {
|
||||||
|
const result = parseCommand(cmd({ id: '1', action: 'addstyle' }));
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('invalid commands', () => {
|
describe('invalid commands', () => {
|
||||||
it('should reject unknown action', () => {
|
it('should reject unknown action', () => {
|
||||||
const result = parseCommand(cmd({ id: '1', action: 'unknown' }));
|
const result = parseCommand(cmd({ id: '1', action: 'unknown' }));
|
||||||
|
|||||||
+14
-2
@@ -47,6 +47,7 @@ const launchSchema = baseCommandSchema.extend({
|
|||||||
userAgent: z.string().optional(),
|
userAgent: z.string().optional(),
|
||||||
provider: z.string().optional(),
|
provider: z.string().optional(),
|
||||||
ignoreHTTPSErrors: z.boolean().optional(),
|
ignoreHTTPSErrors: z.boolean().optional(),
|
||||||
|
allowFileAccess: z.boolean().optional(),
|
||||||
profile: z.string().optional(),
|
profile: z.string().optional(),
|
||||||
storageState: z.string().optional(),
|
storageState: z.string().optional(),
|
||||||
});
|
});
|
||||||
@@ -369,7 +370,7 @@ const traceStartSchema = baseCommandSchema.extend({
|
|||||||
|
|
||||||
const traceStopSchema = baseCommandSchema.extend({
|
const traceStopSchema = baseCommandSchema.extend({
|
||||||
action: z.literal('trace_stop'),
|
action: z.literal('trace_stop'),
|
||||||
path: z.string().min(1),
|
path: z.string().min(1).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const harStartSchema = baseCommandSchema.extend({
|
const harStartSchema = baseCommandSchema.extend({
|
||||||
@@ -989,7 +990,18 @@ export function parseCommand(input: string): ParseResult {
|
|||||||
return { success: false, error: `Validation error: ${errors}`, id };
|
return { success: false, error: `Validation error: ${errors}`, id };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, command: result.data as Command };
|
const command = result.data as Command;
|
||||||
|
|
||||||
|
// Post-parse validation for commands that need cross-field checks
|
||||||
|
if (
|
||||||
|
(command.action === 'addscript' || command.action === 'addstyle') &&
|
||||||
|
!command.content &&
|
||||||
|
!command.url
|
||||||
|
) {
|
||||||
|
return { success: false, error: 'Either content or url must be provided', id };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, command };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+3
-3
@@ -132,8 +132,8 @@ const STRUCTURAL_ROLES = new Set([
|
|||||||
*/
|
*/
|
||||||
function buildSelector(role: string, name?: string): string {
|
function buildSelector(role: string, name?: string): string {
|
||||||
if (name) {
|
if (name) {
|
||||||
const escapedName = name.replace(/"/g, '\\"');
|
const escapedName = JSON.stringify(name);
|
||||||
return `getByRole('${role}', { name: "${escapedName}", exact: true })`;
|
return `getByRole('${role}', { name: ${escapedName}, exact: true })`;
|
||||||
}
|
}
|
||||||
return `getByRole('${role}')`;
|
return `getByRole('${role}')`;
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ export async function getEnhancedSnapshot(
|
|||||||
existingTexts.add(elTextLower);
|
existingTexts.add(elTextLower);
|
||||||
|
|
||||||
const ref = nextRef();
|
const ref = nextRef();
|
||||||
const role = el.hasCursorPointer ? 'clickable' : el.hasOnClick ? 'clickable' : 'focusable';
|
const role = el.hasCursorPointer || el.hasOnClick ? 'clickable' : 'focusable';
|
||||||
|
|
||||||
refs[ref] = {
|
refs[ref] = {
|
||||||
selector: el.selector,
|
selector: el.selector,
|
||||||
|
|||||||
+1
-1
@@ -576,7 +576,7 @@ export interface TraceStartCommand extends BaseCommand {
|
|||||||
|
|
||||||
export interface TraceStopCommand extends BaseCommand {
|
export interface TraceStopCommand extends BaseCommand {
|
||||||
action: 'trace_stop';
|
action: 'trace_stop';
|
||||||
path: string;
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HAR recording
|
// HAR recording
|
||||||
|
|||||||
Reference in New Issue
Block a user