Skip to content

Fluent API

Fluent API

Stepwright uses a fluent (chainable) API pattern that makes scripts readable and self-documenting.

Creating a Script

Every script starts with Stepwright.create():

import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('My Script Name');

The script name is used for logging and reporting.

Method Chaining

All configuration methods return this, allowing you to chain them:

const script = Stepwright.create('E-commerce Checkout')
.config({ headless: true })
.data({ cartId: 'abc123' })
.reporter(new JSONReporter())
.video(true)
.checkpoint('Add to Cart')
.step('Navigate', ...)
.step('Click Add', ...)
.endCheckpoint()
.checkpoint('Checkout')
.step('Go to cart', ...)
.step('Enter details', ...)
.step('Submit', ...)
.endCheckpoint();

Chain Order

The recommended order for method chaining is:

  1. Configuration - config(), data(), video(), reporter()
  2. Checkpoints and Steps - checkpoint(), step(), endCheckpoint()
  3. Execution - run()
Stepwright.create('Script')
// 1. Configuration
.config({ ... })
.data({ ... })
.reporter(myReporter)
// 2. Steps
.checkpoint('Phase 1')
.step('Step 1', ...)
.endCheckpoint()
// 3. Execute
.run();

Configuration Methods

.config(options)

Set execution options:

.config({
browser: 'chromium', // Browser type
headless: true, // Headless mode
defaultTimeout: 30000, // Default step timeout
stopOnFailure: true, // Stop on first failure
screenshotOnFailure: true, // Capture screenshots
domOnFailure: true, // Capture DOM
artifactDir: './artifacts', // Output directory
verbose: true, // Enable verbose logging
})

.data(initialData)

Set initial shared data:

.data({
username: 'testuser',
password: 'secret',
baseUrl: 'https://app.example.com',
})

.video(config)

Enable video recording:

// Simple enable
.video(true)
// With options
.video({
enabled: true,
dir: './videos',
size: { width: 1280, height: 720 },
})

.reporter(reporter)

Add a reporter for output:

import { ConsoleReporter, JSONReporter } from '@korvol/stepwright';
.reporter(new ConsoleReporter({ colors: true }))
.reporter(new JSONReporter({ outputPath: './results.json' }))

Step Definition Methods

.checkpoint(name, options?)

Start a new checkpoint:

.checkpoint('Login') // Simple
.checkpoint('Login', { // With options
required: true,
maxRetries: 3,
})

.step(name, fn, options?)

Define a step:

.step('Navigate', async (ctx) => {
await ctx.page.goto('https://example.com');
})
.step('Fill Form', async (ctx) => {
await ctx.page.fill('#email', ctx.data.email);
}, {
timeout: 10000,
retry: { times: 2 },
})

.endCheckpoint()

Close the current checkpoint:

.checkpoint('Phase 1')
.step('Step 1', ...)
.step('Step 2', ...)
.endCheckpoint() // Close Phase 1
.checkpoint('Phase 2')
.step('Step 3', ...)
.endCheckpoint() // Close Phase 2

Background Task Methods

.background(name, task)

Add a background task that monitors for conditions:

.background('Handle Auth Popup', {
trigger: { type: 'url', pattern: '**/login**' },
handler: async (ctx) => {
await ctx.page.fill('#token', ctx.data.token);
},
})

Execution Methods

.run(options?)

Execute the script:

// Run all steps
const result = await script.run();
// Run until a specific step
const result = await script.run({ untilStep: 'Submit Form' });
// Resume from a checkpoint
const result = await script.run({ fromCheckpoint: 'Checkout' });

Return Values

The run() method returns a StepwrightResult:

interface StepwrightResult<TData> {
success: boolean;
duration: number;
stepsCompleted: number;
totalSteps: number;
steps: StepResult[];
checkpoints?: CheckpointResult[];
data: TData;
failedStep?: {
name: string;
index: number;
checkpoint?: string;
error: Error;
artifacts: StepArtifacts;
};
video?: string;
}

Error Handling

Errors in steps are caught and reported:

const result = await script.run();
if (!result.success) {
console.error('Failed step:', result.failedStep?.name);
console.error('Error:', result.failedStep?.error.message);
console.error('Screenshot:', result.failedStep?.artifacts.screenshot);
}

Immutability

// Don't do this - both share the same instance
const base = Stepwright.create('Base');
const withLogin = base.checkpoint('Login').step(...);
const withoutLogin = base.checkpoint('Direct').step(...);
// Do this instead
function createScript(withLogin: boolean) {
const script = Stepwright.create('Script');
if (withLogin) {
script.checkpoint('Login').step(...).endCheckpoint();
}
return script;
}

Next Steps