Self-Healing
The step() method wraps your Playwright code with self-healing. When selectors break or elements change, the agent automatically recovers by finding alternative ways to accomplish your goal.
Why Self-Healing?
Web applications change frequently. Selectors that work today may break tomorrow due to:
- Updated class names or IDs
- DOM restructuring
- Dynamic content changes
- A/B testing variations
Traditional Playwright tests fail immediately when selectors break. With step(), the agent recovers automatically.
Basic Usage
const result = await agent.step(
page,
async () => {
await page.click("#submit-btn");
},
"Click the submit button",
);
if (result.success) {
console.log("Action completed");
} else {
console.log("Failed:", result.details);
}The three parameters are:
- page - Playwright page instance
- action - Your Playwright code to execute
- description - What you're trying to accomplish (used by the agent if recovery is needed)
How It Works
- Your Playwright code runs normally
- If it succeeds,
step()returns{ success: true } - If it throws (selector not found, timeout, etc.), the agent:
- Analyzes the current page state
- Uses the description to understand the goal
- Finds an alternative way to accomplish it
- Executes the alternative action
The description Parameter
The description is crucial for self-healing. It tells the agent what you're trying to accomplish, not how.
// Good descriptions - focus on intent
"Click the submit button";
"Enter email in the login form";
"Select the first product from search results";
// Bad descriptions - too implementation-focused
"Click element with id submit-btn";
"Find input and type text";Controlling Self-Healing with maxSteps
Use maxSteps to allow multi-step recovery for complex scenarios:
// Multi-step recovery (agent can take multiple actions to recover)
await agent.step(page, action, "Click button", { maxSteps: 5 });Global Self-Healing Strategy
Set a default strategy when creating the agent:
import { WebAgent, createAgentContext, VariableStore } from "@shiplightai/sdk-pro";
const agent = new WebAgent(
createAgentContext({
model: "claude-haiku-4-5",
variableStore: new VariableStore(),
}),
);'single'- Recover with a single AI action (default)'multi'- Multi-step recovery for complex scenarios
The maxSteps option in step() overrides the global strategy for that call.
Return Value
step() returns an AgentStepResult:
interface AgentStepResult {
success: boolean;
details?: string;
}Unlike assert(), step() does not throw on failure. Check result.success to handle failures.
Examples
Wrapping a Single Action
await agent.step(page, async () => await page.click("#submit-btn"), "Click the submit button");Wrapping Multiple Actions
await agent.step(
page,
async () => {
await page.fill("#email", "user@example.com");
await page.fill("#password", "secret");
await page.click("#login");
},
"Fill login form and submit",
);Handling Flaky Elements
// Element appears after animation/loading
await agent.step(
page,
async () => await page.click(".dynamic-button"),
"Click the dynamic button that appears after loading",
{ maxSteps: 5 },
);Migration from Playwright
Wrap existing Playwright code with step() for gradual adoption:
// Before: Pure Playwright (brittle)
await page.click("#old-submit-button");
// After: Self-healing wrapper
await agent.step(page, async () => await page.click("#old-submit-button"), "Click the submit button");This approach lets you:
- Keep existing selectors when they work
- Auto-recover when they break
- Migrate incrementally without rewriting tests