Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cb84a90
feat: add run-script and adapter commands to execute JavaScript files…
aeroxy Jun 30, 2026
c913334
feat: add run-script and adapter functionality for local JavaScript e…
aeroxy Jun 30, 2026
cf0e0ca
fix: stabilize adapter execution, argument parsing, and deepwiki scripts
aeroxy Jun 30, 2026
1bcec81
chore: centralize polling interval for ctx wait helpers
aeroxy Jun 30, 2026
75fb2d6
refactor(evaluate): unify ctx injection, improve domain parsing and U…
aeroxy Jun 30, 2026
8f1a8da
feat(evaluate): improve adapter safety, input handling, and execution…
aeroxy Jun 30, 2026
49c403c
fix(run_adapter): validate post-navigation URL against adapter domains
aeroxy Jun 30, 2026
a160cac
fix(evaluate): enhance contenteditable support and improve host norma…
aeroxy Jun 30, 2026
433bc4c
feat(cli): add custom scripting & adapter support with auto-navigation
aeroxy Jun 30, 2026
d86a029
fix(run_script): improve URL validation and auto-navigation handling
aeroxy Jun 30, 2026
d0d5856
fix(evaluate): enhance value handling in build_ctx_object and improve…
aeroxy Jul 1, 2026
f29afad
feat: improve script execution UX and refactor CLI argument handling
aeroxy Jul 1, 2026
812a238
fix(cli,evaluate): require `--` for raw args, improve script URL pars…
aeroxy Jul 1, 2026
b1d0287
fix(evaluate): enhance value setter logic and improve host normalizat…
aeroxy Jul 1, 2026
b0d1700
fix(evaluate): improve domain parsing logic in parse_adapter_domains …
aeroxy Jul 1, 2026
6eaf78c
refactor: remove toml dependency and make file reads async
aeroxy Jul 2, 2026
f6baea9
fix(args): improve key=value parsing, preserve positional values with…
aeroxy Jul 2, 2026
45642cd
fix(evaluate): improve DOM fill handling, metadata parsing, and expor…
aeroxy Jul 2, 2026
0848cce
fix(evaluate,args): ensure wait helpers always check once and support…
aeroxy Jul 2, 2026
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ High-performance rust CLI that connects to an existing Chrome browser via the De

