{"protocol":"clank-doc/1","frameworkVersion":"0.13.0","slug":"browser-journeys","title":"Semantic browser journeys","description":"Clank journeys let a person or agent describe acceptance behavior as bounded data, then replay it in a real Chrome browser. They address controls by stable agentId or native id , so tests follow the same semantic surface an agent sees inste","group":{"id":"agents","title":"Agents and generation"},"url":"https://docs.clank.run/docs/browser-journeys","source":"docs/browser-journeys.md","headings":["A complete journey","Operations","CLI and CI","Authoring semantic controls","Programmatic driver contract"],"tableOfContents":[{"id":"a-complete-journey","title":"A complete journey","level":2},{"id":"operations","title":"Operations","level":2},{"id":"cli-and-ci","title":"CLI and CI","level":2},{"id":"authoring-semantic-controls","title":"Authoring semantic controls","level":2},{"id":"programmatic-driver-contract","title":"Programmatic driver contract","level":2}],"markdown":"# Semantic browser journeys\n\nClank journeys let a person or agent describe acceptance behavior as bounded data, then replay it\nin a real Chrome browser. They address controls by stable `agentId` or native `id`, so tests follow\nthe same semantic surface an agent sees instead of depending on CSS classes, DOM nesting, pixels,\nor a separate browser package.\n\nGenerated blueprint applications include `journeys/smoke.json` and a `test:journey` script. Start\nthe application in one terminal and replay the journey in another:\n\n```sh\nnpm run dev\nnpm run test:journey\n```\n\nThe CLI defaults to `http://127.0.0.1:3000`, launches installed Chrome or Chromium with a fresh\ntemporary profile, keeps the browser sandbox enabled, and deletes the profile when the run ends.\nIt returns nonzero when a step, page exception, navigation boundary, or timeout fails.\n\n## A complete journey\n\n```json\n{\n  \"name\": \"Create a task\",\n  \"start\": \"/\",\n  \"viewport\": { \"width\": 390, \"height\": 844 },\n  \"steps\": [\n    { \"input\": { \"target\": \"login-email\", \"value\": \"agent@example.invalid\" } },\n    { \"input\": { \"target\": \"login-password\", \"value\": { \"env\": \"CLANK_TEST_PASSWORD\" } } },\n    { \"activate\": \"login-submit\" },\n    { \"wait\": { \"url\": \"/tasks\", \"text\": \"My tasks\", \"timeoutMs\": 10000 } },\n    { \"input\": { \"target\": \"task-title\", \"value\": \"Review launch plan\" } },\n    { \"activate\": \"task-add\" },\n    { \"wait\": { \"target\": \"task-review-launch-plan\", \"text\": \"Review launch plan\" } },\n    { \"expect\": { \"target\": \"task-review-launch-plan\", \"state\": { \"checked\": false } } },\n    { \"inspect\": \"final task surface\" }\n  ]\n}\n```\n\nSet the login secret only for the process that runs the journey:\n\n```sh\nCLANK_TEST_PASSWORD='test-account-password' clank journey journeys/create-task.json\n```\n\nAn `{ \"env\": \"NAME\" }` value is resolved at execution time. The raw value is never copied into\nthe normalized journey, step output, failure report, screenshot name, or semantic inspection.\nPassword controls reject literal values in the real-browser driver. Use a disposable seeded test\naccount; never automate a production account or commit credentials.\n\n## Operations\n\n| Operation | Contract | Purpose |\n| --- | --- | --- |\n| `visit` | relative path | Navigate without leaving the configured application origin. |\n| `input` | `{ target, value }` | Enter text, numbers, booleans, selections, or a secret reference. |\n| `activate` | stable ID | Click an enabled semantic control. |\n| `expect` | expectation | Check immediately. |\n| `wait` | expectation plus optional `timeoutMs` | Poll through hydration, navigation, and live updates. |\n| `inspect` | bounded label | Attach a value-redacted semantic surface snapshot to the report. |\n\nExpectations can combine visible `text`, a relative `url`, a semantic `target`, and target `state`.\nSupported state is `label`, `role`, `checked`, `expanded`, `disabled`, `readonly`, `invalid`, or\n`value`. Prefer role and behavioral state; assert visible copy only when the wording is itself part\nof the product contract.\n\nJourney definitions allow at most 100 steps. Suites allow at most 50 journeys and source files\nare limited to 1 MiB. Navigation is same-origin, waits and complete runs are bounded, URL query\nstrings are omitted from summary reports, inspected control values are removed, and unexpected\npage exceptions fail the run.\n\n## CLI and CI\n\n```sh\nclank journey journeys/smoke.json --url=http://127.0.0.1:4100\nclank journey journeys/acceptance.json --headed\nclank journey journeys/acceptance.json --output=.clank/journey-report.json --json\nclank journey journeys/acceptance.json --browser=/path/to/chrome\nclank journey journeys/acceptance.json --cdp=http://127.0.0.1:9222\n```\n\n`--cdp` accepts only an HTTP loopback endpoint and rejects redirected or network WebSocket targets;\nit cannot attach to a network browser. `--output` writes an owner-only JSON report atomically and\nadds an owner-only PNG on failure when the run did not resolve secrets. Secret-bearing runs omit\nscreenshots so an application cannot accidentally render a credential into an artifact. JSON output uses\nthe stable `clank-journey-suite/1` envelope around `clank-journey-report/1` reports, making the\nresult suitable for agents and CI annotations.\n\nJSON is the safe format for agent proposals. `.js` and `.mjs` files are supported for trusted\nlocal composition, but they execute as ordinary local code and are not a sandbox.\n\n## Authoring semantic controls\n\n```tsx\n<input\n  agentId=\"task-title\"\n  agentLabel=\"Task title\"\n  bind:value={title}\n/>\n<button\n  agentId=\"task-add\"\n  agentLabel=\"Add task\"\n  agentAction={api.todos.add}\n  onClick={addTodo}\n>\n  Add\n</button>\n```\n\nUse durable product-language IDs, not generated row positions. Server-backed controls should also\nuse the typed backend function reference in `agentAction`; generated contract tests then verify\nthat the UI, browser journey surface, and per-app MCP tools describe the same operation.\n\n## Programmatic driver contract\n\n```ts\nimport { createDomJourneyDriver, defineJourney, runJourney } from \"@clank.run/framework/journey\";\nimport { createAgentSurface } from \"@clank.run/framework/ai\";\n\nconst journey = defineJourney({\n  name: \"Settings open\",\n  steps: [\n    { activate: \"settings-open\" },\n    { wait: { url: \"/settings\" } }\n  ]\n});\n\nconst report = await runJourney(\n  journey,\n  createDomJourneyDriver(window, createAgentSurface(document)),\n  { baseUrl: window.location.origin }\n);\n```\n\n`runJourney` is driver-neutral for deterministic unit tests. `createDomJourneyDriver` adapts an\nexisting application window; the CLI's driver uses Chrome DevTools directly and remains\ndependency-free.\n"}