Skip to content

feat: emit action events for real-time Playwright step visibility#1687

Open
mainnebula wants to merge 2 commits intobrowserbase:mainfrom
mainnebula:steward/186-make-playwright-steps-visible
Open

feat: emit action events for real-time Playwright step visibility#1687
mainnebula wants to merge 2 commits intobrowserbase:mainfrom
mainnebula:steward/186-make-playwright-steps-visible

Conversation

@mainnebula
Copy link

@mainnebula mainnebula commented Feb 14, 2026

Summary

Closes #186

  • Adds an ActionEvent interface and emits "action" events on the existing bus EventEmitter whenever a Playwright action starts, completes, or errors
  • Adds on() / off() convenience methods to the Stagehand class so users can subscribe without accessing bus directly
  • Covers both the primary execution path and the self-heal retry path in ActHandler
  • Agent actions flow through automatically since agent() calls v3.act() internally

Usage

const stagehand = new Stagehand({ env: "LOCAL" });
await stagehand.init();

stagehand.on("action", (event) => {
  console.log(`[${event.phase}] ${event.method}${event.description}`);
  // [start] click — click the login button
  // [complete] click — click the login button
});

await stagehand.act("click the login button");

ActionEvent shape

interface ActionEvent {
  phase: "start" | "complete" | "error";
  method: string;       // "click", "fill", "type", etc.
  selector: string;     // XPath selector
  description: string;  // Human-readable action description
  arguments?: string[];
  url?: string;
  timestamp: number;
  error?: string;       // Only present on phase: "error"
}

Test plan

  • All 373 existing unit tests pass
  • Build succeeds across all packages
  • ActionEvent type exported in declaration file
  • on()/off() methods available on Stagehand.prototype
  • Manual verification: subscribe to "action" events and run act() to confirm events fire

This issue was highlighted as important by Token Steward


Summary by cubic

Adds real-time “action” events for Playwright steps and simple Stagehand.on/off helpers so users can observe start, complete, and error as they happen. Addresses steward/186 by making steps visible during execution, including self-heal retries. Includes a changeset for a minor release.

  • New Features
    • Emit "action" events from ActHandler on start, complete, and error; covers self-heal retries.
    • Add ActionEvent type (phase, method, selector, description, arguments, url, timestamp, error).
    • Add Stagehand.on()/off() as convenience wrappers over the internal bus.
    • Wire bus into ActHandler; events fire for both act() and agent() flows.
    • Add changeset to publish this feature as a minor release.

Written for commit 63533a4. Summary will update on new commits. Review in cubic

…rowserbase#186)

Add an event system that lets users observe Playwright actions as they
execute, rather than only seeing results after completion.

- Add ActionEvent interface (phase, method, selector, description, etc.)
- Emit "action" events on the existing bus from ActHandler
- Add on()/off() convenience methods to Stagehand class
- Cover both primary execution and self-heal retry paths
@changeset-bot
Copy link

changeset-bot bot commented Feb 14, 2026

🦋 Changeset detected

Latest commit: 63533a4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Minor
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 14, 2026

Greptile Overview

Greptile Summary

Adds real-time action event visibility by emitting "action" events throughout the Playwright action lifecycle. Users can now subscribe to action events via stagehand.on("action", listener) to receive notifications when actions start, complete, or error.

  • Added ActionEvent interface with phase tracking (start/complete/error)
  • Emits events in both primary execution and self-heal retry paths
  • Convenience on()/off() methods added to Stagehand class
  • Events include method, selector, description, arguments, and timestamp

Issues found:

  • The url field in ActionEvent interface is never populated by the implementation

Confidence Score: 4/5

  • Safe to merge with one minor issue to address
  • The implementation correctly emits events at all critical points (start, complete, error, and retry paths). Event structure is well-defined and TypeScript types are properly exported. The only issue is the unused url field in the interface which should either be populated or removed
  • packages/core/lib/v3/types/public/methods.ts requires attention for the unused url field

Important Files Changed

Filename Overview
packages/core/lib/v3/handlers/actHandler.ts Adds event emission for action lifecycle (start/complete/error) with proper error handling and retry support
packages/core/lib/v3/types/public/methods.ts Defines ActionEvent interface with proper typing for action lifecycle events
packages/core/lib/v3/v3.ts Adds on()/off() convenience methods and passes bus to ActHandler constructor

Sequence Diagram

sequenceDiagram
    participant User
    participant Stagehand
    participant ActHandler
    participant EventBus
    participant PerformMethod
    
    User->>Stagehand: on("action", listener)
    Stagehand->>EventBus: register listener
    
    User->>Stagehand: act(instruction)
    Stagehand->>ActHandler: act(params)
    
    ActHandler->>EventBus: emit("action", {phase: "start"})
    EventBus->>User: notify listener (start)
    
    ActHandler->>PerformMethod: performUnderstudyMethod()
    
    alt Success
        PerformMethod-->>ActHandler: success
        ActHandler->>EventBus: emit("action", {phase: "complete"})
        EventBus->>User: notify listener (complete)
    else Error (with selfHeal)
        PerformMethod-->>ActHandler: error
        ActHandler->>EventBus: emit("action", {phase: "error"})
        EventBus->>User: notify listener (error)
        ActHandler->>ActHandler: self-heal retry
        ActHandler->>EventBus: emit("action", {phase: "start"})
        ActHandler->>PerformMethod: performUnderstudyMethod(newSelector)
        PerformMethod-->>ActHandler: success
        ActHandler->>EventBus: emit("action", {phase: "complete"})
        EventBus->>User: notify listener (complete)
    end
    
    ActHandler-->>Stagehand: ActResult
    Stagehand-->>User: ActResult
Loading

Last reviewed commit: 95fc7d6

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

