npm package: @korvol/stepwright
Description: Step-based framework for writing resilient Playwright scripts with rich failure context
Latest version: 0.1.3

# @korvol/stepwright

A step-based framework for writing resilient Playwright scripts with rich failure context, video recording, and trace capture.

## Features

- **Fluent API** - Chain steps, checkpoints, and configuration
- **Video & Trace Recording** - Capture video and Playwright traces with configurable modes
- **Rich Failure Context** - Automatic capture of screenshots, DOM, and logs on failure
- **Checkpoints** - Group steps and define retry strategies
- **Background Tasks** - Handle modals, auth redirects, and dynamic content
- **Reporters** - Console and JSON reporters included, extensible architecture
- **Failure Cases** - Generate structured failure data for AI-powered fixing with Fixwright

## Installation

```bash
npm install @korvol/stepwright playwright
# or
pnpm add @korvol/stepwright playwright
```

## Quick Start

```typescript
import { Stepwright, ConsoleReporter } from '@korvol/stepwright';

interface MyData {
  username: string;
  loggedIn: boolean;
  [key: string]: unknown;
}

const script = Stepwright.create<MyData>('Login Flow')
  .config({
    headless: false,
    defaultTimeout: 30000,
    screenshotOnFailure: true,
  })
  .video({ mode: 'on-failure' })
  .trace({ mode: 'on-failure' })
  .reporter(new ConsoleReporter())
  .data({
    username: 'user@example.com',
    loggedIn: false,
  })

  .checkpoint('Authentication', { required: true })

  .step('Navigate to login', async (ctx) => {
    await ctx.page.goto('https://example.com/login');
    ctx.log('Navigated to login page');
  })

  .step('Enter credentials', async (ctx) => {
    await ctx.page.fill('#username', ctx.data.username);
    await ctx.page.fill('#password', 'password');
  })

  .step('Submit form', async (ctx) => {
    await ctx.page.click('button[type="submit"]');
    await ctx.page.waitForURL('**/dashboard');
    ctx.data.loggedIn = true;
  })

  .endCheckpoint();

const result = await script.run();
console.log(result.success ? 'Passed!' : `Failed at: ${result.failedStep?.name}`);
```

## Configuration

### Basic Options

```typescript
Stepwright.create('My Script')
  .config({
    // Browser
    browser: 'chromium',           // 'chromium' | 'firefox' | 'webkit'
    headless: true,
    launchOptions: {},             // Playwright LaunchOptions
    contextOptions: {},            // Playwright BrowserContextOptions

    // Timeouts
    defaultTimeout: 30000,         // ms
    stopOnFailure: true,

    // Failure Artifacts
    screenshotOnFailure: true,
    domOnFailure: true,

    // Output
    runDir: '.stepwright/runs',    // Base directory for run artifacts
    artifactDir: '.stepwright/artifacts',
    verbose: false,
  })
```

### Video Recording

```typescript
.video({
  mode: 'on-failure',              // 'off' | 'on' | 'on-failure' | 'retain-on-failure'
  size: { width: 1280, height: 720 },
  dir: './custom-videos',          // Optional: override output directory
})
```

### Trace Recording

```typescript
.trace({
  mode: 'on-failure',              // 'off' | 'on' | 'on-failure' | 'retain-on-failure'
  screenshots: true,
  snapshots: true,
  sources: true,
  dir: './custom-traces',          // Optional: override output directory
})
```

### Reporters

```typescript
import { ConsoleReporter, JSONReporter } from '@korvol/stepwright';

.reporter(new ConsoleReporter({
  timestamps: true,
  durations: true,
  colors: true,
}))

.reporter(new JSONReporter({
  outputPath: './results/output.json',  // Optional: defaults to run directory
  pretty: true,
}))
```

## API Reference

### Stepwright

#### `Stepwright.create<T>(name: string)`
Create a new Stepwright instance with typed data.

#### `.config(options: StepwrightConfig)`
Configure the instance. Returns `this` for chaining.

#### `.data(initialData: T)`
Set initial shared data. Returns `this` for chaining.

#### `.video(config: VideoConfig | VideoMode)`
Configure video recording. Returns `this` for chaining.

#### `.trace(config: TraceConfig | TraceMode)`
Configure trace recording. Returns `this` for chaining.

#### `.reporter(reporter: Reporter)`
Add a reporter. Returns `this` for chaining.

