craftdriver 0.0.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +186 -10
- package/bin/craftdriver.mjs +26 -0
- package/dist/cli/client.d.ts +17 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +88 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/daemon.d.ts +15 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +145 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/defaults.d.ts +10 -0
- package/dist/cli/defaults.d.ts.map +1 -0
- package/dist/cli/defaults.js +42 -0
- package/dist/cli/defaults.js.map +1 -0
- package/dist/cli/dispatcher.d.ts +28 -0
- package/dist/cli/dispatcher.d.ts.map +1 -0
- package/dist/cli/dispatcher.js +319 -0
- package/dist/cli/dispatcher.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +404 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +24 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +192 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mcp/artifacts.d.ts +43 -0
- package/dist/cli/mcp/artifacts.d.ts.map +1 -0
- package/dist/cli/mcp/artifacts.js +104 -0
- package/dist/cli/mcp/artifacts.js.map +1 -0
- package/dist/cli/mcp/server.d.ts +34 -0
- package/dist/cli/mcp/server.d.ts.map +1 -0
- package/dist/cli/mcp/server.js +214 -0
- package/dist/cli/mcp/server.js.map +1 -0
- package/dist/cli/mcp/tools.d.ts +34 -0
- package/dist/cli/mcp/tools.d.ts.map +1 -0
- package/dist/cli/mcp/tools.js +236 -0
- package/dist/cli/mcp/tools.js.map +1 -0
- package/dist/cli/parseArgs.d.ts +37 -0
- package/dist/cli/parseArgs.d.ts.map +1 -0
- package/dist/cli/parseArgs.js +215 -0
- package/dist/cli/parseArgs.js.map +1 -0
- package/dist/cli/protocol.d.ts +34 -0
- package/dist/cli/protocol.d.ts.map +1 -0
- package/dist/cli/protocol.js +2 -0
- package/dist/cli/protocol.js.map +1 -0
- package/dist/cli/selector.d.ts +29 -0
- package/dist/cli/selector.d.ts.map +1 -0
- package/dist/cli/selector.js +100 -0
- package/dist/cli/selector.js.map +1 -0
- package/dist/cli/snapshot.d.ts +48 -0
- package/dist/cli/snapshot.d.ts.map +1 -0
- package/dist/cli/snapshot.js +179 -0
- package/dist/cli/snapshot.js.map +1 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/a11y.d.ts +81 -0
- package/dist/lib/a11y.d.ts.map +1 -0
- package/dist/lib/a11y.js +171 -0
- package/dist/lib/a11y.js.map +1 -0
- package/dist/lib/bidi/connection.d.ts +62 -0
- package/dist/lib/bidi/connection.d.ts.map +1 -0
- package/dist/lib/bidi/connection.js +227 -0
- package/dist/lib/bidi/connection.js.map +1 -0
- package/dist/lib/bidi/index.d.ts +101 -0
- package/dist/lib/bidi/index.d.ts.map +1 -0
- package/dist/lib/bidi/index.js +163 -0
- package/dist/lib/bidi/index.js.map +1 -0
- package/dist/lib/bidi/logs.d.ts +104 -0
- package/dist/lib/bidi/logs.d.ts.map +1 -0
- package/dist/lib/bidi/logs.js +273 -0
- package/dist/lib/bidi/logs.js.map +1 -0
- package/dist/lib/bidi/network.d.ts +165 -0
- package/dist/lib/bidi/network.d.ts.map +1 -0
- package/dist/lib/bidi/network.js +477 -0
- package/dist/lib/bidi/network.js.map +1 -0
- package/dist/lib/bidi/storage.d.ts +83 -0
- package/dist/lib/bidi/storage.d.ts.map +1 -0
- package/dist/lib/bidi/storage.js +299 -0
- package/dist/lib/bidi/storage.js.map +1 -0
- package/dist/lib/bidi/types.d.ts +323 -0
- package/dist/lib/bidi/types.d.ts.map +1 -0
- package/dist/lib/bidi/types.js +6 -0
- package/dist/lib/bidi/types.js.map +1 -0
- package/dist/lib/browser.d.ts +776 -13
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +1547 -70
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/browserContext.d.ts +486 -0
- package/dist/lib/browserContext.d.ts.map +1 -0
- package/dist/lib/browserContext.js +937 -0
- package/dist/lib/browserContext.js.map +1 -0
- package/dist/lib/builder.d.ts +3 -0
- package/dist/lib/builder.d.ts.map +1 -1
- package/dist/lib/builder.js +31 -4
- package/dist/lib/builder.js.map +1 -1
- package/dist/lib/by.d.ts +18 -2
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +20 -2
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/chrome.d.ts +13 -1
- package/dist/lib/chrome.d.ts.map +1 -1
- package/dist/lib/chrome.js +13 -17
- package/dist/lib/chrome.js.map +1 -1
- package/dist/lib/clock.d.ts +115 -0
- package/dist/lib/clock.d.ts.map +1 -0
- package/dist/lib/clock.js +407 -0
- package/dist/lib/clock.js.map +1 -0
- package/dist/lib/driver.d.ts +45 -0
- package/dist/lib/driver.d.ts.map +1 -1
- package/dist/lib/driver.js +129 -1
- package/dist/lib/driver.js.map +1 -1
- package/dist/lib/driverManager.d.ts +25 -0
- package/dist/lib/driverManager.d.ts.map +1 -0
- package/dist/lib/driverManager.js +399 -0
- package/dist/lib/driverManager.js.map +1 -0
- package/dist/lib/elementHandle.d.ts +69 -2
- package/dist/lib/elementHandle.d.ts.map +1 -1
- package/dist/lib/elementHandle.js +215 -40
- package/dist/lib/elementHandle.js.map +1 -1
- package/dist/lib/errors.d.ts +71 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +73 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/expect.d.ts +6 -1
- package/dist/lib/expect.d.ts.map +1 -1
- package/dist/lib/expect.js +55 -37
- package/dist/lib/expect.js.map +1 -1
- package/dist/lib/firefox.d.ts +18 -0
- package/dist/lib/firefox.d.ts.map +1 -0
- package/dist/lib/firefox.js +33 -0
- package/dist/lib/firefox.js.map +1 -0
- package/dist/lib/frame.d.ts +57 -0
- package/dist/lib/frame.d.ts.map +1 -0
- package/dist/lib/frame.js +277 -0
- package/dist/lib/frame.js.map +1 -0
- package/dist/lib/keyboard.d.ts +1 -1
- package/dist/lib/keyboard.d.ts.map +1 -1
- package/dist/lib/keyboard.js +1 -1
- package/dist/lib/keyboard.js.map +1 -1
- package/dist/lib/locator.d.ts +71 -2
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +289 -31
- package/dist/lib/locator.js.map +1 -1
- package/dist/lib/mouse.d.ts +1 -1
- package/dist/lib/mouse.d.ts.map +1 -1
- package/dist/lib/mouse.js +1 -1
- package/dist/lib/mouse.js.map +1 -1
- package/dist/lib/page.d.ts +107 -0
- package/dist/lib/page.d.ts.map +1 -0
- package/dist/lib/page.js +388 -0
- package/dist/lib/page.js.map +1 -0
- package/dist/lib/tracing.d.ts +128 -0
- package/dist/lib/tracing.d.ts.map +1 -0
- package/dist/lib/tracing.js +272 -0
- package/dist/lib/tracing.js.map +1 -0
- package/dist/lib/wait.d.ts.map +1 -1
- package/dist/lib/wait.js +14 -1
- package/dist/lib/wait.js.map +1 -1
- package/dist/lib/webelement.d.ts +2 -0
- package/dist/lib/webelement.d.ts.map +1 -1
- package/dist/lib/webelement.js +18 -0
- package/dist/lib/webelement.js.map +1 -1
- package/docs/accessibility.md +177 -0
- package/docs/api-reference.md +71 -0
- package/docs/assertions.md +182 -0
- package/docs/bidi-features.md +462 -0
- package/docs/browser-api.md +673 -0
- package/docs/browser-context.md +522 -0
- package/docs/cli.md +233 -0
- package/docs/clock.md +245 -0
- package/docs/dialogs.md +143 -0
- package/docs/driver-configuration.md +64 -0
- package/docs/element-api.md +219 -0
- package/docs/emulation.md +155 -0
- package/docs/error-codes.md +67 -0
- package/docs/getting-started.md +168 -0
- package/docs/keyboard-mouse.md +250 -0
- package/docs/mcp.md +250 -0
- package/docs/mobile-emulation.md +194 -0
- package/docs/screenshots.md +146 -0
- package/docs/selectors.md +382 -0
- package/docs/session-management.md +229 -0
- package/docs/tracing.md +282 -0
- package/package.json +25 -5
- package/skills/craftdriver/SKILL.md +84 -0
- package/skills/craftdriver/cheatsheet.md +194 -0
- package/skills/craftdriver/cli.md +104 -0
- package/skills/craftdriver/patterns.md +139 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# [0.2.0](https://github.com/dtopuzov/craftdriver/compare/v0.1.0...v0.2.0) (2026-05-29)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* accessibility audits ([0b59957](https://github.com/dtopuzov/craftdriver/commit/0b59957ecb014d32962298de8da939cb873c6380))
|
|
7
|
+
* add driver manager ([3f60949](https://github.com/dtopuzov/craftdriver/commit/3f6094918fdaf0e04a8df61d732ea878c215875f))
|
|
8
|
+
* AI productivity tooling ([ffac9f0](https://github.com/dtopuzov/craftdriver/commit/ffac9f02d45bc14126e016bd2b7cfac3cf2fb057))
|
|
9
|
+
* browser-context ([6ddbc8a](https://github.com/dtopuzov/craftdriver/commit/6ddbc8acf619418987dc2be1613271bcd5ada488))
|
|
10
|
+
* browser.emulate ([aefabba](https://github.com/dtopuzov/craftdriver/commit/aefabba9132275d77ad673539c71242affbb47fb))
|
|
11
|
+
* ship the May browser automation feature set ([b89f3a6](https://github.com/dtopuzov/craftdriver/commit/b89f3a65b704178b7d984d65af9c12bddbc2d524))
|
|
12
|
+
* **tracing:** mimeType + fromCache on response events; final.png on failure ([4b1b293](https://github.com/dtopuzov/craftdriver/commit/4b1b293cd46d9641fa1106c7ae9736a47e418e62))
|
|
13
|
+
* **tracing:** streaming NDJSON tracer with on-failure vitest helper ([3c95a4b](https://github.com/dtopuzov/craftdriver/commit/3c95a4b2880ff379668b449a47e422ce74e2cfb3))
|
|
14
|
+
* virtual clock ([1d6b0ec](https://github.com/dtopuzov/craftdriver/commit/1d6b0eca4d8f0eed03dc597a3bf443836a206208))
|
|
15
|
+
|
|
16
|
+
# [0.1.0](https://github.com/dtopuzov/craftdriver/compare/v0.0.3...v0.1.0) (2026-02-08)
|
|
17
|
+
|
|
18
|
+
## Unreleased
|
|
19
|
+
|
|
20
|
+
- fix(security): clamp user-supplied `timeout` and `idleDuration` values passed to `setTimeout` in `waitForNetworkIdle` (network.ts) and `waitForLoadState` (browser.ts) to prevent resource exhaustion from unbounded timer durations (CodeQL high-severity alerts). The `ms()` helper in `dispatcher.ts` (the socket→number boundary for the CLI daemon) now also caps at 300,000 ms so the sanitization happens at the untrusted-input boundary rather than deep in the call chain. Caps: 300,000 ms for timeouts, 60,000 ms for idle durations.
|
|
21
|
+
- fix(tests): `driver-manager.test.ts` integration test was silently short-circuiting on systems where `chromedriver` is already on `PATH` (ubuntu-latest CI runners, nvm-managed Node envs). The resolution chain hit the PATH probe at step 5 before ever reaching the auto-download step, so nothing was written to the cache directory and the cache assertions failed. Fix: `beforeAll` now filters chromedriver-containing directories out of `PATH` to force the download path; `PATH` is restored in `afterAll`.
|
|
22
|
+
|
|
23
|
+
- fix(browser-context): `page.context()` now returns `browser.defaultContext` for pages opened via `browser.openPage()` / `browser.waitForPage()` / `browser.pages()` / `browser.activePage()` instead of `undefined`. Previously these pages lived in the default user context at the BiDi layer but had no owner wired up, which meant `page.navigateTo('/foo')` silently ignored the default context's `baseURL`, and the model leaked. New regression test in [tests/browser-context-hooks.test.ts](tests/browser-context-hooks.test.ts).
|
|
24
|
+
- fix(browser-context): `browser.contexts()`, `browser.defaultContext`, and `browser.newContext()` now share a single cached `BrowserContext` instance per BiDi user-context id, evicted on context close. Previously `browser.contexts()` minted a fresh wrapper every call, silently losing every `on()` listener, `addInitScript`, and `route()` registration attached to a sibling instance. [src/lib/browser.ts](src/lib/browser.ts).
|
|
25
|
+
- chore(browser-context): internal cleanup pass — dropped dead state (`_routeIds`, `_tracking`), parallelized per-page intercept registration in `ctx.route()`, fixed stale jsdoc on `ctx.route()` and `ctx.on()`, and trimmed `docs/browser-context.md` to remove the obsolete "no `storageState` round-trip" callout and a duplicated multi-user-login example. Added a "Scope and precedence (gotchas)" section documenting the `storageState` coverage (cookies + localStorage only — not sessionStorage / IndexedDB), Browser-vs-Context emulation precedence, the service-worker scope of `ctx.route()`, and the non-transactional shape of `grantPermissions`. [docs/browser-context.md](docs/browser-context.md).
|
|
26
|
+
|
|
27
|
+
- feat: `BrowserContext` identity & device emulation (Milestone C of the browser-context plan) — tight, capability-gated slice. New `browser.newContext({ locale, timezoneId, geolocation })` options plus live setters `ctx.setLocale(locale | null)`, `ctx.setTimezone(timezoneId | null)`, `ctx.setGeolocation(coords | null)`, `ctx.grantPermissions(names, { origin, state? })`, and `ctx.clearPermissions(names, { origin })`. All five are scoped via BiDi `userContexts: [<id>]` / `userContext` so future pages (and popups) inherit automatically with no per-page plumbing. `locale`, `timezoneId`, and `grantPermissions` work on Chrome **and** Firefox; `setGeolocation` is reliable on Chrome and wraps the BiDi error with the engine name on Firefox where coverage is still uneven. A default `browser.newContext()` (no options) remains cross-browser. Out-of-scope by design: per-context `viewport` / `colorScheme` / `reducedMotion` / `userAgent` — those still need either CDP (Chromium-only) or BiDi primitives that aren't broadly shipped. See [docs/browser-context.md](docs/browser-context.md), tests in [tests/browser-context-emulation.test.ts](tests/browser-context-emulation.test.ts).
|
|
28
|
+
|
|
29
|
+
- feat: `BrowserContext` page-scoped hooks & routing (Milestone B of the browser-context plan) — `ctx.on('page' | 'close', listener)` / `ctx.off(...)` for tab-and-popup observation across every page in a context, `ctx.addInitScript(script)` returning an `InitScriptHandle` with `.id` + `.remove()` (and matching `ctx.removeInitScript(id)`) for per-context preload scripts that don't leak to sibling contexts, `ctx.route(pattern, handler)` / `ctx.unroute(id?)` for context-scoped request mocking (URL substring, `RegExp`, or `**`-aware glob; the handler receives `{ request, fulfill, continue, abort }`), `ctx.setExtraHTTPHeaders(headers)` to swap the per-context header bag at runtime, plus `browser.newContext({ baseURL, extraHTTPHeaders })` options so `ctx.newPage({ url: '/login' })` resolves against the context's base URL and every outbound request carries the configured headers. Adds `page.context()` so a `Page` can reach back to its owning `BrowserContext`. Routes are registered per BiDi browsing context (and re-registered on every new page), which is what makes Firefox honour them under a non-default `userContext`. Works on Chrome **and** Firefox via BiDi. New exported types `BrowserContextConfig`, `BrowserContextHooks`, `InitScriptHandle`, `RoutePattern`. See [docs/browser-context.md](docs/browser-context.md), tests in [tests/browser-context-hooks.test.ts](tests/browser-context-hooks.test.ts).
|
|
30
|
+
|
|
31
|
+
- feat: `BrowserContext` cookies + storage state (Milestone A of the browser-context plan) — `ctx.cookies(urls?)`, `ctx.addCookies(cookies)`, `ctx.clearCookies(filter?)`, `ctx.storageState(opts?)`, `ctx.saveStorageState(path, opts?)`, `ctx.loadStorageState(source)`, and `browser.newContext({ storageState })`. All operations are scoped to the user context via the BiDi `storageKey` partition; localStorage is restored on first navigation through an internal preload script bound to the user context. Enables the auth-fixture pattern (log in once, save to JSON, reuse across tests) and proper multi-user isolation. Works on Chrome **and** Firefox. New exported types `ClearCookiesFilter`, `ContextStorageStateOptions`. See [docs/browser-context.md](docs/browser-context.md), tests in [tests/browser-context-storage.test.ts](tests/browser-context-storage.test.ts).
|
|
32
|
+
|
|
33
|
+
- chore(tests): drop the `bidi-` filename prefix on the four BiDi-specific test files (`bidi-network` → `network`, `bidi-logs` → `logs`, `bidi-load-state` → `load-state`, `bidi-storage` → `storage`). The prefix dates back to when those were the only BiDi tests; today most tests are BiDi-only (BiDi is default-on) and the inconsistent prefix actively misled readers. BiDi-ness is now treated as an implementation detail of the feature, not a test category. Renamed via `git mv` so blame survives. Updated [.github/instructions/tests.instructions.md](.github/instructions/tests.instructions.md) accordingly. Also renamed the non-test helper `tests/auto-trace.ts` → `tests/_auto-trace.ts` so it sorts to the top of the directory and reads as "not a test" at a glance.
|
|
34
|
+
- test: smoke tests for the MCP server in [tests/mcp-smoke.test.ts](tests/mcp-smoke.test.ts) — spawn `node bin/craftdriver.mjs mcp` as a child process, speak JSON-RPC 2.0 over its stdio (no LLM required — MCP is a deterministic protocol), and assert on the protocol surface. Three cases: `tools/list` returns the 14 documented tools by name, `browser_navigate` + `browser_snapshot` against [examples/login.html](examples/login.html) yields refs for the known form controls, and `browser_click` on a missing selector returns `isError: true` with a stable `structuredContent.error.code`.
|
|
35
|
+
- test: smoke tests for the CLI binary in [tests/cli-smoke.test.ts](tests/cli-smoke.test.ts) — spawn `node bin/craftdriver.mjs --ephemeral` as a child process, pipe a script of commands via stdin, and assert on the JSON-per-line output. Three cases: a full login flow against [examples/login.html](examples/login.html), a `snapshot` call that verifies refs + roles for known controls, and an error-code path that confirms `exists`/`text` against a missing selector returns `ok:false` with `code: NO_MATCH` plus a non-zero exit. Requires `npm run build` first (the bin shim loads `dist/cli/index.js`).
|
|
36
|
+
- docs: align README / docs / skill on the three AI-agent surfaces (CLI, skill pack, MCP) and `craftdriver init` rules files. Fixed stale "13 tools" copy in [README.md](README.md), [docs/mcp.md](docs/mcp.md), and [skills/craftdriver/SKILL.md](skills/craftdriver/SKILL.md) — MCP exposes 14 tools. Added a Skill pack subsection to [README.md](README.md) and [docs/cli.md](docs/cli.md) so the tarball-shipped `skills/craftdriver/` files are discoverable from the front door.
|
|
37
|
+
|
|
38
|
+
- feat: sanitized DOM snapshot with refs — `craftdriver snapshot` (CLI) and `browser_snapshot` (MCP tool) return a flat accessibility-tree summary of the active page where each visible interactive element gets a stable ref (`e1`, `e2`, …). Use `ref=eN` as a selector for the next command and refs auto-resolve to the right DOM node (`[data-craftdriver-ref="eN"]`) with full auto-wait — no selector hallucination, no per-element `find` round-trips, and 5-char selectors instead of 26-char `role=button[name=…]` expressions. Refs invalidate on the next snapshot or navigation; stale refs fail with `NO_MATCH`. The same renderer powers the post-action snapshot diff in MCP, so refs in the diff are usable in the very next call. Inspired by [Playwright CLI](https://playwright.dev/agent-cli/introduction). [src/cli/snapshot.ts](src/cli/snapshot.ts), [src/cli/selector.ts](src/cli/selector.ts).
|
|
39
|
+
|
|
40
|
+
- feat: MCP server — `craftdriver mcp` runs a stdio JSON-RPC 2.0 server speaking [Model Context Protocol](https://modelcontextprotocol.io) `2024-11-05`, so MCP-aware hosts (Claude Desktop / Code, Cursor, Windsurf, Zed, Goose, Gemini CLI) can drive a real browser without spawning a CLI per turn. 14 schema-typed tools (`browser_navigate`, `browser_click`, `browser_fill`, `browser_press`, `browser_hover`, `browser_find`, `browser_exists`, `browser_wait`, `browser_read`, `browser_pages`, `browser_snapshot`, `browser_screenshot`, `browser_status`, `browser_advanced_eval`) share the dispatcher and selector syntax with the CLI; mutating tools additionally return a **compact a11y snapshot diffed from the previous turn** (≤ 80 nodes, ref + role + accessible name + locator hint, full snapshot on first call / URL change, set-difference thereafter) so the agent sees what the action changed without a follow-up read. Errors come back as MCP `isError: true` content with the stable `code` field in `structuredContent.error.code`; JSON-RPC `error` is reserved for protocol-level failures. **Token-efficient artifact spilling** ([src/cli/mcp/artifacts.ts](src/cli/mcp/artifacts.ts)) writes screenshots, large snapshots, and large tool results to a per-session directory (`<tmpdir>/craftdriver-mcp-<pid>-<stamp>/`) instead of inlining them as content blocks — the agent gets a short preview plus the absolute path, paying no image tokens for screenshots and bounded text tokens for everything else. Configurable via `$CRAFTDRIVER_MCP_ARTIFACTS_DIR` and `$CRAFTDRIVER_MCP_SPILL_BYTES` (default 2 KB). Hand-rolled (no `@modelcontextprotocol/sdk` dep) — [src/cli/mcp/server.ts](src/cli/mcp/server.ts), [src/cli/mcp/tools.ts](src/cli/mcp/tools.ts), [src/cli/snapshot.ts](src/cli/snapshot.ts). See [docs/mcp.md](docs/mcp.md).
|
|
41
|
+
|
|
42
|
+
- feat: `craftdriver init <flavor>` — writes a short, opinionated agent-guide file into the current project so AI assistants pick up craftdriver conventions (selector preference, auto-waiting, error codes, CLI usage) on every turn. Flavors: `agents` (`AGENTS.md`), `copilot` (`.github/copilot-instructions.md`), `claude` (`CLAUDE.md`), `cursor` (`.cursor/rules/craftdriver.mdc` with frontmatter), `gemini` (`GEMINI.md`), `all`. One shared body, N filenames — [src/cli/init.ts](src/cli/init.ts). `--force` overwrites, `--dry-run` previews. Per-project by design; never writes to a user's home directory.
|
|
43
|
+
- docs: CLI section in [README.md](README.md) plus dedicated [docs/cli.md](docs/cli.md) reference and an agent-facing [skills/craftdriver/cli.md](skills/craftdriver/cli.md). `docs/` and `skills/` are now shipped in the npm tarball alongside `dist/` and `bin/` so installs of `craftdriver` carry their own docs and SKILL pack for in-repo agents. Added `cli`, `agent`, `ai` keywords and wired `--version` to print the package version.
|
|
44
|
+
- feat: agent-first CLI v1 — new `craftdriver` binary (re-exported via `bin/craftdriver.mjs`, wired through `package.json#bin`) wraps the public Browser API for shell agents and humans. Daemon mode keeps a long-lived browser behind a Unix-domain socket at `~/.craftdriver/sock` (override with `$CRAFTDRIVER_SOCKET`) so successive commands share state; `--ephemeral` reads a command-per-line script from stdin in a single short-lived session for sandboxed cloud agents. Default per-call timeout is 5 s on the agent surface (vs. 30 s in the library); override with `--timeout` or `$CRAFTDRIVER_AGENT_TIMEOUT`. Selectors follow `kind=value` syntax (`css=`, `xpath=`, `role=button[name=Submit]`, `text=Sign In`, `label=`, `placeholder=`, `testid=`, …); CSS is the default when no prefix is given. Output is JSON when stdout is piped, pretty when on a TTY (force with `--json` / `--pretty`). Commands: `go`, `find` (with `--all/--limit/--offset` and stable `next_offset`), `click`, `fill`, `press`, `hover`, `text`, `attr`, `value`, `is`, `wait`, `exists` (0-wait probe, exit 1 when missing), `pages`, `screenshot`, `eval`, `back`/`forward`/`reload`/`status`/`quit`, `daemon start|status|stop`. Every error carries the existing stable `code` field plus a one-line `hint:`. Run `npm run build` then `craftdriver --help`.
|
|
45
|
+
- feat: stable, machine-readable error codes — every error thrown from the public API is now a `CraftdriverError` (re-exported from [src/index.ts](src/index.ts)) carrying a stable `code`, JSON-serializable `detail`, and an optional `hint`. Codes distinguish the three common probe failures (`NO_MATCH` vs `TIMEOUT_WAITING_VISIBLE` vs `TIMEOUT_WAITING_STATE`) plus `TIMEOUT_WAITING_LOAD`, `EXPECT_MISMATCH`, `EVAL_THREW`, `EVAL_BAD_ARG`, `INVALID_ARGUMENT`, `UNSUPPORTED`, `STATE_INVALID`, `A11Y_VIOLATIONS`, and more. `instanceof Error` still holds; existing message-based catches keep working. `A11yError` now extends `CraftdriverError`. See [docs/error-codes.md](docs/error-codes.md).
|
|
46
|
+
- feat: generated [docs/api-reference.md](docs/api-reference.md) — one canonical table of every public symbol re-exported from [src/index.ts](src/index.ts), produced by `scripts/gen-api-reference.mjs`. Run `npm run docs:api` to regenerate; `npm run docs:api:check` and the vitest guard `tests/api-reference.test.ts` fail when the file is out of sync.
|
|
47
|
+
- feat: tiered agent-facing SKILL content under `skills/craftdriver/` — `SKILL.md` (always-on, ≤ 500 tokens, decision rules + pointers), `cheatsheet.md` (command-by-command reference for writing tests), `patterns.md` (worked recipes for login / upload / network-wait / a11y / tracing / clock).
|
|
48
|
+
- chore: `Locator` simple-path shortcut removed in favour of a single polling code path. No public behaviour change beyond improved error-code accuracy.
|
|
49
|
+
|
|
50
|
+
- feat: tracing — `browser.startTrace({ outDir, … })` streams a chronological log of `action` / `console` / `error` / `request` / `response` / `navigation` / `screenshot` events to `outDir/trace.ndjson`, one JSON value per line, written synchronously per event so a thrown `expect` cannot lose data. Response events include `mimeType` and `fromCache` when available. Screenshots land in `outDir/screenshots/NNNN.png` as captures resolve. `browser.stopTrace()` drains pending captures and writes a closing `meta` line — but is purely cosmetic: partial files left behind by a throwing test (or `browser.quit()`) are still valid NDJSON. Pillars toggled independently (`actions`, `network`, `console`, `screenshots`); screenshots are evidence-driven (one before each action, one on each page error) with `screenshots: 'auto'` default. Actions instrumented: `navigateTo` / `goBack` / `goForward` / `reload` / `setContent` / `click` / `fill` / `clear` / `acceptDialog` / `dismissDialog`. BiDi-only. See [docs/tracing.md](docs/tracing.md).
|
|
51
|
+
- feat: `browser.emulate({...})` — override `prefers-color-scheme`, `prefers-reduced-motion`, `forced-colors`, `locale`, `timezoneId`, and `offline` for the current session. `locale` and `timezoneId` use BiDi `emulation.setLocaleOverride` / `setTimezoneOverride` and work cross-browser; media features and `offline` use the BiDi+CDP bridge and are Chromium-only with a clear error on Firefox. See [docs/emulation.md](docs/emulation.md).
|
|
52
|
+
|
|
53
|
+
- feat: virtual clock — `browser.clock.install()`, `tick()`, `fastForward()`, `setFixedTime()`, `setSystemTime()`, `runFor()`, `uninstall()`. Fakes `Date`, `performance.now`, `setTimeout`, `setInterval`, and `requestAnimationFrame` via an injected shim. Persists across navigations via BiDi preload script. See [docs/clock.md](docs/clock.md).
|
|
54
|
+
|
|
55
|
+
- feat: accessibility audits — `browser.a11y.audit()` / `check()` plus element-scoped `element.a11y.*` and `locator.a11y.*` wrap axe-core (shipped as a direct dependency — works out of the box). Ergonomic `disableRules: string[]` shorthand, `rules` whitelist, `minImpact` filter, and `axeOptions` escape hatch. `check()` throws an `A11yError` listing every violation with its help URL. See [docs/accessibility.md](docs/accessibility.md).
|
|
56
|
+
- feat: full-page screenshots — `browser.screenshot({ fullPage: true })` captures the entire scrollable document via BiDi `browsingContext.captureScreenshot` with `origin: 'document'`. Viewport remains the default. `fullPage` and `selector` are mutually exclusive. See [docs/screenshots.md](docs/screenshots.md).
|
|
57
|
+
- feat: history navigation — `browser.goBack()` / `goForward()` / `reload()`, mirrored on `Page`. BiDi `browsingContext.traverseHistory` / `reload` with Classic `/back`, `/forward`, `/refresh` fallback.
|
|
58
|
+
- feat: `page.content()` and `browser.content()` return the full document HTML; `page.setContent(html, { waitUntil })` and `browser.setContent(...)` replace the document via a `data:text/html` navigation.
|
|
59
|
+
- feat: `browser.setViewportSize({ width, height })` resizes the layout viewport (BiDi `browsingContext.setViewport`) with a Classic `setWindowRect` fallback.
|
|
60
|
+
- feat: `browser.grantPermissions([...], { origin?, state? })` and `browser.clearPermissions(...)` over BiDi `permissions.setPermission`. BiDi-only.
|
|
61
|
+
- feat: `browser.setGeolocation({ latitude, longitude, accuracy? } | null)` over BiDi `emulation.setGeolocationOverride`. BiDi-only.
|
|
62
|
+
- feat: `browser.on('request' | 'response', cb)` — persistent network listeners returning an `off()` function. Sits next to the existing one-shot `waitForRequest` / `waitForResponse`.
|
|
63
|
+
- **breaking** (pre-1.0): `element.tag()` renamed to `element.tagName()` for symmetry with `By.tagName()` and the DOM `Element.tagName` property.
|
|
64
|
+
- **breaking** (pre-1.0): screenshot APIs converged on a single options-bag form. `browser.screenshot(opts?: { path?, selector?, timeout? })`, `element.screenshot(opts?: { path?, timeout? })`. `browser.saveScreenshot(...)` removed.
|
|
65
|
+
- **breaking** (pre-1.0): `KeyboardController` / `MouseController` renamed to `Keyboard` / `Mouse` for Playwright parity. Re-exported from `src/index.ts`.
|
|
66
|
+
- feat: `By.text(text, { exact: false })` is now sugar for `By.partialText(text)`. `getByText` and `By.text` share one vocabulary.
|
|
67
|
+
- docs: dedicated [docs/dialogs.md](docs/dialogs.md); browser-api dialogs section trimmed to a callout. Attached-vs-visible gloss added to the waiting section. `By.role({ includeHidden })` documented in [docs/selectors.md](docs/selectors.md). `browser.actions()` gets a runnable shift-drag example in [docs/browser-api.md](docs/browser-api.md).
|
|
68
|
+
- docs: BiDi-feature matrix added at the top of [docs/bidi-features.md](docs/bidi-features.md), timeout-defaults table added in [docs/browser-api.md](docs/browser-api.md), mobile-emulation Chrome-only callout promoted to the top of [docs/mobile-emulation.md](docs/mobile-emulation.md), cleanup / failure-recovery guidance added to [docs/browser-api.md](docs/browser-api.md).
|
|
69
|
+
- feat: re-export `Keyboard` and `Mouse` (was `KeyboardController` / `MouseController`) from [src/index.ts](src/index.ts).
|
|
70
|
+
- **breaking** (pre-1.0): remove deprecated aliases `Browser.waitForNewContext()` and `LogMonitor.getConsoleLogs()`. One API per concept; no aliases pre-1.0.
|
|
71
|
+
- **breaking** (pre-1.0): rename `By.nameAttr` → `By.name` and `By.tag` → `By.tagName` for parity with Selenium's canonical `By` API.
|
|
72
|
+
- **breaking** (pre-1.0): drop `BiDiSession` / `BiDiConnection` from the public surface.
|
|
73
|
+
- docs: close coverage gaps — `By.attr` / `dataAttr` / `aria` / `partialText` / `altText` / `title` / `testId` documented in [docs/selectors.md](docs/selectors.md); `getValue` / `getAttribute` get full signature blocks; `actions()` and `isBiDiEnabled()` documented; custom-binary / port configuration via `ChromeService` / `FirefoxService` documented in [docs/getting-started.md](docs/getting-started.md).
|
|
74
|
+
- docs: Browser properties table now lists the correct exported types `NetworkInterceptor` / `LogMonitor`.
|
|
75
|
+
- feat: Firefox support via geckodriver — `Browser.launch({ browserName: 'firefox' })` spawns geckodriver, negotiates BiDi, and runs the full test suite under `BROWSER_NAME=firefox`. `FirefoxService` exported; respects `GECKODRIVER_PATH` / `GECKODRIVER_FILEPATH`; mobile emulation throws a clear error on Firefox.
|
|
76
|
+
- feat: lightweight tracing — `browser.startTrace()` / `stopTrace(path)` write a JSON bundle of console, error, request, response and navigation events plus optional periodic screenshots. BiDi-only. See [docs/tracing.md](docs/tracing.md).
|
|
77
|
+
- feat: `browser.activePage()` returns the focused page in `defaultContext` — explicit handle for the implicit target of `browser.click()` / `find()` / etc.
|
|
78
|
+
- feat: `BrowserContext` API — real BiDi user contexts (isolated profiles). `browser.newContext()`, `browser.contexts()`, `browser.defaultContext`; `BrowserContext.newPage()`, `pages()`, `waitForPage()`, `close()`. BiDi-only.
|
|
79
|
+
- feat: `Page` API — `browser.openPage({ url?, type? })` (BiDi-only), `browser.pages()`, `browser.waitForPage(action)`.
|
|
80
|
+
- feat: iframe support — `browser.frame(selector)` returns a `Frame` scoped to the iframe; `browser.frames()` returns all frames.
|
|
81
|
+
- feat: dialog handling — `browser.waitForDialog()`, `onDialog()`, `acceptDialog()`, `dismissDialog()`; `Dialog` / `DialogType` types exported.
|
|
82
|
+
- feat: configurable default timeouts via `setDefaultTimeout` / `setDefaultNavigationTimeout`.
|
|
83
|
+
- feat: composable `Locator` API — `browser.locator()`, `.nth()`, `.first()`, `.last()`, `.filter()`, `.count()`, `.all()`, `browser.findAll()`.
|
|
84
|
+
- feat: `browser.evaluate()`, `element.evaluate()`, and `browser.addInitScript()`.
|
|
85
|
+
- feat: `browser.waitForResponse()` / `waitForRequest()` for observing network traffic.
|
|
86
|
+
- feat: `element.setInputFiles()` for file uploads; `browser.waitForDownload()` for downloads.
|
|
87
|
+
- feat: BiDi-first navigation — `navigateTo(url, { waitUntil })`, `waitForLoadState()`, `network.waitForNetworkIdle()`; BiDi enabled by default.
|
|
88
|
+
|
|
89
|
+
### Features
|
|
90
|
+
|
|
91
|
+
- mobile emulation ([36ab952](https://github.com/dtopuzov/craftdriver/commit/36ab9527eb8c111565ad80c56e82644b52b39511))
|
|
92
|
+
- network and session management ([d23057d](https://github.com/dtopuzov/craftdriver/commit/d23057d94de842e9a670246de0d8190a429a0657))
|
|
93
|
+
|
|
94
|
+
## [0.0.3](https://github.com/dtopuzov/craftdriver/compare/v0.0.2...v0.0.3) (2026-02-03)
|
|
95
|
+
|
|
96
|
+
### Bug Fixes
|
|
97
|
+
|
|
98
|
+
- lint task ([7b83099](https://github.com/dtopuzov/craftdriver/commit/7b8309973f8660f3ec57548c030e6da7cd173748))
|
|
99
|
+
|
|
100
|
+
## [0.0.2](https://github.com/dtopuzov/craftdriver/compare/v0.0.1...v0.0.2) (2026-02-03)
|
|
101
|
+
|
|
102
|
+
### Bug Fixes
|
|
103
|
+
|
|
104
|
+
- improve API consistency and test reliability ([495c49a](https://github.com/dtopuzov/craftdriver/commit/495c49a1fc68451cbdc27298c04d51e5f6c6f016))
|
package/README.md
CHANGED
|
@@ -1,32 +1,208 @@
|
|
|
1
1
|
# Craftdriver 🍺🍺🍺
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/dtopuzov/craftdriver/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/craftdriver)
|
|
5
|
+
[](./LICENSE)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Crafted Node.js browser automation built directly on the WebDriver protocols.
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**Playwright's ergonomics. WebDriver's standards-compliance. AI-ready.**
|
|
10
|
+
|
|
11
|
+
Auto-waiting, semantic locators, network mocking, session management, mobile emulation, and more.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
Based on W3C specs, so your tests stay stable across real browsers.
|
|
14
|
+
|
|
15
|
+
Ships AI productivity tooling out of the box: CLI, MCP, assistant bootstrap files, and packaged skill docs — no extra packages required.
|
|
16
|
+
|
|
17
|
+
## Getting started
|
|
10
18
|
|
|
11
19
|
```bash
|
|
12
|
-
npm install craftdriver
|
|
20
|
+
npm install craftdriver --save-dev
|
|
13
21
|
```
|
|
14
22
|
|
|
23
|
+
That's it.
|
|
24
|
+
No drivers, no extra steps to install browsers.
|
|
25
|
+
|
|
15
26
|
Quick example:
|
|
16
27
|
|
|
17
28
|
```ts
|
|
18
29
|
import { Browser } from 'craftdriver';
|
|
19
30
|
|
|
20
31
|
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
21
|
-
|
|
22
|
-
await browser.
|
|
23
|
-
await browser.fill('#
|
|
32
|
+
|
|
33
|
+
await browser.navigateTo('https://example.com');
|
|
34
|
+
await browser.fill('#username', 'alice');
|
|
35
|
+
await browser.fill('#password', 'hunter2');
|
|
24
36
|
await browser.click('#submit');
|
|
25
|
-
await browser.expect('#result').toHaveText('Welcome
|
|
37
|
+
await browser.expect('#result').toHaveText('Welcome alice');
|
|
38
|
+
|
|
26
39
|
await browser.quit();
|
|
27
40
|
```
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
Mobile emulation:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const browser = await Browser.launch({
|
|
46
|
+
browserName: 'chrome',
|
|
47
|
+
mobileEmulation: 'iPhone 14',
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Network mocking (BiDi is on by default):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const browser = await Browser.launch({ browserName: 'chrome' });
|
|
55
|
+
|
|
56
|
+
await browser.network.mock('**/api/users', {
|
|
57
|
+
status: 200,
|
|
58
|
+
body: { users: [] },
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Session persistence:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
await browser.saveState('./session.json');
|
|
66
|
+
|
|
67
|
+
// Later: restore cookies and localStorage in one shot
|
|
68
|
+
const browser2 = await Browser.launch({
|
|
69
|
+
browserName: 'chrome',
|
|
70
|
+
storageState: './session.json',
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## AI Productivity Tooling
|
|
75
|
+
|
|
76
|
+
`craftdriver` ships three surfaces for AI agents (Copilot, Claude
|
|
77
|
+
Code, Cursor, Codex, Gemini CLI, …), in addition to the library:
|
|
78
|
+
|
|
79
|
+
If you want Craftdriver to work well with coding agents on day one, this is the stack: a shell-friendly CLI, an MCP server for tool-calling hosts, packaged skills, and `craftdriver init` to drop the right repo instructions into place.
|
|
80
|
+
|
|
81
|
+
- **CLI** (this section) — a `craftdriver` binary for shell-capable agents.
|
|
82
|
+
- **Skill pack** — ready-to-load rules at [skills/craftdriver/](./skills/craftdriver/),
|
|
83
|
+
shipped in the npm tarball.
|
|
84
|
+
- **MCP server** — stdio JSON-RPC for sandboxed hosts. See below.
|
|
85
|
+
- **`craftdriver init`** — drop per-project rules files for the popular
|
|
86
|
+
assistants (Copilot, Claude, Cursor, Gemini, Codex / AGENTS.md).
|
|
87
|
+
|
|
88
|
+
All four share the same dispatcher, error codes, and selector syntax.
|
|
89
|
+
|
|
90
|
+
The `craftdriver` binary is for shell scripts, ad-hoc exploration, and
|
|
91
|
+
shell-capable AI agents. It wraps the same Browser API as the library,
|
|
92
|
+
with fail-fast defaults tuned for probing instead of stable test runs.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# terminal 1 — keep a long-lived browser around
|
|
96
|
+
npx craftdriver daemon start
|
|
97
|
+
|
|
98
|
+
# terminal 2 — drive it
|
|
99
|
+
npx craftdriver go http://127.0.0.1:8080/login.html
|
|
100
|
+
npx craftdriver fill '#username' alice
|
|
101
|
+
npx craftdriver fill '#password' hunter2
|
|
102
|
+
npx craftdriver click 'button[type=submit]'
|
|
103
|
+
npx craftdriver wait '#result' --state visible
|
|
104
|
+
npx craftdriver text '#result'
|
|
105
|
+
|
|
106
|
+
npx craftdriver daemon stop
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
State (page, cookies, storage) survives between calls — the CLI feels
|
|
110
|
+
like a REPL for the browser. For sandboxed environments that can't keep
|
|
111
|
+
a daemon, pipe a script through `--ephemeral` instead.
|
|
112
|
+
|
|
113
|
+
Selectors are CSS by default; switch with a `prefix=value` form
|
|
114
|
+
(`role=button[name=Submit]`, `text=Sign In`, `label=Email`,
|
|
115
|
+
`placeholder=...`, `testid=...`, `xpath=...`). Output is pretty on a
|
|
116
|
+
TTY and JSON when piped. Every error carries the same stable `code` and
|
|
117
|
+
`hint:` as the library. See [docs/cli.md](./docs/cli.md) for the full
|
|
118
|
+
command list and selector reference.
|
|
119
|
+
|
|
120
|
+
### Teach your AI assistant
|
|
121
|
+
|
|
122
|
+
`craftdriver init <flavor>` writes a short rules file into your project
|
|
123
|
+
so Copilot, Claude, Cursor, Codex, Gemini, OpenCode, Aider, …
|
|
124
|
+
pick up the right conventions on every turn (selector preference,
|
|
125
|
+
auto-waiting, error codes, CLI usage). Per-project, checked into git,
|
|
126
|
+
zero runtime cost.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx craftdriver init copilot # .github/copilot-instructions.md
|
|
130
|
+
npx craftdriver init claude # CLAUDE.md
|
|
131
|
+
npx craftdriver init cursor # .cursor/rules/craftdriver.mdc
|
|
132
|
+
npx craftdriver init gemini # GEMINI.md
|
|
133
|
+
npx craftdriver init agents # AGENTS.md (Codex, OpenCode, Aider, Amp, Cursor)
|
|
134
|
+
npx craftdriver init all # every file above
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use `--force` to overwrite existing files, `--dry-run` to preview.
|
|
138
|
+
|
|
139
|
+
### Skill pack
|
|
140
|
+
|
|
141
|
+
For agents that load skills explicitly (Claude Code's Skills system,
|
|
142
|
+
Copilot agent customization, custom orchestrators), the npm tarball
|
|
143
|
+
ships a tiered skill pack under [skills/craftdriver/](./skills/craftdriver/):
|
|
144
|
+
|
|
145
|
+
- [`SKILL.md`](./skills/craftdriver/SKILL.md) — always-on, ≤ 500 tokens.
|
|
146
|
+
Decision rules: selector preference order, error-code-first error
|
|
147
|
+
handling, the auto-wait contract, when to reach for CLI/MCP.
|
|
148
|
+
- [`cheatsheet.md`](./skills/craftdriver/cheatsheet.md) — command-by-command
|
|
149
|
+
reference for writing tests.
|
|
150
|
+
- [`patterns.md`](./skills/craftdriver/patterns.md) — worked recipes
|
|
151
|
+
(login, upload, network-wait, a11y, tracing, virtual clock).
|
|
152
|
+
- [`cli.md`](./skills/craftdriver/cli.md) — agent-facing CLI reference.
|
|
153
|
+
|
|
154
|
+
Point your agent at `node_modules/craftdriver/skills/craftdriver/SKILL.md`
|
|
155
|
+
(or copy the file into your project) and the rest is loaded on demand.
|
|
156
|
+
|
|
157
|
+
### MCP server
|
|
158
|
+
|
|
159
|
+
For agents that talk [Model Context Protocol](https://modelcontextprotocol.io)
|
|
160
|
+
(Claude Desktop / Code, Cursor, Windsurf, Zed, Goose, Gemini CLI, …),
|
|
161
|
+
`craftdriver mcp` exposes the same dispatcher as a stdio JSON-RPC
|
|
162
|
+
server with 14 schema-typed tools. Mutating tools return a **compact
|
|
163
|
+
a11y snapshot diffed from the previous turn** — the agent sees what
|
|
164
|
+
changed on the page without a follow-up read.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Claude Code
|
|
168
|
+
claude mcp add craftdriver -- npx -y craftdriver mcp
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```jsonc
|
|
172
|
+
// Cursor / Windsurf / Zed
|
|
173
|
+
{
|
|
174
|
+
"mcpServers": {
|
|
175
|
+
"craftdriver": { "command": "npx", "args": ["-y", "craftdriver", "mcp"] }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
See [docs/mcp.md](./docs/mcp.md) for the full tool list, install
|
|
181
|
+
snippets for every host, and the snapshot format.
|
|
182
|
+
|
|
183
|
+
## Feature Guide
|
|
184
|
+
|
|
185
|
+
One table is enough here: what Craftdriver does, and where to learn the exact API.
|
|
186
|
+
|
|
187
|
+
| Area | What you get | Learn more |
|
|
188
|
+
| --- | --- | --- |
|
|
189
|
+
| Getting started | Install, launch a browser, write the first test | [Getting Started](./docs/getting-started.md) |
|
|
190
|
+
| Driver management | Zero-config driver download, cache behavior, env vars, offline mode | [Driver Configuration](./docs/driver-configuration.md) |
|
|
191
|
+
| Browser control | Navigation, tabs, popups, iframes, content helpers, evaluate, init scripts | [Browser API](./docs/browser-api.md) |
|
|
192
|
+
| Elements and locators | CSS, XPath, text, role, label, test id, and composable `locator()` chains | [Selectors](./docs/selectors.md) |
|
|
193
|
+
| Element actions | Click, fill, upload, inspect, and interact through element handles | [Element API](./docs/element-api.md) |
|
|
194
|
+
| Assertions and auto-waiting | Built-in `expect(...)`, retries, visibility, text, attributes, and timing behavior | [Assertions](./docs/assertions.md) |
|
|
195
|
+
| Keyboard and mouse | Low-level key presses, mouse movement, hover, drag, and pointer input | [Keyboard & Mouse](./docs/keyboard-mouse.md) |
|
|
196
|
+
| Dialogs | `alert`, `confirm`, `prompt`, and `beforeunload` handling | [Dialogs](./docs/dialogs.md) |
|
|
197
|
+
| Sessions and storage | Cookies, localStorage, save/load state, persistent flows | [Session Management](./docs/session-management.md) |
|
|
198
|
+
| Screenshots | Page and element screenshots for tests and debugging | [Screenshots](./docs/screenshots.md) |
|
|
199
|
+
| Mobile and emulation | Device presets, viewport emulation, locale, timezone, offline, reduced motion | [Mobile Emulation](./docs/mobile-emulation.md), [Emulation](./docs/emulation.md) |
|
|
200
|
+
| Browser contexts | Isolated profiles for multi-user and multi-session testing | [Browser Contexts](./docs/browser-context.md) |
|
|
201
|
+
| BiDi features | Network mocking, request/response listeners, console logs, JS errors | [BiDi Features](./docs/bidi-features.md) |
|
|
202
|
+
| Tracing and debugging | Crash-resilient NDJSON traces and evidence screenshots | [Tracing](./docs/tracing.md) |
|
|
203
|
+
| Accessibility | Built-in axe-core audits for page, element, and locator scopes | [Accessibility](./docs/accessibility.md) |
|
|
204
|
+
| Virtual time | Fake `Date`, `setTimeout`, and `setInterval` for time-sensitive flows | [Virtual Clock](./docs/clock.md) |
|
|
205
|
+
| Agent surfaces | Shell CLI, MCP server, assistant bootstrap, and packaged skill files | [CLI](./docs/cli.md), [MCP server](./docs/mcp.md), [Skill pack](./skills/craftdriver/SKILL.md) |
|
|
30
206
|
|
|
31
207
|
## Contributing
|
|
32
208
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* craftdriver — CLI entry shim.
|
|
4
|
+
*
|
|
5
|
+
* Resolves the compiled CLI in `dist/cli/index.js` and invokes its
|
|
6
|
+
* `main()`. If the dist tree is missing, fail with a clear message
|
|
7
|
+
* pointing at `npm run build` rather than a stack trace.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
|
+
import { dirname, resolve } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const distEntry = resolve(here, '..', 'dist', 'cli', 'index.js');
|
|
15
|
+
|
|
16
|
+
if (!existsSync(distEntry)) {
|
|
17
|
+
process.stderr.write(
|
|
18
|
+
'craftdriver: compiled CLI not found at ' + distEntry + '\n' +
|
|
19
|
+
'hint: run `npm run build` first (the bin shim loads `dist/cli/index.js`).\n'
|
|
20
|
+
);
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const mod = await import(pathToFileURL(distEntry).href);
|
|
25
|
+
const rc = await mod.main(process.argv.slice(2));
|
|
26
|
+
process.exit(typeof rc === 'number' ? rc : 0);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Response } from './protocol.js';
|
|
2
|
+
export interface ClientOptions {
|
|
3
|
+
socketPath?: string;
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class DaemonClient {
|
|
7
|
+
private nextId;
|
|
8
|
+
private readonly socketPath;
|
|
9
|
+
private readonly timeoutMs;
|
|
10
|
+
constructor(opts?: ClientOptions);
|
|
11
|
+
/** Returns true if a daemon socket is reachable. */
|
|
12
|
+
static isRunning(socketPath?: string): Promise<boolean>;
|
|
13
|
+
/** Returns the daemon PID, if a PID file exists and the process is alive. */
|
|
14
|
+
static getPid(pidPath?: string): number | null;
|
|
15
|
+
send(cmd: string, args?: Record<string, unknown>): Promise<Response>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cli/client.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAW,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,IAAI,GAAE,aAAkB;IAKpC,oDAAoD;WACvC,SAAS,CAAC,UAAU,GAAE,MAA2B,GAAG,OAAO,CAAC,OAAO,CAAC;IAWjF,6EAA6E;IAC7E,MAAM,CAAC,MAAM,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,GAAG,IAAI;IAYzD,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAsC/E"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny IPC client: speaks the line-delimited JSON protocol over a
|
|
3
|
+
* Unix socket. Each call opens a fresh connection — daemon-side state
|
|
4
|
+
* (active page, browser) survives between calls.
|
|
5
|
+
*/
|
|
6
|
+
import net from 'net';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { DAEMON_SOCKET_PATH, DAEMON_PID_PATH } from './defaults.js';
|
|
9
|
+
export class DaemonClient {
|
|
10
|
+
nextId = 1;
|
|
11
|
+
socketPath;
|
|
12
|
+
timeoutMs;
|
|
13
|
+
constructor(opts = {}) {
|
|
14
|
+
this.socketPath = opts.socketPath ?? DAEMON_SOCKET_PATH;
|
|
15
|
+
this.timeoutMs = opts.timeoutMs ?? 60_000;
|
|
16
|
+
}
|
|
17
|
+
/** Returns true if a daemon socket is reachable. */
|
|
18
|
+
static async isRunning(socketPath = DAEMON_SOCKET_PATH) {
|
|
19
|
+
if (!fs.existsSync(socketPath))
|
|
20
|
+
return false;
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const sock = net.createConnection(socketPath);
|
|
23
|
+
const done = (v) => { sock.destroy(); resolve(v); };
|
|
24
|
+
sock.once('connect', () => done(true));
|
|
25
|
+
sock.once('error', () => done(false));
|
|
26
|
+
sock.setTimeout(500, () => done(false));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Returns the daemon PID, if a PID file exists and the process is alive. */
|
|
30
|
+
static getPid(pidPath = DAEMON_PID_PATH) {
|
|
31
|
+
try {
|
|
32
|
+
const raw = fs.readFileSync(pidPath, 'utf8').trim();
|
|
33
|
+
const pid = Number(raw);
|
|
34
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return pid;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async send(cmd, args = {}) {
|
|
49
|
+
const req = { id: this.nextId++, cmd, args };
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
const sock = net.createConnection(this.socketPath);
|
|
52
|
+
let buf = '';
|
|
53
|
+
const timer = setTimeout(() => {
|
|
54
|
+
sock.destroy();
|
|
55
|
+
reject(new Error(`daemon call timed out after ${this.timeoutMs}ms`));
|
|
56
|
+
}, this.timeoutMs);
|
|
57
|
+
sock.on('error', (err) => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
reject(err);
|
|
60
|
+
});
|
|
61
|
+
sock.on('connect', () => {
|
|
62
|
+
sock.write(JSON.stringify(req) + '\n');
|
|
63
|
+
});
|
|
64
|
+
sock.on('data', (chunk) => {
|
|
65
|
+
buf += chunk.toString('utf8');
|
|
66
|
+
const idx = buf.indexOf('\n');
|
|
67
|
+
if (idx >= 0) {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
const line = buf.slice(0, idx);
|
|
70
|
+
sock.end();
|
|
71
|
+
try {
|
|
72
|
+
resolve(JSON.parse(line));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
reject(new Error('invalid response from daemon: ' + e.message));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
sock.on('end', () => {
|
|
80
|
+
if (!buf.includes('\n')) {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
reject(new Error('daemon closed connection without sending a response'));
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/cli/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LaunchOptions } from '../lib/browser.js';
|
|
2
|
+
interface DaemonOptions {
|
|
3
|
+
socketPath?: string;
|
|
4
|
+
pidPath?: string;
|
|
5
|
+
launch: LaunchOptions;
|
|
6
|
+
}
|
|
7
|
+
export declare function runDaemon(opts: DaemonOptions): Promise<void>;
|
|
8
|
+
export declare function toWireError(err: unknown): {
|
|
9
|
+
code: string;
|
|
10
|
+
message: string;
|
|
11
|
+
hint?: string;
|
|
12
|
+
detail?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAMvD,UAAU,aAAa;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FlE;AAUD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAc5H"}
|