Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 15 additions & 1 deletion docs/references/act.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,25 @@ await page.act(observe_result: ObserveResult) -> ActResult
The action that was performed.
</ParamField>

<ParamField
path="playwrightCommand"
type="PlaywrightCommandResult"
optional
>
Details about the underlying Playwright method that Stagehand executed,
including the selector and arguments that were used.
</ParamField>

```Example Response
{
success: true,
message: 'Action [scrollTo] performed successfully on selector: /html[1]',
action: 'Scrollable area of the page where user can navigate to the pricing section or other parts of the page'
action: 'Scrollable area of the page where user can navigate to the pricing section or other parts of the page',
playwrightCommand: {
method: 'scrollTo',
selector: '/html[1]',
arguments: []
}
}
```

Expand Down
74 changes: 70 additions & 4 deletions lib/handlers/actHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@ export class StagehandActHandler {
});

const method = observe.method;
const args = Array.isArray(observe.arguments) ? [...observe.arguments] : [];
const selector =
this.normalizeSelector(observe.selector) ?? observe.selector;
let attemptedPlaywrightCommand = this.buildPlaywrightCommand(
method,
args,
selector,
);

if (!method) {
this.logger({
category: "action",
message: "ObserveResult did not include a Playwright method",
level: 1,
auxiliary: {
observeResult: {
value: JSON.stringify(observe),
type: "object",
},
},
});
return {
success: false,
message:
"Unable to perform action: The ObserveResult did not include a Playwright method.",
action: observe.description || "ObserveResult action",
playwrightCommand: attemptedPlaywrightCommand,
};
}

if (method === "not-supported") {
this.logger({
category: "action",
Expand All @@ -92,11 +122,9 @@ export class StagehandActHandler {
success: false,
message: `Unable to perform action: The method '${method}' is not supported in ObserveResult. Please use a supported Playwright locator method.`,
action: observe.description || `ObserveResult action (${method})`,
playwrightCommand: attemptedPlaywrightCommand,
};
}
const args = observe.arguments ?? [];
// remove the xpath prefix on the selector
const selector = observe.selector.replace("xpath=", "");

try {
await this._performPlaywrightMethod(
Expand All @@ -110,6 +138,7 @@ export class StagehandActHandler {
success: true,
message: `Action [${method}] performed successfully on selector: ${selector}`,
action: observe.description || `ObserveResult action (${method})`,
playwrightCommand: attemptedPlaywrightCommand,
};
} catch (err) {
if (
Expand All @@ -129,6 +158,7 @@ export class StagehandActHandler {
success: false,
message: `Failed to perform act: ${err.message}`,
action: observe.description || `ObserveResult action (${method})`,
playwrightCommand: attemptedPlaywrightCommand,
};
}
// We will try to use observeAct on a failed ObserveResult-act if selfHeal is true
Expand Down Expand Up @@ -165,21 +195,30 @@ export class StagehandActHandler {
success: false,
message: `Failed to self heal act: Element not found.`,
action: actCommand,
playwrightCommand: attemptedPlaywrightCommand,
};
}
const element: ObserveResult = observeResults[0];
const fallbackSelector =
this.normalizeSelector(element.selector) ?? element.selector;
attemptedPlaywrightCommand = this.buildPlaywrightCommand(
observe.method,
observe.arguments,
fallbackSelector,
);
Comment on lines +204 to +208
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: self-heal fallback uses wrong method/args

uses observe.method and observe.arguments (from original failed attempt) instead of element.method and element.arguments from the newly observed element

Suggested change
attemptedPlaywrightCommand = this.buildPlaywrightCommand(
observe.method,
observe.arguments,
fallbackSelector,
);
attemptedPlaywrightCommand = this.buildPlaywrightCommand(
element.method,
element.arguments,
fallbackSelector,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/handlers/actHandler.ts
Line: 204:208

Comment:
**logic:** self-heal fallback uses wrong method/args

uses `observe.method` and `observe.arguments` (from original failed attempt) instead of `element.method` and `element.arguments` from the newly observed element

```suggestion
        attemptedPlaywrightCommand = this.buildPlaywrightCommand(
          element.method,
          element.arguments,
          fallbackSelector,
        );
```

How can I resolve this? If you propose a fix, please make it concise.

await this._performPlaywrightMethod(
// override previously provided method and arguments
observe.method,
observe.arguments,
// only update selector
element.selector,
fallbackSelector,
domSettleTimeoutMs,
Comment on lines 209 to 215
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: self-heal uses original method/args instead of new element's

the comment says "override previously provided method and arguments" but the code does the opposite - it uses the original observe.method and observe.arguments instead of using the new element.method and element.arguments from the fresh observation

Suggested change
await this._performPlaywrightMethod(
// override previously provided method and arguments
observe.method,
observe.arguments,
// only update selector
element.selector,
fallbackSelector,
domSettleTimeoutMs,
await this._performPlaywrightMethod(
element.method,
element.arguments,
fallbackSelector,
domSettleTimeoutMs,
);
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/handlers/actHandler.ts
Line: 209:215

Comment:
**logic:** self-heal uses original method/args instead of new element's

the comment says "override previously provided method and arguments" but the code does the opposite - it uses the original `observe.method` and `observe.arguments` instead of using the new `element.method` and `element.arguments` from the fresh observation

```suggestion
        await this._performPlaywrightMethod(
          element.method,
          element.arguments,
          fallbackSelector,
          domSettleTimeoutMs,
        );
```

How can I resolve this? If you propose a fix, please make it concise.

);
return {
success: true,
message: `Action [${element.method}] performed successfully on selector: ${element.selector}`,
action: observe.description || `ObserveResult action (${method})`,
playwrightCommand: attemptedPlaywrightCommand,
};
} catch (err) {
this.logger({
Expand All @@ -195,11 +234,38 @@ export class StagehandActHandler {
success: false,
message: `Failed to perform act: ${err.message}`,
action: observe.description || `ObserveResult action (${method})`,
playwrightCommand: attemptedPlaywrightCommand,
};
}
}
}

private normalizeSelector(selector?: string): string | undefined {
if (!selector) {
return undefined;
}

return selector.replace(/^xpath=/i, "").trim();
}

private buildPlaywrightCommand(
method: string | undefined,
args: unknown[] | undefined,
selector: string | undefined,
): ActResult["playwrightCommand"] {
if (!method || !selector) {
return undefined;
}

const commandArguments = Array.isArray(args) ? args : [];

return {
method,
selector,
arguments: commandArguments,
};
}

/**
* Perform an act based on an instruction.
* This method will observe the page and then perform the act on the first element returned.
Expand Down
7 changes: 7 additions & 0 deletions types/stagehand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,17 @@ export interface ActOptions {
frameId?: string;
}

export interface PlaywrightCommandResult {
method: string;
selector: string;
arguments: unknown[];
}

export interface ActResult {
success: boolean;
message: string;
action: string;
playwrightCommand?: PlaywrightCommandResult;
}

export interface ExtractOptions<T extends z.AnyZodObject> {
Expand Down