#### `.checkpoint(name: string, options?: CheckpointOptions)`
Start a checkpoint group. Returns `this` for chaining.

#### `.endCheckpoint()`
End the current checkpoint. Returns `this` for chaining.

#### `.step(name: string, fn: StepFunction, options?: StepOptions)`
Add a step. Returns `this` for chaining.

```typescript
.step('My Step', async (ctx) => {
  // ctx.page - Playwright Page
  // ctx.browser - Playwright Browser
  // ctx.browserContext - Playwright BrowserContext
  // ctx.data - Typed shared data
  // ctx.log(message) - Log a message
  // ctx.screenshot(name) - Take a screenshot
}, {
  timeout: 60000,
  critical: true,
  retry: { times: 3, delay: 1000, backoff: 'exponential' },
})
```

#### `.background(name: string, task: BackgroundTaskDefinition)`
Add a background task for handling dynamic content.

#### `.run(options?: RunOptions)`
Execute the script. Returns `Promise<StepwrightResult<T>>`.

### StepwrightResult

```typescript
interface StepwrightResult<T> {
  success: boolean;
  duration: number;
  stepsCompleted: number;
  totalSteps: number;
  steps: StepResult[];
  checkpoints?: CheckpointResult[];
  data: T;

  // On failure
  failedStep?: {
    name: string;
    index: number;
    checkpoint?: string;
    error: Error;
    artifacts: StepArtifacts;
  };

  // Artifact paths
  runDirectory?: string;
  trace?: string;
  video?: string;
  log?: string;
}
```

### Context (ctx)

Available in step functions:

| Property | Type | Description |
|----------|------|-------------|
| `page` | `Page` | Playwright Page |
| `browser` | `Browser` | Playwright Browser |
| `browserContext` | `BrowserContext` | Playwright BrowserContext |
| `data` | `T` | Typed shared data between steps |
| `config` | `StepwrightConfig` | Current configuration |
| `step` | `CurrentStepInfo` | Current step info |
| `results` | `StepResult[]` | Results of completed steps |
| `log(msg)` | `Function` | Log a message |
| `screenshot(name?)` | `Function` | Take a screenshot |
| `getDOM(selector?)` | `Function` | Get DOM content |

## Generating Failure Cases

When a script fails, you can generate a structured failure case for AI-powered fixing with Fixwright:

```typescript
import { Stepwright, generateFailureCase } from '@korvol/stepwright';

const result = await script.run();

if (!result.success && result.failedStep) {
  const failureCase = generateFailureCase(result, {
    scriptInfo: {
      name: 'My Script',
      path: 'scripts/my-script.ts',
      repository: 'my-org/my-repo',
      baseBranch: 'main',
      commit: 'abc123',
    },
    artifactPaths: {
      screenshot: result.failedStep.artifacts.screenshot,
      dom: result.failedStep.artifacts.dom,
    },
  });

  // Save or send to Fixwright
  await fs.writeFile(`failures/${failureCase.id}.json`, JSON.stringify(failureCase));
}
```

## Run Directory Structure

When video/trace recording is enabled, artifacts are saved to:

```
.stepwright/
└── runs/
    └── script-name-20240102-123456/
        ├── trace/           # Playwright trace (unzipped)
        ├── screenshots/     # Manual screenshots
        ├── video.webm       # Video recording
        ├── stepwright.log   # ctx.log messages
        └── result.json      # JSON reporter output
```

## Background Tasks

Handle dynamic content like modals and auth redirects:

```typescript
.background('cookie-consent', {
  trigger: (event) => event.type === 'dom' && event.mutation === 'added',
  handler: async (event, ctx, queue) => {
    const button = ctx.page.locator('[data-testid="accept-cookies"]');
    if (await button.isVisible({ timeout: 500 })) {
      await button.click();
      ctx.log('Dismissed cookie consent');
    }
  },
  debounce: 1000,
})
```

## Integration with Fixwright

Stepwright produces failure cases that Fixwright can automatically fix:

```
Stepwright (runner)          Fixwright (fixer)
       │                            │
       │  ──── FailureCase ────▶    │
       │                            │
   [runs scripts]              [analyzes failures]
   [captures errors]           [generates fixes]
```

See [@korvol/fixwright](../fixwright) for AI-powered automatic fixing.

## License

MIT