[![crates.io](https://img.shields.io/crates/v/chrome-devtools-cli.svg)](https://crates.io/crates/chrome-devtools-cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/aeroxy/chrome-devtools-cli)

## Installation

Expand Down
138 changes: 138 additions & 0 deletions skill/chrome-devtools/CUSTOM_SCRIPTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Custom Scripting & Adapters Guide

This guide details how to create and execute custom JavaScript scripts (`run-script`) and custom domain-aware adapters (`adapter`) using the Chrome DevTools CLI.

---

## 1. Custom Scripts (`run-script`)

`run-script` reads a local JavaScript file, wraps it inside an Immediately Invoked Function Expression (IIFE), and evaluates it directly inside the target browser's page context.

### Flexible Argument Syntax
Dynamic arguments passed to the script can be specified in several styles and are automatically parsed and made available inside `ctx.args`. Note that raw positional values (styles 1 & 2 below) must come after a literal `--`, and any options like `--output`/`--track-navigation` must be given *before* it:

1. **Named Style via `-a`/`--arg` (Recommended):**
Pass one or more `key=value` pairs with the repeatable `-a`/`--arg` flag. This form doesn't need a `--` separator:
```bash
chrome-devtools run-script search_hn.js -a query="Rust"
```
2. **Pure Positional Style:**
Append raw positional strings after `--`. A single trailing positional argument is automatically mapped to `ctx.args.query` (as well as `ctx.args._0`):
```bash
chrome-devtools run-script search_hn.js -- "Rust"
```
3. **Hybrid Style (Positional + Named, after `--`):**
```bash
chrome-devtools run-script search_hn.js -- "Rust" limit=10 safeSearch=true
```

### Comment-based Auto-Navigation
By declaring a standard `// @url <target_url>` or `// @navigate <target_url>` comment marker at the top of your script file, the CLI will check the active tab's current URL before executing your script.

If the active tab is not currently on a domain matching the target URL, **the CLI will automatically navigate the tab to the target URL first**, wait for the page to load, and then execute your script. You can use `{arg_name}` placeholders inside the `@url` template to interpolate CLI arguments dynamically:
```javascript
// @url https://hn.algolia.com/?query={query}
```

---

## 2. Custom Domain-Aware Adapters (`adapter`)

`adapter` reads a local custom JS adapter file, parses the target `@domain` JSDoc markers, and ensures the browser is on a matching domain before invoking a specific named function inside the script.

### Domain Protection and Auto-Navigation
By declaring standard `@domain` markers at the top of your adapter file, the CLI checks the active page URL before executing your function. If the active tab is not on the target domain, **it automatically navigates the tab to the first target domain**, waits for it to load, and then runs your adapter.

```javascript
// ==UserAdapter==
// @name Hacker News Search Adapter
// @domain hn.algolia.com
// ==/UserAdapter==
```

---

## 3. Injected Helper Context (`ctx`)

Both `run-script` and `adapter` functions are passed an injected `ctx` context containing standard helper utilities:

* `ctx.args`: Object containing typed key-value arguments.
* `ctx.wait(ms)`: Sleep/delay utility (`await ctx.wait(1000)`).
* `ctx.waitForText(text, timeout_ms)`: Polls the page body text until the string is present (defaults to 30s).
* `ctx.waitForSelector(selector, timeout_ms)`: Polls until an element matching the CSS selector exists in the DOM.
* `ctx.click(selector)`: DOM clicking helper.
* `ctx.fill(selector, value)`: DOM value input helper. Highly compatible with stateful frameworks (like React, Vue, and Angular) as it overrides standard value setters and fires appropriate events.

---

## 4. Real-World SPA Example (Hacker News Search)

These real-world examples work on `hn.algolia.com`.

### Script file (`skill/chrome-devtools/examples/search_hn.js`)
```javascript
// @url https://hn.algolia.com/?query={query}

// search_hn.js
// Run with: chrome-devtools run-script skill/chrome-devtools/examples/search_hn.js -a query="Rust"
//
// run-script injects `ctx` and runs this file inside an async context.
// Setting `@url` above tells the CLI to automatically navigate to the pre-rendered query URL first!

const query = ctx.args.query;
if (!query) {
throw new Error("Query argument is required. Pass it with '-a query=...'");
}

// Wait for results to update/load
await ctx.waitForSelector("article.Story", 10000);

// Extract results
const results = Array.from(document.querySelectorAll("article.Story")).map(el => {
const titleEl = el.querySelector(".Story_title a");
const metaEl = el.querySelector(".Story_meta");
return {
title: titleEl?.innerText.trim() || "",
meta: metaEl?.innerText.trim() || "",
url: titleEl?.href || ""
};
});

return results;
```

### Adapter file (`skill/chrome-devtools/examples/hn_adapter.js`)
```javascript
// ==UserAdapter==
// @name Hacker News Search Adapter
// @domain hn.algolia.com
// ==/UserAdapter==

// Run with: chrome-devtools adapter skill/chrome-devtools/examples/hn_adapter.js search -a query="Rust"

async function search(ctx) {
const query = ctx.args.query;
if (!query) throw new Error("query argument is required");

// Fill search input (the SPA will fetch and render results dynamically)
await ctx.fill("input.SearchInput", query);

// Wait a brief moment for React and the network request to resolve and update the DOM
await ctx.wait(1500);

// Wait for results to update/load
await ctx.waitForSelector("article.Story", 10000);

const results = Array.from(document.querySelectorAll("article.Story")).map(el => {
const titleEl = el.querySelector(".Story_title a");
const metaEl = el.querySelector(".Story_meta");
return {
title: titleEl?.innerText.trim() || "",
meta: metaEl?.innerText.trim() || "",
url: titleEl?.href || ""
};
});

return results;
}
```
30 changes: 29 additions & 1 deletion skill/chrome-devtools/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ chrome-devtools --page 0 navigate https://example.com

- **Navigation**: `navigate`, `navigate --back`, `navigate --forward`, `navigate --reload`
- **Page management**: `list-pages`, `new-page`, `close-page`, `select-page`
- **Extraction**: `screenshot`, `snapshot` (accessibility tree), `evaluate` (JavaScript), `read-page` (page content as markdown)
- **Extraction**: `screenshot`, `snapshot` (accessibility tree), `evaluate` (JavaScript), `read-page` (page content as markdown), `run-script` (run local JS file), `adapter` (run site adapter)
- **Interaction**: `click`, `fill`, `type-text`, `press-key`, `hover`, `click-at`
- **Emulation**: `emulate` (viewport, mobile, geolocation, URL blocking)
- **Inspection**: `console` (logs), `network` (requests), `sw-logs` (extension service workers)
Expand Down Expand Up @@ -311,6 +311,28 @@ chrome-devtools --target warm-squid read-page --json
- `read-page` — you want the page's textual content as readable markdown (articles, docs, wiki pages). Best for summarization, extraction, or feeding content to an LLM.
- `snapshot` — you need the full accessibility tree with element IDs, roles, and interactive elements. Best for understanding page structure and finding elements to click/fill.

### Pattern 13: Local JS Scripting (run-script)

Evaluate a local JavaScript file inside the page context. Dynamic arguments can be passed as raw positional values at the end of the command or via `-a/--arg` keys, and are automatically typed and injected into the execution context as `ctx.args`. Supports comment-based `@url` auto-navigation.

See the dedicated [Custom Scripting Guide](./CUSTOM_SCRIPTING.md) for full documentation on script creation, argument parsing, and auto-navigation.

```bash
# Run a script with trailing positional arguments (auto-navigates if @url is present)
chrome-devtools --target warm-squid run-script skill/chrome-devtools/examples/search_hn.js -- "Rust"
```

### Pattern 14: Custom Domain-Aware Adapters (adapter)

Run site-specific adapter actions. If the browser is not currently on a matching domain (as defined by `@domain` comments in the JSDoc header), the CLI auto-navigates to that domain first.

See the dedicated [Custom Scripting Guide](./CUSTOM_SCRIPTING.md) for full documentation on custom adapters, domain protection, and argument parsing.

```bash
# Run an adapter function with positional args (auto-navigates if target domain is mismatch)
chrome-devtools --target warm-squid adapter skill/chrome-devtools/examples/hn_adapter.js search -- "Rust"
```

## Complete Command Reference

### Navigation
Expand Down Expand Up @@ -366,6 +388,12 @@ chrome-devtools --target <name> list-3p-tools
chrome-devtools --target <name> execute-3p-tool <name> '<json-params>'
```

### Custom Scripting & Adapters
```bash
chrome-devtools --target <name> run-script <file-path> [--arg key=value] [--output <path>] [--track-navigation]
chrome-devtools --target <name> adapter <file-path> <function-name> [--arg key=value] [--output <path>] [--track-navigation]
```

### Daemon
```bash
chrome-devtools kill-daemon # stop the background daemon process
Expand Down
32 changes: 32 additions & 0 deletions skill/chrome-devtools/examples/hn_adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// ==UserAdapter==
// @name Hacker News Search Adapter
// @domain hn.algolia.com
// ==/UserAdapter==

// Run with: chrome-devtools adapter skill/chrome-devtools/examples/hn_adapter.js search -a query="Rust"

async function search(ctx) {
const query = ctx.args.query;
if (!query) throw new Error("query argument is required");

// Fill search input (the SPA will fetch and render results dynamically)
await ctx.fill("input.SearchInput", query);

// Wait a brief moment for React and the network request to resolve and update the DOM
await ctx.wait(1500);

// Wait for results to update/load
await ctx.waitForSelector("article.Story", 10000);

const results = Array.from(document.querySelectorAll("article.Story")).map(el => {
const titleEl = el.querySelector(".Story_title a");
const metaEl = el.querySelector(".Story_meta");
return {
title: titleEl?.innerText.trim() || "",
meta: metaEl?.innerText.trim() || "",
url: titleEl?.href || ""
};
});

return results;
}
28 changes: 28 additions & 0 deletions skill/chrome-devtools/examples/search_hn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @url https://hn.algolia.com/?query={query}

// search_hn.js
// Run with: chrome-devtools run-script skill/chrome-devtools/examples/search_hn.js -a query="Rust"
//
// run-script injects `ctx` and runs this file inside an async context.
// Setting `@url` above tells the CLI to automatically navigate to the pre-rendered query URL first!

const query = ctx.args.query;
if (!query) {
throw new Error("Query argument is required. Pass it with '-a query=...'");
}

// Wait for results to update/load
await ctx.waitForSelector("article.Story", 10000);

// Extract results
const results = Array.from(document.querySelectorAll("article.Story")).map(el => {
const titleEl = el.querySelector(".Story_title a");
const metaEl = el.querySelector(".Story_meta");
return {
title: titleEl?.innerText.trim() || "",
meta: metaEl?.innerText.trim() || "",
url: titleEl?.href || ""
};
});

return results;
Loading