Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a6b568e
evals: port act/extract/observe bench tasks to the v4 SDK
shriyatheunicorn Jul 29, 2026
c831658
Update packages/evals/tasks/bench/act/osr_in_spif.ts
shriyatheunicorn Jul 29, 2026
89c3206
evals: address review findings - error sanitization, replay click fid…
shriyatheunicorn Jul 29, 2026
de97287
evals: use cross-frame locators for iframe form assertions (#2431)
shriyatheunicorn Jul 29, 2026
faf57e0
Update packages/evals/tasks/bench/act/custom_dropdown.ts
shriyatheunicorn Jul 29, 2026
e77fc2d
evals: fail fast when a v4 bench task is invoked with a v3 context
shriyatheunicorn Jul 29, 2026
cbf7cac
evals: replay observed actions through act(Action) (#2427) and drop t…
shriyatheunicorn Jul 29, 2026
a4840d3
Update extract_hamilton_weather.ts
shriyatheunicorn Jul 29, 2026
f53d0c0
Update extract_hamilton_weather.ts
shriyatheunicorn Jul 29, 2026
e817832
Refactor defineBenchV4Task to defineBenchTask
shriyatheunicorn Jul 29, 2026
6745b4c
Update packages/evals/tasks/bench/act/shadow_dom.ts
shriyatheunicorn Jul 29, 2026
30c4d4e
Update packages/evals/tasks/bench/act/multi_tab.ts
shriyatheunicorn Jul 29, 2026
9d890a6
Revert "Refactor defineBenchV4Task to defineBenchTask"
shriyatheunicorn Jul 29, 2026
91f5752
evals: fold typesV4 into types and drop the extra module
shriyatheunicorn Jul 29, 2026
678b725
Update flight observation method in google_flights.ts
shriyatheunicorn Jul 29, 2026
0731d06
Update packages/evals/tasks/bench/act/hidden_input_dropdown.ts
shriyatheunicorn Jul 29, 2026
f38304e
Refactor type imports in types.ts
shriyatheunicorn Jul 29, 2026
c481923
Update documentation for systemPrompt in types.ts
shriyatheunicorn Jul 29, 2026
c8e31b2
Update csr_in_oopif.ts
shriyatheunicorn Jul 29, 2026
d62fbc1
Add comment placeholder in publications schema
shriyatheunicorn Jul 29, 2026
e922097
Fix syntax error in extract_csa.ts
shriyatheunicorn Jul 29, 2026
1d6ee88
Add publication details to extract_csa.ts schema
shriyatheunicorn Jul 29, 2026
80a24e0
Clean up extract_csa.ts by removing excess code
shriyatheunicorn Jul 29, 2026
8b68d62
Add null check for context in defineTask function
shriyatheunicorn Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/evals/framework/defineTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
TaskDefinition,
TaskMeta,
TaskResult,
BenchV4TaskContext,
} from "./types.js";

/**
Expand Down Expand Up @@ -43,6 +44,31 @@ export function defineBenchTask(
};
}

/**
* Define a bench tier task ported to the Stagehand v4 SDK.
* v4 bench tasks receive { stagehand, page, logger, input, ... } and return
* TaskResult. They live under tasks/bench/ and are selected via --sdk v4.
*/
export function defineBenchV4Task(
Comment thread
shriyatheunicorn marked this conversation as resolved.
meta: BenchTaskMeta,
fn: (ctx: BenchV4TaskContext) => Promise<void | TaskResult>,
): TaskDefinition {
return {
__taskDefinition: true,
meta,
// Fail fast with a clear message if a v3-context runner invokes a v4
// task — the v4 init/dispatch path lands with the harness change.
fn: (ctx) => {
if (!ctx || !("stagehand" in ctx) || ctx.stagehand === undefined) {
throw new Error(
`Task "${meta.name}" requires the v4 harness (--sdk v4); it was invoked with a v3 context.`,
);
}
return fn(ctx as unknown as BenchV4TaskContext);
},
};
}

/**
* Generic defineTask — for cases where the tier is ambiguous at definition time.
* Prefer defineCoreTask / defineBenchTask for better type inference.
Expand Down
42 changes: 40 additions & 2 deletions packages/evals/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
*
* A third tier ("interpret") is planned but not yet implemented.
*/
import type { AgentToolMode, AgentInstance, AvailableModel, LogLine, V3 } from "stagehand-v3";
import type { AvailableModel, LogLine } from "stagehand-v3";
import type { Page, Stagehand } from "@browserbasehq/stagehand";
import type {
CorePageHandle,
CoreSession,
Expand Down Expand Up @@ -35,6 +36,13 @@ export interface TaskMeta {
export interface BenchTaskMeta extends TaskMeta {
/** Override the default model list for this specific task. */
models?: string[];
/**
* Custom system prompt passed to the Stagehand instance at init time
* (v3 `V3Options.systemPrompt` / v4 `StagehandInitParams.systemPrompt`).
* Used by tasks that test instruction-following (e.g. combination/instructions).
* Additional instructions applied when initializing Stagehand for this task.
*/
systemPrompt?: string;
Comment thread
shriyatheunicorn marked this conversation as resolved.
}

/** Context provided to core (tier 1) tasks. */
Expand Down Expand Up @@ -128,7 +136,7 @@ export interface TaskDefinition {
/** User-provided metadata. */
meta: TaskMeta | BenchTaskMeta;
/** The task function. */
fn: (ctx: CoreTaskContext | BenchTaskContext) => Promise<void | TaskResult>;
fn: (ctx: CoreTaskContext | BenchTaskContext | BenchV4TaskContext) => Promise<void | TaskResult>;
Comment thread
shriyatheunicorn marked this conversation as resolved.
Comment thread
shriyatheunicorn marked this conversation as resolved.
/** Which tier this task was defined for (set during discovery from directory). */
tier?: Tier;
}
Expand Down Expand Up @@ -162,3 +170,33 @@ export interface TaskRegistry {
/** Lookup by category. */
byCategory: Map<string, DiscoveredTask[]>;
}

/**
* Context type for bench tasks ported to the V4Stagehand v4 SDK.
*
* Deliberately mirrors BenchTaskContext (types.ts) so per-task diffs between
* the v3 and v4 suites stay 1:1, with the v3 surface swapped for the v4 one:
* `v3` → `stagehand`, Playwright `page` → v4 `V4Page`. Keeping `page` typed as
* the v4 V4Page makes any Playwright API usage in a ported task a type error.
*/

export interface BenchV4TaskContext {
/** V4Stagehand v4 client instance. */
stagehand: V4Stagehand;
/** v4 page object (RPC-backed — url()/title() are async). */
page: V4Page;
/** Eval logger. Note: the v4 SDK itself logs to the console, not here. */
logger: EvalLogger;
/** Full eval input (name, modelName, params). */
input: {
name: string;
modelName: AvailableModel;
params?: Record<string, unknown>;
};
/** Model used for this run. */
modelName: AvailableModel;
/** Debug URL (unavailable from the v4 SDK — always empty for now). */
debugUrl: string;
/** Session URL (Browserbase; constructed from the session ID). */
sessionUrl: string;
}
17 changes: 8 additions & 9 deletions packages/evals/tasks/bench/act/amazon_add_to_cart.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask(
export default defineBenchV4Task(
{ name: "amazon_add_to_cart" },
async ({ logger, debugUrl, sessionUrl, v3 }) => {
async ({ logger, debugUrl, sessionUrl, stagehand, page }) => {
try {
const page = v3.context.pages()[0];
await page.goto("https://browserbase.github.io/stagehand-eval-sites/sites/amazon/");

await v3.act("click the 'Add to Cart' button");
await stagehand.act("click the 'Add to Cart' button");

await v3.act("click the 'Proceed to checkout' button");
await stagehand.act("click the 'Proceed to checkout' button");

const currentUrl = page.url();
const currentUrl = await page.url();
const expectedUrl =
"https://browserbase.github.io/stagehand-eval-sites/sites/amazon/sign-in.html";

Expand All @@ -27,13 +26,13 @@ export default defineBenchTask(
} catch (error) {
return {
_success: false,
error: error,
error: error instanceof Error ? error.message : String(error),
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await v3.close();
await stagehand.close();
}
},
);
58 changes: 30 additions & 28 deletions packages/evals/tasks/bench/act/bidnet.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask({ name: "bidnet" }, async ({ logger, debugUrl, sessionUrl, v3 }) => {
try {
const page = v3.context.pages()[0];
await page.goto("https://www.bidnetdirect.com/");
export default defineBenchV4Task(
{ name: "bidnet" },
async ({ logger, debugUrl, sessionUrl, stagehand, page }) => {
try {
await page.goto("https://www.bidnetdirect.com/");

await v3.act('Click on the "Construction" keyword');
await stagehand.act('Click on the "Construction" keyword');

const expectedUrl =
"https://www.bidnetdirect.com/public/solicitations/open?keywords=Construction";
const currentUrl = page.url();
const expectedUrl =
"https://www.bidnetdirect.com/public/solicitations/open?keywords=Construction";
const currentUrl = await page.url();

return {
_success: currentUrl.startsWith(expectedUrl),
currentUrl,
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} catch (error) {
return {
_success: false,
error: error,
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await v3.close();
}
});
return {
_success: currentUrl.startsWith(expectedUrl),
currentUrl,
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} catch (error) {
return {
_success: false,
error: error instanceof Error ? error.message : String(error),
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await stagehand.close();
}
},
);
13 changes: 6 additions & 7 deletions packages/evals/tasks/bench/act/checkboxes.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask(
export default defineBenchV4Task(
{ name: "checkboxes" },
async ({ debugUrl, sessionUrl, v3, logger }) => {
async ({ debugUrl, sessionUrl, stagehand, page, logger }) => {
try {
const page = v3.context.pages()[0];
await page.goto("https://browserbase.github.io/stagehand-eval-sites/sites/checkboxes/");

await v3.act("click the 'baseball' option");
await stagehand.act("click the 'baseball' option");

await v3.act("click the 'netball' option");
await stagehand.act("click the 'netball' option");

const baseballChecked = await page
.locator('input[type="checkbox"][name="sports"][value="baseball"]')
Expand All @@ -34,7 +33,7 @@ export default defineBenchTask(
logs: logger.getLogs(),
};
} finally {
await v3.close();
await stagehand.close();
}
},
);
22 changes: 14 additions & 8 deletions packages/evals/tasks/bench/act/csr_in_oopif.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { z } from "zod";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask(
export default defineBenchV4Task(
{ name: "csr_in_oopif" },
async ({ debugUrl, sessionUrl, v3, logger }) => {
async ({ debugUrl, sessionUrl, stagehand, page, logger }) => {
Comment thread
shriyatheunicorn marked this conversation as resolved.
// this eval is designed to test whether stagehand can successfully
// click inside an CSR (closed mode shadow) root that is inside an
// OOPIF (out of process iframe)

try {
const page = v3.context.pages()[0];
await page.goto(
"https://browserbase.github.io/stagehand-eval-sites/sites/closed-shadow-root-in-oopif/",
);
await v3.act("click the button");
await stagehand.act("click the button");

await new Promise((resolve) => setTimeout(resolve, 1000));
const extraction = await v3.extract("extract the entire page text");

// v3 used schemaless extract; v4 requires a schema.
// Single-word key to stay clear of the snake_case wire-casing bug (#14).
const { data: extraction } = await stagehand.extract(
"extract the entire page text",
z.object({ extraction: z.string() }),
);

const pageText = extraction.extraction;

Expand All @@ -38,13 +44,13 @@ export default defineBenchTask(
} catch (error) {
return {
_success: false,
message: `error: ${error.message}`,
error: error instanceof Error ? error.message : String(error),
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await v3.close();
await stagehand.close();
}
},
);
21 changes: 13 additions & 8 deletions packages/evals/tasks/bench/act/csr_in_spif.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { z } from "zod";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask(
export default defineBenchV4Task(
{ name: "csr_in_spif" },
async ({ debugUrl, sessionUrl, v3, logger }) => {
async ({ debugUrl, sessionUrl, stagehand, page, logger }) => {
// this eval is designed to test whether stagehand can successfully
// click inside an CSR (closed mode shadow) root that is inside an
// SPIF (same process iframe)

try {
const page = v3.context.pages()[0];
await page.goto(
"https://browserbase.github.io/stagehand-eval-sites/sites/closed-shadow-dom-in-spif/",
);
await v3.act("click the button");
await stagehand.act("click the button");

const extraction = await v3.extract("extract the entire page text");
// v3 used schemaless extract; v4 requires a schema.
// Single-word key to stay clear of the snake_case wire-casing bug (#14).
const { data: extraction } = await stagehand.extract(
"extract the entire page text",
z.object({ extraction: z.string() }),
);

const pageText = extraction.extraction;

Expand All @@ -37,13 +42,13 @@ export default defineBenchTask(
} catch (error) {
return {
_success: false,
message: `error: ${error.message}`,
error: error instanceof Error ? error.message : String(error),
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await v3.close();
await stagehand.close();
}
},
);
18 changes: 8 additions & 10 deletions packages/evals/tasks/bench/act/custom_dropdown.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { defineBenchTask } from "../../../framework/defineTask.js";
import { defineBenchV4Task } from "../../../framework/defineTask.js";

export default defineBenchTask(
export default defineBenchV4Task(
{ name: "custom_dropdown" },
async ({ debugUrl, sessionUrl, v3, logger }) => {
async ({ debugUrl, sessionUrl, stagehand, page, logger }) => {
/**
* This eval is meant to test whether we do not incorrectly attempt
* the selectOptionFromDropdown method (defined in actHandlerUtils.ts) on a
Expand All @@ -13,14 +13,12 @@ export default defineBenchTask(
*/

try {
const page = v3.context.pages()[0];
await page.goto("https://browserbase.github.io/stagehand-eval-sites/sites/expand-dropdown/");

await v3.act("choose Canada from the 'Select a Country' dropdown");
await stagehand.act("choose Canada from the 'Select a Country' dropdown");

// to test, we'll grab the full a11y tree, and make sure it contains 'Canada'
const extraction = await v3.extract();
const fullTree = extraction.pageText;
// read the rendered page text directly — no second model call
const fullTree = await page.locator("#chosenValue").innerText();

if (fullTree.includes("Canada")) {
return {
Expand All @@ -40,13 +38,13 @@ export default defineBenchTask(
} catch (error) {
return {
_success: false,
message: `error attempting to select an option from the dropdown: ${error.message}`,
message: `error attempting to select an option from the dropdown: ${(error as Error).message}`,
debugUrl,
sessionUrl,
logs: logger.getLogs(),
};
} finally {
await v3.close();
await stagehand.close();
}
},
);
Loading
Loading