feat: #3608867 Add a browser_navigate tool for stateful, multi-page browser navigation.
This MR implements the browser_navigate AI Agent tool proposed in #3608867.
What / Why
browser_preview opens each page in a throwaway browser, so it cannot walk a multi-page flow (open a Drupal Canvas page, go back, go forward, reload) and it re-establishes login on every call. An AI agent verifying a build across several pages loses its session and its navigation history between calls.
This adds a stateful navigation runner that persists the browser session (cookies via Playwright storageState) and a navigation history stack in a per-user state file between calls, so goto / back / forward / reload (and interactions: click / type / scroll / wait / resize) work across separate tool invocations, with login established only once.
Changes
scripts/browser-navigate.mjs(new): restoresstorageState+ history from a state file, performs one action, saves state back, returns url/title/text/console/screenshot/history/index/canGoBack/canGoForward.src/PlaywrightRunner.php: addednavigate()+navigateScriptPath()— resolves goto URLs with the same same-host guard ascapture(), runs the script per acting user, saves the screenshot as a managed file.src/Plugin/AiFunctionCall/BrowserNavigate.php(new): the#[FunctionCall]tool, oneactionJSON argument, sameuse ai playwrightpermission gate asbrowser_preview.
Testing
Verified locally end-to-end: goto -> goto -> back -> forward -> reload keeps an authenticated session and correct history across separate process calls; each step saves a screenshot file. phpcs (Drupal + DrupalPractice) is clean.
AI-Generated: Yes (Used Claude Code to implement the runner/tool and verify locally; human-reviewed before pushing.)
Update: page-verification actions
Extends the browser_navigate action DSL so an agent can check whether the current page did a good job, not just look at it, and decide whether to fix it. New actions, each returning a pass/fail with a detail string in the result:
assertText/assertNoText— text present/absent on the page.assertVisible/assertHidden— element visibility by selector.assertCount— expected number of matches for a selector.assertUrl— current URL matches/contains the expected value.assertNoConsoleErrors— noconsole.errorentries since the last action.- A dependency-free a11y check for WCAG basics: missing
alttext, controls without an accessible name, unlabelled form fields, skipped heading levels.
Changes: scripts/browser-navigate.mjs, src/PlaywrightRunner.php, src/Plugin/AiFunctionCall/BrowserNavigate.php.
AI-Generated: Yes (Used Claude Code, human-reviewed)
Hardening: audit-driven fixes
A follow-up audit of the browser_navigate runner found a few robustness issues; this update (ed4cbb7) fixes them:
- Close the browser on error paths — a failure used to skip the
finally(viaprocess.exit) and orphan a headless Chromium process on every failed call. - Bound each
smartSettlewait by the remaining time budget instead oftimeout:0, which Playwright treats as wait-forever and could hang the tool on a live/chatty page. - Cap the navigation history stack at 50 entries so the per-user state file and response payload stay bounded over a long session.
assertCountnow rejects a missing/non-numeric count instead of silently comparing againstNaN.- Clamp a stale/corrupt saved history index into range immediately on load, so it can't drive
page.goto(undefined).
AI-Generated: Yes (Used Claude Code to implement and verify the hardening fixes; human-reviewed before pushing.)
Checkpoints
- File an issue about this project
- Addition/Change/Update/Fix to this project
- Testing to ensure no regression
- Automated unit/functional testing coverage
- Developer Documentation support on feature change/addition
- User Guide Documentation support on feature change/addition
- UX/UI designer responsibilities
- Accessibility and Readability
- Reviewed by a human
- Code review by maintainers
- Full testing and approval
- Credit contributors
- Review with the product owner
- Update Release Notes
- Release