diff --git a/skill-src/webcmd-browser/SKILL.src.md b/skill-src/webcmd-browser/SKILL.src.md index 2cb1c4ec..f52e2868 100644 --- a/skill-src/webcmd-browser/SKILL.src.md +++ b/skill-src/webcmd-browser/SKILL.src.md @@ -94,7 +94,7 @@ return { title: await page.title(), url: page.url() }; JS ``` -For sandbox boundaries, artifacts, errors, snapshots, and timings, read [`references/browser-run-playwright.md`](references/browser-run-playwright.md). +**`browser run` executes in QuickJS — not in Node, and not in the page.** `document` and `window` are not in scope (use `page.evaluate`), and `Buffer`, `require`, and `fs` do not exist. Read [`references/browser-run-playwright.md`](references/browser-run-playwright.md) before writing your first program; it lists what is available, what is blocked, and what to use instead. --- diff --git a/skill-src/webcmd-browser/references/browser-run-playwright.src.md b/skill-src/webcmd-browser/references/browser-run-playwright.src.md index fdb2ac31..79321e16 100644 --- a/skill-src/webcmd-browser/references/browser-run-playwright.src.md +++ b/skill-src/webcmd-browser/references/browser-run-playwright.src.md @@ -1,18 +1,70 @@ # Browser Run Details -## Sandbox boundaries +## The runtime is QuickJS, not Node and not the page -`run` evaluates the supplied JavaScript in a fresh sandbox. Browser state in the bound session persists, but JavaScript variables and handles do not. `page`, `context`, `browser`, and `console` are normal Playwright globals; use the vendored Playwright client as the API reference. Return only JSON-compatible data. `page.snapshotForAI()` is not available. +`browser run` executes your program in a QuickJS sandbox. `page`, `context`, and `browser` +are Playwright handles that drive a browser running somewhere else — they are not evidence +that you are inside that browser, and not evidence that you are inside Node. -`context.newPage()` is not available inside `run`; create or bind Session tabs through Webcmd commands so page ownership stays deterministic. +Two consequences produce almost every `browser run` failure: -## Artifact paths +- **Anything DOM-shaped must go inside `page.evaluate()`.** `document`, `window`, and + `localStorage` are not in your scope. `document.querySelector(...)` at the top level + throws `'document' is not defined`; `await page.evaluate(() => document.querySelector(...))` + works, because that callback is serialized and run in the page. +- **Anything Node-shaped does not exist.** No `require`, no `import` of host modules, no + `fs`, no `Buffer`, no `process`. -Artifacts written by Playwright must use a relative logical filename. Webcmd returns an artifact receipt with its locator; it does not grant host-path write access. +Browser state in the bound session persists between runs. JavaScript variables and handles +do not — each run starts with a fresh scope. + +## What is available + +| Need | Use | +|---|---| +| Drive the page | `page`, `context`, `browser` (Playwright) | +| Read or manipulate the DOM | `page.evaluate(() => …)` | +| Find elements | `page.locator(selector)`, `page.getByRole(...)`, and the other `getBy*` locators | +| Log | `console` | +| Return data | `return` any JSON-compatible value | + +`page.$` and `page.$$` work, but prefer `page.locator()` — it retries and auto-waits. + +`context.newPage()` works and creates a tab the Webcmd session tracks. You cannot close it +from inside `run` (see below); list tabs with `webcmd --session browser tabs`. + +`page.snapshotForAI()` is not available; use `webcmd browser snapshot` instead. + +## What is blocked, and what to use instead + +These throw `BROWSER_RUN_API_UNSUPPORTED` because page and context ownership belongs to the +Webcmd session, not to your program: + +| Blocked | Instead | +|---|---| +| `page.close()` | Leave the tab open, or `webcmd session close ` | +| `context.close()`, `browser.close()` | `webcmd session close ` | +| `browser.newContext()` | `webcmd session create` — one run is scoped to one context | +| `browser.newBrowserCDPSession()`, `context.newCDPSession()` | Not exposed inside `run` | +| `playwright.request` (`newRequest`) | `page.request` for calls in the page's context | + +## Files and binary data + +There is no host filesystem. Passing a host path to `setInputFiles` fails with +`File paths are unavailable in the QuickJS sandbox; use in-memory file payloads` — supply +`{ name, mimeType, buffer }` with a `Uint8Array` instead. + +Artifacts written from the sandbox must use a relative logical filename. Webcmd returns an +artifact receipt with its locator; it does not grant host-path write access. ## Errors -`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output limits, or serialization failures. A timeout can include `BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. +`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output +limits, or serialization failures. A timeout can include +`BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. + +A rejection phrased `QuickJS promise rejected: 'X' is not defined` means `X` is a Node or DOM +global that the sandbox does not provide — check the two tables above before retrying. ## Snapshot behavior @@ -21,3 +73,9 @@ Use `webcmd --session browser snapshot --snapshot-mode act` to insp ## Timing Run results include timing fields such as `quickjs_boot_ms`, `client_bundle_init_ms`, `program_ms`, `browser_wait_ms`, and `snapshot_ms`. `--timeout ` limits the complete run; `--max-output ` bounds returned data and logs. + +## Hosted mode + +Hosted `browser run` uses the same QuickJS sandbox and the same rules. Only the browser on +the far end differs — hosted runs drive a Browser Use browser over CDP rather than local +Cloak. Programs that work locally work hosted; the tables above apply in both modes. diff --git a/skills/webcmd-browser/SKILL.md b/skills/webcmd-browser/SKILL.md index 1049dd24..0041376c 100644 --- a/skills/webcmd-browser/SKILL.md +++ b/skills/webcmd-browser/SKILL.md @@ -94,7 +94,7 @@ return { title: await page.title(), url: page.url() }; JS ``` -For sandbox boundaries, artifacts, errors, snapshots, and timings, read [`references/browser-run-playwright.md`](references/browser-run-playwright.md). +**`browser run` executes in QuickJS — not in Node, and not in the page.** `document` and `window` are not in scope (use `page.evaluate`), and `Buffer`, `require`, and `fs` do not exist. Read [`references/browser-run-playwright.md`](references/browser-run-playwright.md) before writing your first program; it lists what is available, what is blocked, and what to use instead. --- diff --git a/skills/webcmd-browser/references/browser-run-playwright.md b/skills/webcmd-browser/references/browser-run-playwright.md index fdb2ac31..79321e16 100644 --- a/skills/webcmd-browser/references/browser-run-playwright.md +++ b/skills/webcmd-browser/references/browser-run-playwright.md @@ -1,18 +1,70 @@ # Browser Run Details -## Sandbox boundaries +## The runtime is QuickJS, not Node and not the page -`run` evaluates the supplied JavaScript in a fresh sandbox. Browser state in the bound session persists, but JavaScript variables and handles do not. `page`, `context`, `browser`, and `console` are normal Playwright globals; use the vendored Playwright client as the API reference. Return only JSON-compatible data. `page.snapshotForAI()` is not available. +`browser run` executes your program in a QuickJS sandbox. `page`, `context`, and `browser` +are Playwright handles that drive a browser running somewhere else — they are not evidence +that you are inside that browser, and not evidence that you are inside Node. -`context.newPage()` is not available inside `run`; create or bind Session tabs through Webcmd commands so page ownership stays deterministic. +Two consequences produce almost every `browser run` failure: -## Artifact paths +- **Anything DOM-shaped must go inside `page.evaluate()`.** `document`, `window`, and + `localStorage` are not in your scope. `document.querySelector(...)` at the top level + throws `'document' is not defined`; `await page.evaluate(() => document.querySelector(...))` + works, because that callback is serialized and run in the page. +- **Anything Node-shaped does not exist.** No `require`, no `import` of host modules, no + `fs`, no `Buffer`, no `process`. -Artifacts written by Playwright must use a relative logical filename. Webcmd returns an artifact receipt with its locator; it does not grant host-path write access. +Browser state in the bound session persists between runs. JavaScript variables and handles +do not — each run starts with a fresh scope. + +## What is available + +| Need | Use | +|---|---| +| Drive the page | `page`, `context`, `browser` (Playwright) | +| Read or manipulate the DOM | `page.evaluate(() => …)` | +| Find elements | `page.locator(selector)`, `page.getByRole(...)`, and the other `getBy*` locators | +| Log | `console` | +| Return data | `return` any JSON-compatible value | + +`page.$` and `page.$$` work, but prefer `page.locator()` — it retries and auto-waits. + +`context.newPage()` works and creates a tab the Webcmd session tracks. You cannot close it +from inside `run` (see below); list tabs with `webcmd --session browser tabs`. + +`page.snapshotForAI()` is not available; use `webcmd browser snapshot` instead. + +## What is blocked, and what to use instead + +These throw `BROWSER_RUN_API_UNSUPPORTED` because page and context ownership belongs to the +Webcmd session, not to your program: + +| Blocked | Instead | +|---|---| +| `page.close()` | Leave the tab open, or `webcmd session close ` | +| `context.close()`, `browser.close()` | `webcmd session close ` | +| `browser.newContext()` | `webcmd session create` — one run is scoped to one context | +| `browser.newBrowserCDPSession()`, `context.newCDPSession()` | Not exposed inside `run` | +| `playwright.request` (`newRequest`) | `page.request` for calls in the page's context | + +## Files and binary data + +There is no host filesystem. Passing a host path to `setInputFiles` fails with +`File paths are unavailable in the QuickJS sandbox; use in-memory file payloads` — supply +`{ name, mimeType, buffer }` with a `Uint8Array` instead. + +Artifacts written from the sandbox must use a relative logical filename. Webcmd returns an +artifact receipt with its locator; it does not grant host-path write access. ## Errors -`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output limits, or serialization failures. A timeout can include `BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. +`BROWSER_RUN_*` errors name invalid input, unsupported Playwright calls, timeouts, output +limits, or serialization failures. A timeout can include +`BROWSER_RUN_SIDE_EFFECTS_MAY_HAVE_OCCURRED`; inspect the page state before retrying a write. + +A rejection phrased `QuickJS promise rejected: 'X' is not defined` means `X` is a Node or DOM +global that the sandbox does not provide — check the two tables above before retrying. ## Snapshot behavior @@ -21,3 +73,9 @@ Use `webcmd --session browser snapshot --snapshot-mode act` to insp ## Timing Run results include timing fields such as `quickjs_boot_ms`, `client_bundle_init_ms`, `program_ms`, `browser_wait_ms`, and `snapshot_ms`. `--timeout ` limits the complete run; `--max-output ` bounds returned data and logs. + +## Hosted mode + +Hosted `browser run` uses the same QuickJS sandbox and the same rules. Only the browser on +the far end differs — hosted runs drive a Browser Use browser over CDP rather than local +Cloak. Programs that work locally work hosted; the tables above apply in both modes. diff --git a/src/skills.test.ts b/src/skills.test.ts index ea19e4a8..9667ab6e 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -215,8 +215,9 @@ describe('webcmd skills content', () => { expect(browser).toContain('--no-snapshot-diff'); expect(browser).not.toContain('page.snapshotForAI()'); expect(browser).not.toContain('--snapshot-diff'); - expect(browserRunReference).toMatch(/sandbox boundaries/i); - expect(browserRunReference).toMatch(/artifact paths/i); + expect(browserRunReference).toMatch(/QuickJS/); + expect(browserRunReference).toMatch(/page\.evaluate/); + expect(browserRunReference).toMatch(/artifact/i); expect(browserRunReference).toMatch(/errors/i); expect(browserRunReference).toMatch(/snapshot behavior/i); expect(browserRunReference).toContain('--snapshot-mode act|tree');