Skip to content

Commit 1f2b2c5

Browse files
authored
simplify stagehand method calls (#417)
* simplify stagehand method calls * revert example * changeset * use new syntax in evals * new extract eval * allow no observe input
1 parent 737b4b2 commit 1f2b2c5

10 files changed

+164
-62
lines changed

Diff for: .changeset/hungry-scissors-mix.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": minor
3+
---
4+
5+
Simplify Stagehand method calls by allowing a simple string input instead of an options object.

Diff for: evals/evals.config.json

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"tasks": [
3+
{
4+
"name": "extract_repo_name",
5+
"categories": ["extract"]
6+
},
37
{
48
"name": "amazon_add_to_cart",
59
"categories": ["act"]

Diff for: evals/tasks/arxiv.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ export const arxiv: EvalFunction = async ({
1717
try {
1818
await stagehand.page.goto("https://arxiv.org/search/");
1919

20-
await stagehand.page.act({
21-
action: "search for papers about web agents with multimodal models",
22-
});
20+
await stagehand.page.act(
21+
"search for papers about web agents with multimodal models",
22+
);
2323

2424
const paper_links = await stagehand.page.extract({
2525
instruction: "extract the titles and links for two papers",

Diff for: evals/tasks/expedia.ts

+7-8
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ export const expedia: EvalFunction = async ({ modelName, logger }) => {
1111

1212
try {
1313
await stagehand.page.goto("https://www.expedia.com/flights");
14-
await stagehand.page.act({
15-
action:
16-
"find round-trip flights from San Francisco (SFO) to Toronto (YYZ) for Jan 1, 2025 (up to one to two weeks)",
17-
});
18-
await stagehand.page.act({ action: "Go to the first non-stop flight" });
19-
await stagehand.page.act({ action: "select the cheapest flight" });
20-
await stagehand.page.act({ action: "click on the first non-stop flight" });
21-
await stagehand.page.act({ action: "Take me to the checkout page" });
14+
await stagehand.page.act(
15+
"find round-trip flights from San Francisco (SFO) to Toronto (YYZ) for Jan 1, 2025 (up to one to two weeks)",
16+
);
17+
await stagehand.page.act("Go to the first non-stop flight");
18+
await stagehand.page.act("select the cheapest flight");
19+
await stagehand.page.act("click on the first non-stop flight");
20+
await stagehand.page.act("Take me to the checkout page");
2221

2322
const url = stagehand.page.url();
2423
return {

Diff for: evals/tasks/extract_repo_name.ts

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { EvalFunction } from "../../types/evals";
2+
import { initStagehand } from "../initStagehand";
3+
4+
export const extract_github_commits: EvalFunction = async ({
5+
modelName,
6+
logger,
7+
}) => {
8+
const { stagehand, initResponse } = await initStagehand({
9+
modelName,
10+
logger,
11+
});
12+
13+
const { debugUrl, sessionUrl } = initResponse;
14+
15+
try {
16+
await stagehand.page.goto("https://github.com/facebook/react");
17+
18+
const { extraction } = await stagehand.page.extract(
19+
"extract the repo name",
20+
);
21+
22+
logger.log({
23+
message: "Extracted repo name",
24+
level: 1,
25+
auxiliary: {
26+
repo_name: {
27+
value: extraction,
28+
type: "object",
29+
},
30+
},
31+
});
32+
33+
await stagehand.close();
34+
35+
return {
36+
_success: extraction === "react",
37+
extraction,
38+
debugUrl,
39+
sessionUrl,
40+
logs: logger.getLogs(),
41+
};
42+
} catch (error) {
43+
console.error("Error or timeout occurred:", error);
44+
45+
await stagehand.close();
46+
47+
return {
48+
_success: false,
49+
error: JSON.parse(JSON.stringify(error, null, 2)),
50+
debugUrl,
51+
sessionUrl,
52+
logs: logger.getLogs(),
53+
};
54+
}
55+
};

Diff for: evals/tasks/google_jobs.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ export const google_jobs: EvalFunction = async ({
1616

1717
try {
1818
await stagehand.page.goto("https://www.google.com/");
19-
await stagehand.page.act({ action: "click on the about page" });
20-
await stagehand.page.act({ action: "click on the careers page" });
21-
await stagehand.page.act({ action: "input data scientist into role" });
22-
await stagehand.page.act({ action: "input new york city into location" });
23-
await stagehand.page.act({ action: "click on the search button" });
24-
await stagehand.page.act({ action: "click on the first job link" });
19+
await stagehand.page.act("click on the about page");
20+
await stagehand.page.act("click on the careers page");
21+
await stagehand.page.act("input data scientist into role");
22+
await stagehand.page.act("input new york city into location");
23+
await stagehand.page.act("click on the search button");
24+
await stagehand.page.act("click on the first job link");
2525

2626
const jobDetails = await stagehand.page.extract({
2727
instruction:

Diff for: evals/tasks/homedepot.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ export const homedepot: EvalFunction = async ({
1717

1818
try {
1919
await stagehand.page.goto("https://www.homedepot.com/");
20-
await stagehand.page.act({ action: "search for gas grills" });
21-
await stagehand.page.act({ action: "click on the best selling gas grill" });
22-
await stagehand.page.act({ action: "click on the Product Details" });
23-
await stagehand.page.act({ action: "find the Primary Burner BTU" });
20+
await stagehand.page.act("search for gas grills");
21+
await stagehand.page.act("click on the best selling gas grill");
22+
await stagehand.page.act("click on the Product Details");
23+
await stagehand.page.act("find the Primary Burner BTU");
2424

2525
const productSpecs = await stagehand.page.extract({
2626
instruction: "Extract the Primary exact Burner BTU of the product",

Diff for: evals/tasks/wikipedia.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ export const wikipedia: EvalFunction = async ({ modelName, logger }) => {
1010
const { debugUrl, sessionUrl } = initResponse;
1111

1212
await stagehand.page.goto(`https://en.wikipedia.org/wiki/Baseball`);
13-
await stagehand.page.act({
14-
action: 'click the "hit and run" link in this article',
15-
});
13+
await stagehand.page.act('click the "hit and run" link in this article');
1614

1715
const url = "https://en.wikipedia.org/wiki/Hit_and_run_(baseball)";
1816
const currentUrl = stagehand.page.url();

Diff for: lib/StagehandPage.ts

+62-33
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { LLMClient } from "./llm/LLMClient";
77
import { ActOptions, ActResult, GotoOptions, Stagehand } from "./index";
88
import { StagehandActHandler } from "./handlers/actHandler";
99
import { StagehandContext } from "./StagehandContext";
10-
import { Page } from "../types/page";
10+
import { Page, defaultExtractSchema } from "../types/page";
1111
import {
1212
ExtractOptions,
1313
ExtractResult,
@@ -285,19 +285,25 @@ export class StagehandPage {
285285
}
286286
}
287287

288-
async act({
289-
action,
290-
modelName,
291-
modelClientOptions,
292-
useVision = "fallback",
293-
variables = {},
294-
domSettleTimeoutMs,
295-
}: ActOptions): Promise<ActResult> {
288+
async act(actionOrOptions: string | ActOptions): Promise<ActResult> {
296289
if (!this.actHandler) {
297290
throw new Error("Act handler not initialized");
298291
}
299292

300-
useVision = useVision ?? "fallback";
293+
const options: ActOptions =
294+
typeof actionOrOptions === "string"
295+
? { action: actionOrOptions }
296+
: actionOrOptions;
297+
298+
const {
299+
action,
300+
modelName,
301+
modelClientOptions,
302+
useVision = "fallback",
303+
variables = {},
304+
domSettleTimeoutMs,
305+
} = options;
306+
301307
const requestId = Math.random().toString(36).substring(2);
302308
const llmClient: LLMClient = modelName
303309
? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
@@ -361,18 +367,30 @@ export class StagehandPage {
361367
});
362368
}
363369

364-
async extract<T extends z.AnyZodObject>({
365-
instruction,
366-
schema,
367-
modelName,
368-
modelClientOptions,
369-
domSettleTimeoutMs,
370-
useTextExtract,
371-
}: ExtractOptions<T>): Promise<ExtractResult<T>> {
370+
async extract<T extends z.AnyZodObject = typeof defaultExtractSchema>(
371+
instructionOrOptions: string | ExtractOptions<T>,
372+
): Promise<ExtractResult<T>> {
372373
if (!this.extractHandler) {
373374
throw new Error("Extract handler not initialized");
374375
}
375376

377+
const options: ExtractOptions<T> =
378+
typeof instructionOrOptions === "string"
379+
? {
380+
instruction: instructionOrOptions,
381+
schema: defaultExtractSchema as T,
382+
}
383+
: instructionOrOptions;
384+
385+
const {
386+
instruction,
387+
schema,
388+
modelName,
389+
modelClientOptions,
390+
domSettleTimeoutMs,
391+
useTextExtract,
392+
} = options;
393+
376394
const requestId = Math.random().toString(36).substring(2);
377395
const llmClient = modelName
378396
? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
@@ -432,17 +450,30 @@ export class StagehandPage {
432450
});
433451
}
434452

435-
async observe(options?: ObserveOptions): Promise<ObserveResult[]> {
453+
async observe(
454+
instructionOrOptions?: string | ObserveOptions,
455+
): Promise<ObserveResult[]> {
436456
if (!this.observeHandler) {
437457
throw new Error("Observe handler not initialized");
438458
}
439459

460+
const options: ObserveOptions =
461+
typeof instructionOrOptions === "string"
462+
? { instruction: instructionOrOptions }
463+
: instructionOrOptions || {};
464+
465+
const {
466+
instruction = "Find actions that can be performed on this page.",
467+
modelName,
468+
modelClientOptions,
469+
useVision = false,
470+
domSettleTimeoutMs,
471+
useAccessibilityTree = false,
472+
} = options;
473+
440474
const requestId = Math.random().toString(36).substring(2);
441-
const llmClient = options?.modelName
442-
? this.stagehand.llmProvider.getClient(
443-
options.modelName,
444-
options.modelClientOptions,
445-
)
475+
const llmClient = modelName
476+
? this.stagehand.llmProvider.getClient(modelName, modelClientOptions)
446477
: this.llmClient;
447478

448479
this.stagehand.log({
@@ -451,7 +482,7 @@ export class StagehandPage {
451482
level: 1,
452483
auxiliary: {
453484
instruction: {
454-
value: options?.instruction,
485+
value: instruction,
455486
type: "string",
456487
},
457488
requestId: {
@@ -463,23 +494,21 @@ export class StagehandPage {
463494
type: "string",
464495
},
465496
useAccessibilityTree: {
466-
value: options?.useAccessibilityTree ? "true" : "false",
497+
value: useAccessibilityTree ? "true" : "false",
467498
type: "boolean",
468499
},
469500
},
470501
});
471502

472503
return this.observeHandler
473504
.observe({
474-
instruction:
475-
options?.instruction ??
476-
"Find actions that can be performed on this page.",
505+
instruction,
477506
llmClient,
478-
useVision: options?.useVision ?? false,
507+
useVision,
479508
fullPage: false,
480509
requestId,
481-
domSettleTimeoutMs: options?.domSettleTimeoutMs,
482-
useAccessibilityTree: options?.useAccessibilityTree ?? false,
510+
domSettleTimeoutMs,
511+
useAccessibilityTree,
483512
})
484513
.catch((e) => {
485514
this.stagehand.log({
@@ -500,7 +529,7 @@ export class StagehandPage {
500529
type: "string",
501530
},
502531
instruction: {
503-
value: options?.instruction,
532+
value: instruction,
504533
type: "string",
505534
},
506535
},

Diff for: types/page.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type {
33
BrowserContext as PlaywrightContext,
44
Page as PlaywrightPage,
55
} from "@playwright/test";
6-
import type { z } from "zod";
6+
import { z } from "zod";
77
import type {
88
ActOptions,
99
ActResult,
@@ -13,12 +13,24 @@ import type {
1313
ObserveResult,
1414
} from "./stagehand";
1515

16+
export const defaultExtractSchema = z.object({
17+
extraction: z.string(),
18+
});
19+
1620
export interface Page extends Omit<PlaywrightPage, "on"> {
17-
act: (options: ActOptions) => Promise<ActResult>;
18-
extract: <T extends z.AnyZodObject>(
21+
act(action: string): Promise<ActResult>;
22+
act(options: ActOptions): Promise<ActResult>;
23+
24+
extract(
25+
instruction: string,
26+
): Promise<ExtractResult<typeof defaultExtractSchema>>;
27+
extract<T extends z.AnyZodObject>(
1928
options: ExtractOptions<T>,
20-
) => Promise<ExtractResult<T>>;
21-
observe: (options?: ObserveOptions) => Promise<ObserveResult[]>;
29+
): Promise<ExtractResult<T>>;
30+
31+
observe(): Promise<ObserveResult[]>;
32+
observe(instruction: string): Promise<ObserveResult[]>;
33+
observe(options?: ObserveOptions): Promise<ObserveResult[]>;
2234

2335
on: {
2436
(event: "popup", listener: (page: Page) => unknown): Page;

0 commit comments

Comments
 (0)