selector: string;
description: string;
arguments?: string[];
url?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

url field is defined in the interface but never populated in the implementation

Copy link
Contributor

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

Choose a reason for hiding this comment

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

3 issues found across 3 files

Confidence score: 3/5

  • There is concrete user-facing risk: action events are only emitted for local executions, so the new on("action") API is silently non-functional in API mode (packages/core/lib/v3/v3.ts).
  • Self-heal retry failures/timeouts can return/throw without emitting a terminal error action event, leaving subscribers with a started action and no terminal state (packages/core/lib/v3/handlers/actHandler.ts).
  • Overall risk is moderate due to medium-severity eventing gaps that can break consumers’ expectations about action lifecycle notifications.
  • Pay close attention to packages/core/lib/v3/handlers/actHandler.ts, packages/core/lib/v3/v3.ts, packages/core/lib/v3/types/public/methods.ts - action event emission and schema consistency.
Prompt for AI agents (all 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/core/lib/v3/handlers/actHandler.ts">

<violation number="1" location="packages/core/lib/v3/handlers/actHandler.ts:349">
P2: Self-heal retry failures and timeouts return/throw without emitting an `error` action event, leaving a started action without a terminal event for subscribers.</violation>
</file>

<file name="packages/core/lib/v3/v3.ts">

<violation number="1" location="packages/core/lib/v3/v3.ts:696">
P2: Action events are only emitted for local executions; API/BROWSERBASE `act()` calls bypass ActHandler and StagehandAPIClient has no event bus. This makes the new `on("action")` API silently non-functional in API mode, causing inconsistent behavior across environments.</violation>
</file>

<file name="packages/core/lib/v3/types/public/methods.ts">

<violation number="1" location="packages/core/lib/v3/types/public/methods.ts:39">
P3: The `url` field is defined in the `ActionEvent` interface but is never populated in the implementation. Either remove this unused field, or populate it with `page.url()` when emitting action events to provide the promised functionality.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User Code
    participant SH as Stagehand (V3)
    participant Bus as internal: bus (EventEmitter)
    participant AH as ActHandler
    participant PW as Playwright / Browser

    Note over User, Bus: Setup Phase
    User->>SH: NEW: on("action", callback)
    SH->>Bus: Register listener

    Note over User, PW: Execution Phase
    User->>SH: act(description)
    SH->>AH: execute action logic

    AH->>Bus: NEW: emit "action" (phase: "start")
    Bus-->>User: Trigger callback with ActionEvent

    AH->>PW: performUnderstudyMethod()
    
    alt Action Success
        PW-->>AH: success
        AH->>Bus: NEW: emit "action" (phase: "complete")
        Bus-->>User: Trigger callback
    else Action Failure
        PW-->>AH: error (e.g. selector not found)
        AH->>Bus: NEW: emit "action" (phase: "error", error: msg)
        Bus-->>User: Trigger callback

        opt Self-Heal Enabled
            AH->>AH: Request fallback selector via LLM
            
            Note over AH, Bus: CHANGED: Retry flow also emits events
            AH->>Bus: NEW: emit "action" (phase: "start") [retry]
            AH->>PW: performUnderstudyMethod() with new selector
            
            alt Retry Success
                PW-->>AH: success
                AH->>Bus: NEW: emit "action" (phase: "complete")
            else Retry Failure
                PW-->>AH: error
                Note right of AH: Final failure returned to Stagehand
            end
        end
    end

    AH-->>SH: Return ActResult
    SH-->>User: Result (success/failure)
Loading

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -1,10 +1,16 @@
// lib/v3/handlers/actHandler.ts
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: Self-heal retry failures and timeouts return/throw without emitting an error action event, leaving a started action without a terminal event for subscribers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/handlers/actHandler.ts, line 349:

<comment>Self-heal retry failures and timeouts return/throw without emitting an `error` action event, leaving a started action without a terminal event for subscribers.</comment>

<file context>
@@ -312,14 +338,19 @@ export class ActHandler {
         success: true,
         message: `Action [${method}] performed successfully on selector: ${action.selector}`,
-        actionDescription: action.description || `action (${method})`,
+        actionDescription,
         actions: [
           {
</file context>
Fix with Cubic

inferenceTimeMs,
),
this.domSettleTimeoutMs,
this.bus,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: Action events are only emitted for local executions; API/BROWSERBASE act() calls bypass ActHandler and StagehandAPIClient has no event bus. This makes the new on("action") API silently non-functional in API mode, causing inconsistent behavior across environments.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/v3.ts, line 696:

<comment>Action events are only emitted for local executions; API/BROWSERBASE `act()` calls bypass ActHandler and StagehandAPIClient has no event bus. This makes the new `on("action")` API silently non-functional in API mode, causing inconsistent behavior across environments.</comment>

<file context>
@@ -663,6 +693,7 @@ export class V3 {
               inferenceTimeMs,
             ),
           this.domSettleTimeoutMs,
+          this.bus,
         );
         this.extractHandler = new ExtractHandler(
</file context>
Fix with Cubic

selector: string;
description: string;
arguments?: string[];
url?: string;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P3: The url field is defined in the ActionEvent interface but is never populated in the implementation. Either remove this unused field, or populate it with page.url() when emitting action events to provide the promised functionality.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/types/public/methods.ts, line 39:

<comment>The `url` field is defined in the `ActionEvent` interface but is never populated in the implementation. Either remove this unused field, or populate it with `page.url()` when emitting action events to provide the promised functionality.</comment>

<file context>
@@ -30,6 +30,17 @@ export interface Action {
+  selector: string;
+  description: string;
+  arguments?: string[];
+  url?: string;
+  timestamp: number;
+  error?: string;
</file context>
Fix with Cubic

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.

Make executed Playwright steps visible to the user

1 participant