Skip to content

refactor: redesign Explorer API to a minimal, recovery-ambient interface#101

Open
DavertMik wants to merge 4 commits into
mainfrom
refactor/explorer-api
Open

refactor: redesign Explorer API to a minimal, recovery-ambient interface#101
DavertMik wants to merge 4 commits into
mainfrom
refactor/explorer-api

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

Summary

Explorer had grown into a god-module: ~42 public members mixing six concerns, four inconsistent error policies for the same failure, seven service-locator getters, and a public playwrightHelper that let 18 call sites reach raw Playwright internals — which in turn forced the whole recovery machinery to be public. This PR reshapes it into a deep module with a 13-member interface.

The new interface

class Explorer {
  get actor()                                 // CodeceptJS I, for user hooks
  get page(): Page | null                     // raw peek, null-safe, NO recovery (background consumers)
  get activeTest(): Test | null
  start() / stop()                            // idempotent
  action(): Action                            // recovery-aware
  visit(url, { screenshot? }): ActionResult   // navigate + observe in one call
  capture({ screenshot? }): ActionResult
  withPage(fn)                                // THE page seam — recovery-wrapped
  exitIframe()                                // no-op outside a frame
  recover(error?)                             // single recovery entry point
  beginTest(test): TestRun                    // { started, stop(meta) } handle
  openFreshTab()
}

Key changes

  • Recovery is ambient, not opt-in. Every page-touching member passes through one internal recovery ladder (readiness-wait → page reattach → browser restart → retry once). The five public recovery entry points collapse into recover(error?). This also fixes a real bug: Actions created via bare createAction() silently bypassed recovery while executeAction got it.
  • playwrightHelper is private. withPage(fn) replaces all 18 external raw-page reaches and the 5 caller-side runWithBrowserRecovery wraps; the page getter covers the three background peek-only consumers (axe scans, screencast) where triggering recovery would race the main flow.
  • Explorer stops being a service locator. Agents receive an AgentDeps object (src/ai/agent.ts); TaskAgent owns the shared fields and getters. ExplorBot now truly owns StateManager, Reporter, RequestStore, and PlaywrightRecorder via lazy accessors and injects them downward.
  • Test lifecycle is a handle. beginTest(test) returns TestRun { started, stop(meta) }, making the old startTest(a)/stopTest(b) mismatch unrepresentable; page watchers are fully internal.
  • Element annotation moved to web- utils. utils/web-annotate.ts (annotatePageElements) and utils/web-eidx.ts (eidxInContainer, eidxByLocator) are page-taking functions called through withPage.
  • Tabs are observed state. stateManager.otherTabs replaces the hasOtherTabs/getOtherTabsInfo/clearOtherTabsInfo trio.

User-visible behavior

  • Browser crash recovery now applies uniformly to every operation — sessions self-heal where some paths previously failed permanently.
  • Captain's browser tool: the restart action merged into recover (recovery escalates to restart automatically).

Verification

  • 872/872 unit + integration tests pass against the committed tree in isolation
  • biome check clean; scoped tsc shows no new errors beyond pre-existing ones
  • Net −56 lines despite the new DI wiring and two extracted util modules

Not run: tests/regression (real-AI harness) — recommend one live session before merging.

Follow-up (out of scope)

Action still exposes its own playwrightHelper (single read at navigator.ts:357) — same leak one layer down, worth a companion cleanup.

🤖 Generated with Claude Code

DavertMik and others added 2 commits July 17, 2026 02:39
Planner and Pilot ran at a 9% prompt cache rate while the default-model
agents sat at 75%. The gap was three missing properties: a stable
byte-identical prefix, an unchanging tool list, and append-only history.

- Pilot: system, verdict and reset prompts now open with their static
  bodies and close with the per-test task block. Extract buildTaskContext()
  for the dynamic part; drop the request type from the verdict opener since
  the user message already carries it.
- Pilot: always send the same tool set and gate it with toolChoice
  'auto'/'none' instead of flipping the tool list between calls, which
  invalidated the whole conversation's cache.
- Pilot: stop rewriting history via cleanupTag. Pilot conversations are
  light by design; the trimmed KBs cost more re-tokenized than they save.
