From fe6569fd0e729ee35596a0bb896f3ffc18847dbc Mon Sep 17 00:00:00 2001 From: Filip Michalsky Date: Wed, 25 Sep 2024 20:25:44 -0400 Subject: [PATCH] change act api output --- examples/index.ts | 25 ++++++++++++++++++++----- lib/index.ts | 41 +++++++++++++++++++++++++---------------- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/examples/index.ts b/examples/index.ts index 2bad293c..d6ae0871 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -9,10 +9,20 @@ async function example() { debugDom: true, }); - await stagehand.init({ modelName: "claude-3-5-sonnet-20240620" }); // optionally specify model_name, defaults to "gpt-4o" (as of sept 18, 2024, we need to specify the model name with date, changing on 10/2/2024) + await stagehand.init({ modelName: "claude-3-5-sonnet-20240620" }); await stagehand.page.goto("https://www.nytimes.com/games/wordle/index.html"); - await stagehand.act({ action: "start the game" }); - await stagehand.act({ action: "close tutorial popup" }); + + const startGameResult = await stagehand.act({ action: "start the game" }); + if (!startGameResult.success) { + console.error("Failed to start the game:", startGameResult.error); + return; + } + + const closeTutorialResult = await stagehand.act({ action: "close tutorial popup" }); + if (!closeTutorialResult.success) { + console.error("Failed to close tutorial:", closeTutorialResult.error); + // Decide whether to continue or return based on the importance of this action + } let guesses: { guess: string | null; description: string | null }[] = []; for (let i = 0; i < 6; i++) { @@ -22,8 +32,13 @@ async function example() { throw new Error("no response when asking for a guess"); } - await stagehand.page.locator("body").pressSequentially(response); - await stagehand.page.keyboard.press("Enter"); + try { + await stagehand.page.locator("body").pressSequentially(response); + await stagehand.page.keyboard.press("Enter"); + } catch (error) { + console.error("Failed to input guess:", error.message); + continue; + } const guess = await stagehand.extract({ instruction: "extract the five letter guess at the bottom", diff --git a/lib/index.ts b/lib/index.ts index 9fd655e9..878a872e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -251,7 +251,7 @@ export class Stagehand { } } - async observe(observation: string, modelName?: string): Promise { + async observe(observation: string, modelName?: string): Promise<{ success: boolean; result?: string; error?: string }> { this.log({ category: "observation", message: `starting observation: ${observation}`, @@ -278,7 +278,7 @@ export class Stagehand { message: `no element found for ${observation}`, level: 1 }); - return null; + return { success: false, error: `No element found for observation: ${observation}` }; } this.log({ @@ -299,13 +299,18 @@ export class Stagehand { // the locator string found by the LLM might resolve to multiple places in the DOM const firstLocator = this.page.locator(locatorString).first(); - await expect(firstLocator).toBeAttached(); + try { + await expect(firstLocator).toBeAttached(); + } catch (error) { + return { success: false, error: `Element found but not attached: ${error.message}` }; + } + const observationId = await this.recordObservation( observation, locatorString ); - return observationId; + return { success: true, result: observationId }; } async ask(question: string, modelName?: string): Promise { return ask({ @@ -344,7 +349,7 @@ export class Stagehand { steps?: string; chunksSeen?: Array; modelName?: string; - }): Promise { + }): Promise<{ success: boolean; error?: string }> { this.log({ category: "action", message: `taking action: ${action}`, @@ -390,7 +395,7 @@ export class Stagehand { level: 1 }); this.recordAction(action, null); - return; + return { success: false, error: "Action could not be performed after checking all chunks" }; } } @@ -416,22 +421,24 @@ export class Stagehand { }); const locator = await this.page.locator(`xpath=${path}`).first(); - if (method === 'scrollIntoView') { // this is not a native playwright function + if (method === 'scrollIntoView') { await locator.evaluate((element) => { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); }); } else if (typeof locator[method as keyof typeof locator] === "function") { - - const isLink = await locator.evaluate((element) => { - return element.tagName.toLowerCase() === 'a' && element.hasAttribute('href'); - }); - - // Perform the action - //@ts-ignore playwright's TS does not think this is valid, but we proved it with the check above - await locator[method](...args); + try { + //@ts-ignore playwright's TS does not think this is valid, but we proved it with the check above + await locator[method](...args); + } catch (error) { + return { success: false, error: `Failed to perform ${method} on element: ${error.message}` }; + } // Check if a new page was created, but only if the method is 'click' if (method === 'click') { + const isLink = await locator.evaluate((element) => { + return element.tagName.toLowerCase() === 'a' && element.hasAttribute('href'); + }); + if (isLink) { // Create a promise that resolves when a new page is created console.log("clicking link"); @@ -452,7 +459,7 @@ export class Stagehand { } } } else { - throw new Error(`stagehand: chosen method ${method} is invalid`); + return { success: false, error: `Invalid method: ${method}` }; } if (!response.completed) { @@ -468,6 +475,8 @@ export class Stagehand { modelName, }); } + + return { success: true }; } setPage(page: Page) { this.page = page;