
Research
/Security News
TensorLake npm SDK Compromised in ChainDrop Shai-Hulud Credential-Stealing Attack
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.
playwright-state-model
Advanced tools
Model-Based Testing driver connecting XState with Playwright Page Objects
Model-Based Testing driver connecting XState state machines with Playwright Page Objects.
Playwright State Model bridges the gap between formal state machine specifications and end-to-end testing. Write maintainable, scalable tests by modeling your application's behavior with XState and validating it with Playwright.
npm install playwright-state-model
Peer Dependencies:
@playwright/test: ^1.30.0xstate: ^4.30.0 || ^5.0.0Note: The library automatically supports both XState v4 and v5. No code changes needed when upgrading XState versions.
import { createMachine } from "xstate";
export const appMachine = createMachine({
id: "app",
initial: "home",
states: {
home: {
id: "home",
on: {
NAVIGATE_TO_DASHBOARD: { target: "dashboard" },
},
},
dashboard: {
id: "dashboard",
on: {
NAVIGATE_TO_HOME: { target: "home" },
},
},
},
});
import { Page, expect } from "@playwright/test";
import { BaseState } from "playwright-state-model";
export class HomePage extends BaseState {
constructor(page: Page, context?: any) {
super(page, context);
}
async validateState(): Promise<void> {
await expect(this.page).toHaveURL("https://example.com");
await expect(this.page.locator("h1")).toBeVisible();
}
async NAVIGATE_TO_DASHBOARD(): Promise<void> {
await this.page.getByRole("link", { name: "Dashboard" }).click();
}
}
import { Page } from "@playwright/test";
import { StateFactory } from "playwright-state-model";
import { HomePage } from "./pages/HomePage";
import { DashboardPage } from "./pages/DashboardPage";
export function createStateFactory(page: Page): StateFactory {
const factory = new StateFactory(page);
factory.register("home", HomePage);
factory.register("dashboard", DashboardPage);
return factory;
}
Option A: Using convenience helper (recommended for reduced boilerplate)
import { test, expect } from "@playwright/test";
import { createExecutor } from "playwright-state-model";
import { appMachine } from "./machine";
import { HomePage } from "./pages/HomePage";
import { DashboardPage } from "./pages/DashboardPage";
test("navigate through states", async ({ page }) => {
const executor = createExecutor(page, appMachine, (factory) => {
factory.register("home", HomePage);
factory.register("dashboard", DashboardPage);
});
await page.goto("https://example.com");
await executor.expectState("home");
await executor.navigateAndValidate("NAVIGATE_TO_DASHBOARD");
await executor.expectState("dashboard");
});
Option B: Traditional setup (more explicit)
import { test, expect } from "@playwright/test";
import { ModelExecutor } from "playwright-state-model";
import { appMachine } from "./machine";
import { createStateFactory } from "./factory";
test("navigate through states", async ({ page }) => {
const factory = createStateFactory(page);
const executor = new ModelExecutor(page, appMachine, factory);
await page.goto("https://example.com");
await executor.validateCurrentState();
await executor.dispatch("NAVIGATE_TO_DASHBOARD");
expect(executor.currentStateValue).toBe("dashboard");
});
Full XState v4 and v5 Compatibility: The library automatically detects and supports both XState v4 (interpret) and v5 (createActor) APIs. No code changes needed when upgrading XState versions - the library handles version detection automatically.
Nested state mapping. Automatically resolves complex nested XState states to Page Object chains. Define hierarchical states once and validate entire UI compositions automatically.
// XState: { docs: { overview: {} } }
// Automatically resolves to: [DocsPage, DocsOverviewPage]
// Validates both parent and child states
Smart event dispatch. Events bubble from leaf states to root, ensuring the most specific handler executes first. Matches how modern web applications handle events.
// Event 'NAVIGATE_TO_HOME' bubbles from:
// GettingStartedPage → DocsPage → AppPage
// First handler found executes
Complete UI validation. Validates entire state hierarchy from root to leaf, ensuring parent components are validated before children. Guarantees consistent UI state.
XState context integration. Automatically injects XState context into Page Objects, enabling data-driven testing scenarios without manual state management.
Complete type inference. Full TypeScript support with proper type inference for state machines, Page Objects, and context data.
Use State Model (dispatch() / navigateAndValidate()) when:
Use Direct Navigation (page.goto() / pageObject.goto()) when:
Example: State transitions (recommended for navigation tests)
// ✅ Good: Uses state machine for navigation
await executor.navigateAndValidate("NAVIGATE_TO_DASHBOARD");
await executor.expectState("dashboard");
Example: Direct navigation (acceptable for single-page tests)
// ✅ Also fine: Direct navigation for simple page tests
await app.dashboard.goto();
await app.dashboard.waitForLoad();
Use createExecutor() helper to reduce setup code:
// Before: 3 lines
const factory = createStateFactory(page);
const executor = new ModelExecutor(page, appMachine, factory);
// After: 1 line
const executor = createExecutor(page, appMachine, (factory) => {
factory.register("home", HomePage);
factory.register("dashboard", DashboardPage);
});
Use navigateAndValidate() and expectState() for cleaner test code:
// Before: 2 lines
await executor.dispatch("NAVIGATE_TO_DASHBOARD");
await executor.validateCurrentState();
expect(executor.currentStateValue).toBe("dashboard");
// After: 1 line
await executor.navigateAndValidate("NAVIGATE_TO_DASHBOARD");
await executor.expectState("dashboard");
Use gotoState() for state-machine-aware navigation instead of direct goto() calls:
// Instead of direct navigation:
await app.dashboard.goto();
await executor.expectState("dashboard");
// Use state-driven navigation:
await executor.gotoState("dashboard");
await executor.expectState("dashboard");
Note: gotoState() navigates to the page but doesn't update the state machine. For state transitions, use navigateAndValidate() instead.
Use syncStateFromPage() to detect state mismatches when navigation happens outside the state machine:
// Direct URL change (bypasses state machine)
await page.goto("https://example.com/dashboard");
// Detect if state machine is out of sync
try {
await executor.syncStateFromPage();
await executor.expectState("dashboard");
} catch (error) {
// State machine needs updating - use navigateAndValidate() instead
await executor.navigateAndValidate("NAVIGATE_TO_DASHBOARD");
}
Test complex nested state machines with automatic resolution:
const machine = createMachine({
id: "app",
states: {
docs: {
id: "docs",
initial: "overview",
states: {
overview: { id: "docs.overview" },
gettingStarted: { id: "docs.gettingStarted" },
},
},
},
});
// Automatically resolves and validates:
// - docs state → DocsPage
// - docs.overview state → DocsOverviewPage
Use XState context for data-driven scenarios:
const machine = createMachine({
context: { userId: null },
// ... states
});
class UserDashboard extends BaseState<{ userId: string }> {
async validateState(): Promise<void> {
await expect(this.page.locator(`[data-user-id="${this.context.userId}"]`)).toBeVisible();
}
}
See the example/ directory for a complete working example testing playwright.dev. The example includes:
playwright-state-model includes AI agents to help you build, maintain, and debug model-based tests:
All agents are designed to ensure tests are parallelism-safe and race condition-free, automatically verifying tests pass with --repeat-each 10 --workers 5.
Initialize agent definitions in your project:
# For VS Code
npx playwright-state-model init-agents --loop=vscode
# For Claude Desktop
npx playwright-state-model init-agents --loop=claude
# For OpenCode
npx playwright-state-model init-agents --loop=opencode
This creates agent definitions in .vscode/agents/, .claude/agents/, or .opencode/agents/ depending on your chosen environment.
See the agents/ directory for agent definitions and documentation.
BaseState<TContext>Abstract base class for all Page Objects. Extend this class to create state-specific Page Objects.
Methods:
validateState(): Promise<void> - Must be implemented to assert the current page stateProperties:
context: TContext - Injected XState context dataprotected page: Page - Playwright Page instanceStateFactoryMaps XState state IDs to Page Object classes. Manages the registry of state-to-PageObject mappings.
Methods:
register(id: string, stateClass: StateConstructor): void - Register a state mappingget<T extends BaseState>(id: string, context: any): T - Create a Page Object instancegetRegisteredStates(): string[] - Returns array of all registered state IDsModelExecutorOrchestrates state machine execution and Page Object validation. The main entry point for model-based testing.
Methods:
validateCurrentState(): Promise<void> - Validates the entire state hierarchy with detailed error messagesdispatch(event: string, payload?: any): Promise<void> - Dispatches an event and validates the new statenavigateAndValidate(event: string, payload?: any): Promise<void> - Convenience method: dispatches event and validates stateexpectState(expectedState: any, options?: { strict?: boolean }): Promise<void> - Validates current state and asserts it matches expected valuegotoState(targetState: any): Promise<void> - Navigate directly to a target state through Page Object's goto() method (state-machine-aware navigation)syncStateFromPage(): Promise<void> - Detect current page state and verify state machine synchronizationdispose(): void - Cleans up resources (XState interpreter/actor)Properties:
currentStateValue - Returns the current XState valuecreateExecutorConvenience function to reduce boilerplate when creating ModelExecutor instances.
Function:
createExecutor(page: Page, machine: AnyStateMachine, factoryCreator: (factory: StateFactory) => void): ModelExecutor - Creates and configures a ModelExecutor in one callActionLocator<TNext>Smart locator that binds UI elements to actions and transitions. Useful for complex interactions with side effects.
Methods:
perform(action, ...args): Promise<TNext> - Executes action and handles side effectsget raw: Locator - Exposes the underlying Playwright LocatorWe welcome contributions! This project follows best practices for open source development.
Fork and clone the repository:
git clone https://github.com/gustavo-meilus/playwright-state-model.git
cd playwright-state-model
Install dependencies:
npm install
Build the project:
npm run build
Run tests (in the example directory):
cd example
npm install
npm test
Create a branch for your changes:
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-bug-fix
Make your changes following the existing code style:
Test your changes:
npm run buildcd example && npm testCommit your changes:
git add .
git commit -m "feat: add your feature description"
# or
git commit -m "fix: fix your bug description"
Use conventional commit messages:
feat: for new featuresfix: for bug fixesdocs: for documentation changesrefactor: for code refactoringtest: for test additions/changeschore: for maintenance tasksPush your branch:
git push origin feature/your-feature-name
Create a Pull Request on GitHub:
async/await over promisesFound a bug or have a feature request? Please open an issue with:
MIT License - see LICENSE file for details.
FAQs
Model-Based Testing driver connecting XState with Playwright Page Objects
We found that playwright-state-model demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.

Research
/Security News
Socket found 16 malicious Firefox extensions designed to steal crypto wallet recovery phrases and private keys using cloned Rabby and OKX interfaces.

Product
Socket now scans VS Code extensions, giving teams early detection of risky behaviors, hidden capabilities, and supply chain threats in developer tools.