- Planner: order messages system -> static rules -> static output format ->
  page research -> approach + URL context, so re-plans of the same page
  reuse the research prefix.
- Route the click disambiguator off the agentic model: ~350-token one-shot
  prompts can never cache and don't need the expensive model.

Prompts are reordered, not rewritten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Explorer's public surface shrinks from ~42 members to 13. The noise had
three sources: four inconsistent error policies, a service-locator getter
per collaborator, and the playwrightHelper leak that forced the recovery
machinery to be public.

- withPage(fn) is the single recovery-wrapped page seam; the page getter
  gives a raw null-safe peek for background consumers; playwrightHelper
  is private
- one recover(error?) replaces runWithBrowserRecovery,
  recoverFromBrowserError, restartBrowser, ensurePageAvailable, and
  handleExecutionError; the recover-then-restart escalation is internal
- action() returns a recovery-aware Action, fixing bare createAction()
  silently bypassing recovery
- visit(url) returns the observed ActionResult; beginTest(test) returns
  a TestRun handle so a startTest/stopTest mismatch is unrepresentable
- agents receive services via AgentDeps (TaskAgent owns the shared
  fields) instead of Explorer getters; ExplorBot now owns StateManager,
  Reporter, RequestStore, and PlaywrightRecorder
- element annotation and eidx helpers move to utils/web-annotate.ts and
  utils/web-eidx.ts as page-taking functions
- other-tabs info becomes observed state on StateManager

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Frame context is acting-level state, so exitIframe() lives on Action
(which already holds the helper) instead of Explorer's interface.
Failed attempts now restore the main-frame context ambiently for every
action, generalizing the guard the form tool had; the exitIframe tool
runs I.switchTo() through the action seam, so the step is recorded for
generated tests. Explorer is down to 12 public members.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Explorbot Self-Regression

Commit 2437774 · run

Scenario Result Attempts Duration
basic (native) PASS 1/3 10m
experience: control OK — failed as expected 1/1 1m
experience: seeded PASS 1/3 1m

Attempt details

  • basic (native) attempt 1 — PASS: login evidence: PASS (post-login plan=true, post-login research=true); research: PASS (files=6, wellFormed=true, keywords=7/3); scenarios: PASS (tests=6/5, features=5/3); tests passed: PASS (5 passed, 0 failed (reporter: 4 passed, 1 failed))
  • experience: control attempt 1 — PASS: control: OK — failed as expected (0 passed, 1 failed)
  • experience: seeded attempt 1 — PASS: seeded: PASS (1 passed, 0 failed)

Session analysis — basic (native):

Session Analysis

Issue tracking functionality was exercised across creation, labeling, filtering, and search flows. All five tests passed with core functionality verified, though success toast visibility was not reliably confirmed.

Coverage

  • Pages: /issues, /issues?new=1, /issues?q=, /issues?label=1, /issues?status=open
  • Features: Issue creation, label assignment, status filtering, label filtering, keyword search

What works

  • Issue creationET-1 Create new issue with basic fields, ET-2 Create issue with label assignment
  • Status filteringET-3 Filter issues by status
  • Label filteringET-4 Filter issues by label
  • Keyword searchET-5 Search for existing issue by keyword

Execution Issues

  • ET-1 Create new issue with basic fields — success toast not detectable by automation despite issue created successfully
  • ET-2 Create issue with label assignment — success toast not visible after creation, label applied and persisted correctly

@DavertMik
DavertMik requested a review from DenysKuchma July 17, 2026 21:14
Comment thread src/explorer.ts
await this.reporter.reportTestStart(test);
await this.closeOtherTabs();
this.stateManager.otherTabs = [];
if (!this.page && !(await this.recoverOrRestart())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This no-op stop leaves the already started test lifecycle unfinished because test.start() and reportTestStart() were called above. Defer them until recovery succeeds or finalize the failed test

Comment thread src/explorer.ts
this.observedTestPages.clear();
}

async playwrightLocatorCount(locatorFn: (page: any) => any): Promise<number> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove from docs playwrightLocatorCount

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants