Skip to content

evals: port the act/extract/observe bench suite to the v4 SDK - #2494

Open
shriyatheunicorn wants to merge 24 commits into
v4-spikefrom
evals-v4-spike
Open

evals: port the act/extract/observe bench suite to the v4 SDK#2494
shriyatheunicorn wants to merge 24 commits into
v4-spikefrom
evals-v4-spike

Conversation

@shriyatheunicorn

@shriyatheunicorn shriyatheunicorn commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

This will port the act/extract/observe bench suite (77 tasks) from the v3 API over to the v4 SDK, converting each task in place on its existing tasks/bench/ path so file history and blame stay continuous.

Tasks compile clean against the in-tree v4 SDK, including the #2460 result shapes and #2427 Action inputs. The execution harness (v4 init, task worker, run isolation) follows as a stacked change.

Note: this PR also includes Sam's functionality from 2431 to enable the usage of cross-frame locators for iframe form assertions

Scope

act (40) · extract (25) · observe (12). Combination tasks can be added if needed for a more expansive task suite.


Summary by cubic

Ports the act/extract/observe bench suite (77 tasks) to the v4 SDK with behavior parity. Uses @browserbasehq/stagehand actions, zod extracts, cross-frame assertions (OOPIF/CSR), safer errors/clicks, and cleans up extract_csa.ts.

  • New Features

    • Added defineBenchV4Task and BenchV4TaskContext with a fail-fast, null-safe guard for v3 runners; documented BenchTaskMeta.systemPrompt; folded v4 types into types.ts and refactored imports.
    • Updated tasks to use stagehand.act/extract/observe, native observed-action replay via stagehand.act(Action), and the v4 Page (async url()); multi-tab via stagehand.context.activePage()/pages().
    • Switched extracts to zod schemas with per-page targeting; inlined string-similarity helpers for checks; expanded and cleaned the CSA publications schema in extract_csa.ts.
    • Enabled cross-frame assertions via iframe >> selector for iframe/OOPIF cases; added deterministic dropdown checks.
    • Improved click fidelity and sanitized error messages; removed the local replay shim.
  • Migration

    • Run bench tasks with --sdk v4; paths and task names are unchanged.
    • No consumer changes required; results use v4 shapes.

Written for commit 8b68d62. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8b68d62

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@shriyatheunicorn
shriyatheunicorn marked this pull request as ready for review July 29, 2026 13:13

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

6 issues found across 81 files

Confidence score: 3/5

  • In packages/evals/tasks/bench/act/heal_simple_google_search.ts, the v4 benchmark currently keeps selfHeal: false, so the invalid selector fails and the task submits Enter without filling the field, which can invalidate the healing benchmark outcome — enable healing for this task (or otherwise ensure the act step can recover before submit).
  • In packages/evals/tasks/bench/extract/extract_memorial_healthcare.ts and packages/evals/tasks/bench/extract/extract_baptist_health.ts, custom grading logic can drift from prior comparison behavior (whitespace/punctuation normalization, prefix weighting, transpositions, threshold behavior), risking silent score changes — add parity-focused regression tests against the established utility around edge cases.
  • In packages/evals/tasks/bench/observe/observe_iframes2.ts, packages/evals/tasks/bench/observe/observe_yc_startup.ts, and packages/evals/tasks/bench/observe/observe_file_uploads.ts, new selector-resolution and XPath/CSS identity paths lack focused executable coverage, so malformed or nonmatching selectors could silently alter pass/fail scoring — add deterministic matching/nonmatching tests including iframe XPath rejection.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/evals/tasks/bench/extract/extract_memorial_healthcare.ts">

<violation number="1" location="packages/evals/tasks/bench/extract/extract_memorial_healthcare.ts:19">
P3: Evaluator pass/fail scoring can drift from the established comparison utility without detection because this hand-written Jaro-Winkler replacement has no parity coverage for whitespace, punctuation, prefix, and transposition cases. Add focused tests comparing it with `utils.ts` behavior before relying on the port.

(Based on your team's feedback about adding unit tests for new behavior.) [FEEDBACK_USED]</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_yc_startup.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_yc_startup.ts:39">
P3: This new XPath/CSS identity check has no focused regression coverage, so malformed selectors or the expected XPath match can silently change benchmark scoring. Add deterministic coverage for matching XPath, nonmatching CSS, and resolver failures.

(Based on your team's feedback about unit tests for changed logic.) [FEEDBACK_USED]</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_file_uploads.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_file_uploads.ts:28">
P3: The new selector-resolution path has no regression coverage, so malformed/nonmatching XPath results can silently change this benchmark's pass/fail outcome. Add focused matching and nonmatching XPath cases around the identity check.

(Based on your team's feedback about unit tests for new behavior.) [FEEDBACK_USED].</violation>
</file>

<file name="packages/evals/tasks/bench/act/heal_simple_google_search.ts">

<violation number="1" location="packages/evals/tasks/bench/act/heal_simple_google_search.ts:12">
P1: This v4 healing benchmark will not exercise healing: its invalid selector produces a failed `ActResult` when v4 uses its default `selfHeal: false`, then the task presses Enter without filling the field. Enable `selfHeal` in the v4 eval initialization (or configure this task's v4 client) so this remains a healing parity test.</violation>
</file>

<file name="packages/evals/tasks/bench/extract/extract_baptist_health.ts">

<violation number="1" location="packages/evals/tasks/bench/extract/extract_baptist_health.ts:19">
P3: This new grading implementation has no focused regression coverage for normalization, prefix weighting, and transpositions, so parity with the prior evaluator can regress unnoticed. Add focused cases around the 0.85 boundary before relying on it for bench pass/fail results.

(Based on your team's feedback about unit tests for new behavior.) [FEEDBACK_USED]</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_iframes2.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_iframes2.ts:49">
P3: This new selector-resolution path has no executable coverage; add focused cases for matching CSS/XPath candidates and rejecting an iframe XPath so the benchmark's v3-equivalence criterion cannot silently regress.

(Based on your team's feedback about unit tests for changed behavior.) [FEEDBACK_USED]</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Runner as Bench Runner
    participant Task as Task Function
    participant SDK as Stagehand v4 SDK
    participant Page as Page (Browser)
    participant Replay as observeReplay Shim

    Note over Runner,Replay: NEW: v4 Bench Task Execution Flow

    Runner->>Task: invoke with {stagehand, page, ...}
    Task->>SDK: stagehand.act("instruction")
    SDK-->>Task: result (success/failure)
    Task->>SDK: stagehand.extract("instruction", z.object({...}))
    SDK-->>Task: { data }
    alt Observe + Replay Flow
        Task->>SDK: stagehand.observe("instruction")
        SDK-->>Task: { data: ObservedAction[] }
        Task->>Replay: replayObservedAction(page, action)
        Replay->>Page: locator.click() / fill() / etc.
        Page-->>Replay: done
    end
    Task->>Page: await page.url()
    Page-->>Task: current URL
    alt Success
        Task-->>Runner: { _success: true, ... }
    else Failure
        Task->>SDK: stagehand.close()
        Task-->>Runner: { _success: false, error }
    end
    Task->>SDK: stagehand.close()
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/evals/framework/types.ts
// Self-healing act(Action) replay (V4_API_LOGS.md #1, restored by
// stagehand#2427): same intentionally invalid selector as the v3
// twin — healing must re-locate "The search bar" and fill it.
await stagehand.act({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: This v4 healing benchmark will not exercise healing: its invalid selector produces a failed ActResult when v4 uses its default selfHeal: false, then the task presses Enter without filling the field. Enable selfHeal in the v4 eval initialization (or configure this task's v4 client) so this remains a healing parity test.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/act/heal_simple_google_search.ts, line 12:

<comment>This v4 healing benchmark will not exercise healing: its invalid selector produces a failed `ActResult` when v4 uses its default `selfHeal: false`, then the task presses Enter without filling the field. Enable `selfHeal` in the v4 eval initialization (or configure this task's v4 client) so this remains a healing parity test.</comment>

<file context>
@@ -1,25 +1,27 @@
+      // Self-healing act(Action) replay (V4_API_LOGS.md #1, restored by
+      // stagehand#2427): same intentionally invalid selector as the v3
+      // twin — healing must re-locate "The search bar" and fill it.
+      await stagehand.act({
         description: "The search bar",
         selector: "/html/not-the-search-bar",
</file context>

Comment thread packages/evals/tasks/bench/act/osr_in_spif.ts Outdated
Comment thread packages/evals/tasks/bench/act/custom_dropdown.ts Outdated
Comment thread packages/evals/framework/observeReplay.ts Outdated
.trim();
}

function jaroSimilarity(first: string, second: string): number {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This new grading implementation has no focused regression coverage for normalization, prefix weighting, and transpositions, so parity with the prior evaluator can regress unnoticed. Add focused cases around the 0.85 boundary before relying on it for bench pass/fail results.

(Based on your team's feedback about unit tests for new behavior.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/extract/extract_baptist_health.ts, line 19:

<comment>This new grading implementation has no focused regression coverage for normalization, prefix weighting, and transpositions, so parity with the prior evaluator can regress unnoticed. Add focused cases around the 0.85 boundary before relying on it for bench pass/fail results.

(Based on your team's feedback about unit tests for new behavior.) </comment>

<file context>
@@ -1,15 +1,92 @@
+    .trim();
+}
+
+function jaroSimilarity(first: string, second: string): number {
+  // string-comparison initParams: strip all whitespace + lowercase
+  const s1 = first.replace(/\s+/g, "").toLowerCase();
</file context>

Comment thread packages/evals/tasks/bench/act/tab_handling.ts Outdated
matchedLocator = candSel;
break;
}
const matched = await page.evaluate(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This new selector-resolution path has no executable coverage; add focused cases for matching CSS/XPath candidates and rejecting an iframe XPath so the benchmark's v3-equivalence criterion cannot silently regress.

(Based on your team's feedback about unit tests for changed behavior.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/observe/observe_iframes2.ts, line 49:

<comment>This new selector-resolution path has no executable coverage; add focused cases for matching CSS/XPath candidates and rejecting an iframe XPath so the benchmark's v3-equivalence criterion cannot silently regress.

(Based on your team's feedback about unit tests for changed behavior.) </comment>

<file context>
@@ -34,35 +33,65 @@ export default defineBenchTask(
-              matchedLocator = candSel;
-              break;
-            }
+          const matched = await page.evaluate(
+            ({
+              observedSelector,
</file context>

Comment thread packages/evals/tasks/bench/observe/observe_github.ts
Comment thread packages/evals/tasks/bench/observe/observe_vantechjournal.ts
shriyatheunicorn and others added 3 commits July 29, 2026 07:41
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…elity, per-page extract targeting, deterministic dropdown check
The fixture contains a cross-origin OOPIF, so the outer page cannot access
its contentDocument. Validate the form through v4 cross-frame locators
(iframe >> selector) with inputValue()/isChecked() instead.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 67 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/evals/tasks/bench/act/custom_dropdown.ts Outdated
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 81 files

Confidence score: 5/5

  • In packages/evals/tasks/bench/extract/extract_public_notices.ts, the local copy of the scoring algorithm can drift from the other three copies, so later fixes may change comparison semantics in one place but not this benchmark, leading to silent inconsistency in eval results—move the logic to a shared framework utility (allowed by this task) to keep behavior aligned.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/evals/tasks/bench/extract/extract_public_notices.ts">

<violation number="1" location="packages/evals/tasks/bench/extract/extract_public_notices.ts:10">
P3: Four copies of this scoring algorithm must stay aligned; a future comparison fix can silently leave this benchmark using different semantics. A shared framework utility would remain within this task's permitted import boundary and avoid that drift.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/evals/tasks/bench/extract/extract_hamilton_weather.ts
Comment thread packages/evals/tasks/bench/act/google_flights.ts
Comment thread packages/evals/tasks/bench/extract/extract_csa.ts
Comment thread packages/evals/framework/observeReplay.ts Outdated
* string-comparison@1.3.0 — v4 eval tasks may only import "zod" and
* "../../../framework/*.js". Pure computation, no behavior change.
*/
function normalizeString(str: string): string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Four copies of this scoring algorithm must stay aligned; a future comparison fix can silently leave this benchmark using different semantics. A shared framework utility would remain within this task's permitted import boundary and avoid that drift.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/extract/extract_public_notices.ts, line 10:

<comment>Four copies of this scoring algorithm must stay aligned; a future comparison fix can silently leave this benchmark using different semantics. A shared framework utility would remain within this task's permitted import boundary and avoid that drift.</comment>

<file context>
@@ -1,17 +1,94 @@
+ * string-comparison@1.3.0 — v4 eval tasks may only import "zod" and
+ * "../../../framework/*.js". Pure computation, no behavior change.
+ */
+function normalizeString(str: string): string {
+  return str
+    .toLowerCase()
</file context>

Comment thread packages/evals/tasks/bench/observe/observe_iframes1.ts Outdated
Comment thread packages/evals/tasks/bench/observe/observe_github.ts Outdated
Comment thread packages/evals/framework/observeReplay.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

5 issues found across 80 files

Confidence score: 4/5

  • The biggest risk is duplicated selector resolver/equality logic across packages/evals/tasks/bench/observe/observe_yc_startup.ts, observe_github.ts, and observe_file_uploads.ts: future XPath/CSS/frame fixes can land in one task but not others, causing inconsistent benchmark behavior and hard-to-track regressions — extract a shared eval helper and route all six observe tasks through it.
  • packages/evals/tasks/bench/observe/observe_iframes1.ts and observe_iframes2.ts now carry effectively the same iframe selector-matching implementation, so frame-handling or selector-format changes are likely to drift between the two and skew iframe-specific eval results — centralize the iframe resolver/comparison path so both tasks inherit the same semantics and error handling.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/evals/tasks/bench/observe/observe_iframes2.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_iframes2.ts:49">
P3: Iframe selector matching now has two identical implementations in `observe_iframes1.ts` and this task. Extract the resolver/comparison into a shared helper so XPath and frame-handling fixes apply consistently to both benches.</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_github.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_github.ts:45">
P3: Selector-identity matching is now duplicated across multiple observe tasks. A shared helper would keep XPath/selector semantics fixes consistent instead of requiring edits to each copied evaluator.</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_file_uploads.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_file_uploads.ts:36">
P3: Selector-resolution logic is now duplicated across six v4 observe tasks, so fixes to XPath/CSS handling can silently diverge between benchmarks. A shared page-evaluation helper would keep these identity checks consistent.</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_yc_startup.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_yc_startup.ts:47">
P3: Selector parsing is now duplicated across six observe tasks, so XPath or frame-selector fixes will drift between benchmarks. Extract the shared resolver/equality check into an eval helper and reuse it here.</violation>
</file>

<file name="packages/evals/tasks/bench/observe/observe_iframes1.ts">

<violation number="1" location="packages/evals/tasks/bench/observe/observe_iframes1.ts:46">
P3: Future selector-format changes must be copied across several bench tasks because this resolver duplicates existing v4 observe validation logic. A shared helper would keep XPath/CSS resolution and error behavior consistent.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant T as Bench Task (v4)
    participant EV as Eval Runner (--sdk v4)
    participant SH as Stagehand v4 SDK
    participant P as v4 Page (RPC-backed)
    participant B as Browserbase Session
    participant LLM as LLM Provider
    participant L as Eval Logger

    Note over T,L: NEW: v4 bench task execution flow (defineBenchV4Task)

    EV->>EV: Select task via --sdk v4 flag
    EV->>T: invoke fn(ctx) with BenchV4TaskContext
    alt ctx.stagehand is undefined (v3 runner mistaken)
        T-->>EV: Throw "requires v4 harness" error
    else ctx.stagehand is defined
        T->>SH: stagehand.act("click the button")
        SH->>LLM: Resolve instruction to action
        LLM-->>SH: Action (selector, method, args)
        SH->>P: Execute resolved action
        P-->>SH: Action result
        SH-->>T: { success: boolean }

        T->>SH: stagehand.extract("extract page text", z.object({...}))
        SH->>P: Extract current DOM context
        SH->>LLM: Send DOM snapshot + extraction schema
        LLM-->>SH: Structured data
        SH-->>T: { data: { extracted fields } }

        T->>SH: stagehand.observe("find the main header")
        SH->>P: Observe interactable elements
        SH->>LLM: Process observation instruction
        LLM-->>SH: Action[] observations
        SH-->>T: { data: Action[] }

        alt Multi-tab flow
            T->>SH: stagehand.context.activePage()
            SH->>B: Poll for active tab
            B-->>SH: Active page reference
            SH-->>T: v4 Page (active tab)

            T->>SH: stagehand.context.pages()
            SH->>B: List all pages
            B-->>SH: Page references
            SH-->>T: v4 Page[]

            T->>SH: stagehand.context.setActivePage(page1)
            SH->>B: Switch active tab
            B-->>SH: Done
        end

        alt Cross-frame assertions (OOPIF)
            T->>P: page.locator('iframe >> input[placeholder="Jane"]').inputValue()
            P->>B: Resolve >> cross-frame selector server-side
            B-->>P: Input value from OOPIF
            P-->>T: "Stagehand"
        else Same-origin iframe assertions
            T->>P: page.evaluate(() => document.querySelector("iframe")?.contentDocument...)
            P-->>T: Evaluated result
        end

        T->>P: await page.url()
        P-->>T: current URL (async)
        T->>P: page.locator(selector).inputValue()
        P-->>T: Field value
        T->>P: page.evaluate(() => window.scrollY + window.innerHeight / 2)
        P-->>T: Scroll position

        alt Error in task
            T-->>EV: { _success: false, error: error.message }
        else Success
            T-->>EV: { _success: true, ... }
        end

        T->>SH: stagehand.close()
        SH->>B: Cleanup session
        B-->>SH: Closed
    end

    EV->>L: Log result (status, debugUrl, sessionUrl)
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/evals/tasks/bench/act/csr_in_oopif.ts
Comment thread packages/evals/tasks/bench/act/multi_tab.ts Outdated
matchedLocator = candSel;
break;
}
const matched = await page.evaluate(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Iframe selector matching now has two identical implementations in observe_iframes1.ts and this task. Extract the resolver/comparison into a shared helper so XPath and frame-handling fixes apply consistently to both benches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/observe/observe_iframes2.ts, line 49:

<comment>Iframe selector matching now has two identical implementations in `observe_iframes1.ts` and this task. Extract the resolver/comparison into a shared helper so XPath and frame-handling fixes apply consistently to both benches.</comment>

<file context>
@@ -34,35 +33,65 @@ export default defineBenchTask(
-              matchedLocator = candSel;
-              break;
-            }
+          const matched = await page.evaluate(
+            ({
+              observedSelector,
</file context>

Comment thread packages/evals/tasks/bench/act/hidden_input_dropdown.ts Outdated
matchedLocator = candSel;
break;
}
const matched = await page.evaluate(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Selector-identity matching is now duplicated across multiple observe tasks. A shared helper would keep XPath/selector semantics fixes consistent instead of requiring edits to each copied evaluator.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/observe/observe_github.ts, line 45:

<comment>Selector-identity matching is now duplicated across multiple observe tasks. A shared helper would keep XPath/selector semantics fixes consistent instead of requiring edits to each copied evaluator.</comment>

<file context>
@@ -33,35 +32,62 @@ export default defineBenchTask(
-              matchedLocator = candSel;
-              break;
-            }
+          const matched = await page.evaluate(
+            ({
+              observedSelector,
</file context>

observedSelector: string;
candidateSelectors: string[];
}) => {
const resolve = (selector: string): Element | null => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Selector parsing is now duplicated across six observe tasks, so XPath or frame-selector fixes will drift between benchmarks. Extract the shared resolver/equality check into an eval helper and reuse it here.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/observe/observe_yc_startup.ts, line 47:

<comment>Selector parsing is now duplicated across six observe tasks, so XPath or frame-selector fixes will drift between benchmarks. Extract the shared resolver/equality check into an eval helper and reuse it here.</comment>

<file context>
@@ -28,35 +27,61 @@ export default defineBenchTask(
+              observedSelector: string;
+              candidateSelectors: string[];
+            }) => {
+              const resolve = (selector: string): Element | null => {
+                const raw = selector.startsWith("xpath=")
+                  ? selector.slice("xpath=".length)
</file context>

Comment thread packages/evals/tasks/bench/observe/observe_vantechjournal.ts
observedSelector: string;
candidateSelectors: string[];
}) => {
const resolve = (selector: string): Element | null => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Future selector-format changes must be copied across several bench tasks because this resolver duplicates existing v4 observe validation logic. A shared helper would keep XPath/CSS resolution and error behavior consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/observe/observe_iframes1.ts, line 46:

<comment>Future selector-format changes must be copied across several bench tasks because this resolver duplicates existing v4 observe validation logic. A shared helper would keep XPath/CSS resolution and error behavior consistent.</comment>

<file context>
@@ -24,35 +23,64 @@ export default defineBenchTask(
+              observedSelector: string;
+              candidateSelectors: string[];
+            }) => {
+              const resolve = (selector: string): Element | null => {
+                const raw = selector.startsWith("xpath=")
+                  ? selector.slice("xpath=".length)
</file context>

Comment thread packages/evals/tasks/bench/observe/observe_iframes1.ts
Comment thread packages/evals/tasks/bench/act/shadow_dom.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 79 files

Confidence score: 4/5

  • In packages/evals/tasks/bench/act/multi_tab.ts, the final stagehand.extract can read from the wrong tab if focus shifts after the URL check, which can produce flaky or incorrect multi-tab eval results even when activePage was captured correctly — pass { page: activePage } to lock extraction to the intended tab.
  • In packages/evals/tasks/bench/act/multi_tab.ts, the V4 GAP note says extract lacks a { page } option, but nearby usage in tab_handling.ts shows it does; this mismatch can mislead future edits and preserve the tab-selection bug path — update the comment to reflect actual API support and intended usage.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/evals/tasks/bench/act/multi_tab.ts">

<violation number="1" location="packages/evals/tasks/bench/act/multi_tab.ts:53">
P3: The V4 GAP comment claims that `extract` has no `{ page }` option, but `tab_handling.ts` in the same directory already uses it successfully: `stagehand.extract(instruction, z.object({...}), { page: page1 })`. This comment is factually inaccurate and could mislead future maintainers into thinking they need to work around a nonexistent limitation. Consider updating the comment to reflect the actual v4 API or removing it.</violation>

<violation number="2" location="packages/evals/tasks/bench/act/multi_tab.ts:58">
P2: The final extraction can target a different active tab if focus changes after the URL check, even though `activePage` already identifies page2. `stagehand.extract` supports `{ page }`; pass the captured page to preserve the multi-tab assertion.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Runner as V4 Task Runner
    participant TaskDef as defineBenchV4Task
    participant Ctx as BenchV4TaskContext
    participant Stagehand as Stagehand (V4)
    participant Page as V4Page
    participant Target as Evaluation Site

    Note over Runner,Target: NEW: V4 Bench Task Execution Flow

    Runner->>TaskDef: load task definition (default export)
    TaskDef-->>Runner: { meta, fn: wrapperFn }

    Runner->>TaskDef: invoke wrapperFn(ctx)
    alt ctx.stagehand is undefined
        TaskDef->>TaskDef: throw "requires v4 harness (--sdk v4)"
    else stagehand available
        TaskDef->>Ctx: cast ctx to BenchV4TaskContext
        Note over Ctx: NEW: provides stagehand, page, logger<br/>page.url() is async

        Ctx->>Page: await page.goto(siteUrl)
        Page-->>Target: navigate

        Ctx->>Stagehand: stagehand.act(instruction)
        Stagehand->>Target: perform action (click/fill/scroll)
        Target-->>Stagehand: result
        Stagehand-->>Ctx: { message, ... }

        alt Extract instruction
            Ctx->>Stagehand: stagehand.extract(instruction, zodSchema)
            Note over Stagehand: NEW: schema required<br/>returns { data: ... }
            Stagehand->>Target: observe & extract
            Target-->>Stagehand: data
            Stagehand-->>Ctx: { data: ... }
        end

        alt Observe instruction
            Ctx->>Stagehand: stagehand.observe(instruction)
            Stagehand->>Target: generate observation candidates
            Target-->>Stagehand: Action[]
            Stagehand-->>Ctx: { data: Action[] }
            Ctx->>Stagehand: stagehand.act(observation)
            Note over Stagehand: replay Action from observe
        end

        Ctx->>Page: await page.url() (async)
        Page-->>Ctx: current URL
        Ctx->>Ctx: evaluate pass/fail
        Ctx-->>Runner: return TaskResult
    end

    alt Error in task
        Ctx->>Ctx: catch (error) → error.message or String(error)
        Ctx-->>Runner: return { _success: false, error: ... }
    end

    Note over Ctx,Stagehand: Cleanup (finally)
    Ctx->>Stagehand: stagehand.close()
    Stagehand-->>Ctx: done
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/evals/framework/defineTask.ts Outdated
Comment thread packages/evals/tasks/bench/act/hidden_input_dropdown.ts Outdated
Comment thread packages/evals/framework/types.ts
// active page here, so extract operates on it. v3 also used schemaless
// extract; v4 requires a schema. Single-word key to
// stay clear of the snake_case wire-casing bug (#14).
const { data: page2text } = await stagehand.extract(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The final extraction can target a different active tab if focus changes after the URL check, even though activePage already identifies page2. stagehand.extract supports { page }; pass the captured page to preserve the multi-tab assertion.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/act/multi_tab.ts, line 58:

<comment>The final extraction can target a different active tab if focus changes after the URL check, even though `activePage` already identifies page2. `stagehand.extract` supports `{ page }`; pass the captured page to preserve the multi-tab assertion.</comment>

<file context>
@@ -45,10 +50,18 @@ export default defineBenchTask(
+      // active page here, so extract operates on it. v3 also used schemaless
+      // extract; v4 requires a schema. Single-word key to
+      // stay clear of the snake_case wire-casing bug (#14).
+      const { data: page2text } = await stagehand.extract(
+        "extract the entire page text",
+        z.object({ extraction: z.string() }),
</file context>

Comment thread packages/evals/framework/defineTask.ts
}

const page2text = await v3.extract({ page: activePage });
// V4 GAP: extract has no { page } option (v3:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The V4 GAP comment claims that extract has no { page } option, but tab_handling.ts in the same directory already uses it successfully: stagehand.extract(instruction, z.object({...}), { page: page1 }). This comment is factually inaccurate and could mislead future maintainers into thinking they need to work around a nonexistent limitation. Consider updating the comment to reflect the actual v4 API or removing it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/evals/tasks/bench/act/multi_tab.ts, line 53:

<comment>The V4 GAP comment claims that `extract` has no `{ page }` option, but `tab_handling.ts` in the same directory already uses it successfully: `stagehand.extract(instruction, z.object({...}), { page: page1 })`. This comment is factually inaccurate and could mislead future maintainers into thinking they need to work around a nonexistent limitation. Consider updating the comment to reflect the actual v4 API or removing it.</comment>

<file context>
@@ -45,10 +50,18 @@ export default defineBenchTask(
       }
 
-      const page2text = await v3.extract({ page: activePage });
+      // V4 GAP: extract has no { page } option (v3:
+      // v3.extract({ page: activePage })) — the target page is already the
+      // active page here, so extract operates on it. v3 also used schemaless
</file context>

shriyatheunicorn and others added 4 commits July 29, 2026 13:51
Replaced stagehand act with replayObservedAction for flight observation.
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Added note about additional instructions for Stagehand.

@shriyatheunicorn shriyatheunicorn left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

resolved

Comment thread packages/evals/framework/defineTask.ts
@browserbase browserbase deleted a comment from cubic-dev-ai Bot Jul 29, 2026
@browserbase browserbase deleted a comment from cubic-dev-ai Bot Jul 29, 2026
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.

1 participant