Screenplay pattern implementation using Playwright for test automation
npm install @comatoastuk/screenplay-playwright


A comprehensive TypeScript implementation of the Screenplay pattern for test automation using Playwright. This framework provides a clean, maintainable, and readable approach to writing automated tests by modeling user interactions as a series of tasks and interactions.
โจ Features:
- ๐ญ Full Screenplay pattern implementation
- ๐ธ Built-in visual regression testing
- ๐ Multi-browser support (Chromium, Firefox, WebKit)
- ๐ง TypeScript with strict typing
- ๐ฆ Ready for CI/CD with automatic baseline management
- ๐ฏ Comprehensive test utilities and page objects
The Screenplay pattern is a user-centered approach to writing automated tests. It models the way that real users interact with software applications, focusing on:
- Actors - Users who interact with the system
- Abilities - What the actors can do (e.g., browse the web)
- Tasks - High-level business activities
- Interactions - Direct interactions with the system
- Questions - Queries about the current state of the system
``bashInstall from npm
npm install @comatoastuk/screenplay-playwright
$3
`bash
Run all tests
npm testRun tests in headed mode
npm run test:headedRun tests with debug mode
npm run test:debugOpen test UI
npm run test:uiView test report
npm run test:reportVisual regression testing
npm run test:visual # Run visual tests
npm run test:visual:update # Update visual baselines
npm run test:visual:headed # Debug visual tests
npm run test:visual:chromium # Run visual tests on Chromium only
`$3
`bash
Build TypeScript
npm run buildBuild and watch for changes
npm run build:watchClean build artifacts
npm run clean
`๐ Basic Usage
$3
`typescript
import { Actor, BrowseTheWeb, Cast } from 'screenplay-playwright';// Create an actor with the ability to browse the web
const actor = Cast.theActorNamed('TestUser').whoCan(
BrowseTheWeb.using(page, context)
);
`$3
`typescript
import { NavigateTo, Click, Type, Wait } from 'screenplay-playwright';// Basic interactions
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
Click.on('#login-button'),
Type.theText('username').into('#username-field'),
Wait.forElementToBeVisible('.dashboard')
);
`$3
`typescript
import { Text, Visibility, PageTitle, CurrentUrl } from 'screenplay-playwright';// Query the current state
const title = await actor.asks(PageTitle.current());
const isVisible = await actor.asks(Visibility.of('#element'));
const text = await actor.asks(Text.of('.message'));
const url = await actor.asks(CurrentUrl.value());
`$3
`typescript
import { Login, Search } from 'screenplay-playwright';// High-level business tasks
await actor.attemptsTo(
Login.withCredentials('testuser', 'password').at('https://example.com/login'),
Search.for('playwright').using({
input: '#search-input',
submit: '#search-button',
})
);
`$3
`typescript
import {
TakeScreenshot,
CompareScreenshot,
VisualTesting,
VisualTestConfig,
} from 'screenplay-playwright';// Configure visual testing
VisualTestConfig.configure({
threshold: 0.2,
defaultOptions: {
fullPage: true,
animations: 'disabled',
},
});
// Take screenshots
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
TakeScreenshot.named('homepage')
);
// Compare with baselines
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
CompareScreenshot.named('homepage').withThreshold(0.1)
);
// Wait for visual stability before capturing
await VisualTesting.waitForVisualStability(actor);
// Complete visual testing workflow
await VisualTesting.performVisualTest(
actor,
'homepage-test',
undefined, // Full page
0.2 // Threshold
);
`๐๏ธ Framework Structure
`
src/
โโโ screenplay/
โ โโโ interfaces.ts # Core interfaces
โโโ actors/
โ โโโ Actor.ts # Actor implementation
โโโ abilities/
โ โโโ BrowseTheWeb.ts # Web browsing ability
โโโ interactions/
โ โโโ NavigateTo.ts # Navigate to URL
โ โโโ Click.ts # Click elements
โ โโโ Type.ts # Type text
โ โโโ Wait.ts # Wait operations
โ โโโ Select.ts # Select dropdown options
โ โโโ Scroll.ts # Scroll operations
โ โโโ Hover.ts # Hover over elements
โ โโโ visual/ # Visual regression testing
โ โโโ TakeScreenshot.ts # Capture screenshots
โ โโโ CompareScreenshot.ts # Compare with baselines
โโโ questions/
โ โโโ Text.ts # Get element text
โ โโโ Visibility.ts # Check visibility
โ โโโ PageTitle.ts # Get page title
โ โโโ CurrentUrl.ts # Get current URL
โ โโโ Attribute.ts # Get attributes
โ โโโ Count.ts # Count elements
โ โโโ Value.ts # Get input values
โ โโโ visual/ # Visual testing questions
โ โโโ VisualMatch.ts # Visual comparison results
โโโ tasks/
โ โโโ Login.ts # Login task
โ โโโ Search.ts # Search task
โโโ utils/
โ โโโ Cast.ts # Actor management
โ โโโ visual/ # Visual testing utilities
โ โโโ VisualTestConfig.ts # Configuration
โ โโโ VisualTesting.ts # Testing workflows
โโโ models/
โ โโโ PageObjects.ts # Page object models
โโโ index.ts # Main exports
`๐ Core Concepts
$3
Actors represent users of your system. They have abilities and can perform activities:
`typescript
const alice = Cast.theActorNamed('Alice').whoCan(
BrowseTheWeb.using(page, context)
);// Actors can remember information
alice.remember('userRole', 'admin');
const role = alice.recall('userRole');
`$3
Abilities define what actors can do. The main ability is
BrowseTheWeb:`typescript
const browseTheWeb = BrowseTheWeb.using(page, context);
actor.whoCan(browseTheWeb);
`$3
Direct interactions with the system:
`typescript
// Available interactions
Click.on('#button');
Type.theText('text').into('#input');
Select.option('value').from('#dropdown');
Hover.over('#element');
Scroll.toElement('#target');
Wait.forElementToBeVisible('#element');
NavigateTo.url('https://example.com');// Visual regression testing interactions
TakeScreenshot.named('screenshot-name');
TakeScreenshot.ofElement('#specific-element');
CompareScreenshot.named('baseline-name');
CompareScreenshot.ofElement('#element').withThreshold(0.1);
`$3
Query the current state of the system:
`typescript
// Available questions
Text.of('#element');
Visibility.of('#element');
Value.of('#input');
Count.of('.items');
Attribute.of('#element').named('class');
PageTitle.current();
CurrentUrl.value();// Visual testing questions
VisualMatch.between('screenshot1.png', 'screenshot2.png');
VisualMatch.forBaseline('baseline-name').withThreshold(0.1);
`$3
High-level business activities composed of multiple interactions:
`typescript
// Pre-built tasks
Login.withCredentials('user', 'pass').at('/login');
Search.for('query').using({ input: '#search', submit: '#go' });// Custom tasks
class CompleteProfile implements Task {
async performAs(actor: Actor): Promise {
await actor.attemptsTo(
Click.on('#profile-link'),
Type.theText('John Doe').into('#full-name'),
Select.option('Developer').from('#role'),
Click.on('#save-button')
);
}
toString(): string {
return 'complete the user profile';
}
}
`๐ฏ Advanced Usage
$3
The framework includes comprehensive visual regression testing capabilities:
`typescript
import {
VisualTestConfig,
VisualTesting,
TakeScreenshot,
CompareScreenshot,
VisualMatch,
} from 'screenplay-playwright';// Configure visual testing globally
VisualTestConfig.configure({
threshold: 0.2,
defaultOptions: {
fullPage: true,
animations: 'disabled',
caret: 'hide',
},
});
// Basic screenshot workflow
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
VisualTesting.waitForVisualStability(actor), // Wait for animations to complete
TakeScreenshot.named('homepage-baseline')
);
// Compare screenshots with custom threshold
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
CompareScreenshot.named('homepage-baseline').withThreshold(0.1)
);
// Element-specific visual testing
await actor.attemptsTo(
CompareScreenshot.ofElement('#header').named('header-comparison')
);
// Complete visual testing workflow with utilities
await VisualTesting.performVisualTest(
actor,
'responsive-homepage',
'#main-content', // Specific element (optional)
0.15 // Custom threshold
);
// Ask questions about visual differences
const hasVisualChanges = await actor.asks(
VisualMatch.forBaseline('homepage').withThreshold(0.05)
);
`#### Visual Testing in CI/CD
The framework automatically handles baseline management in CI environments:
- First CI run: Creates baseline screenshots (expected "failures")
- Subsequent runs: Compares against established baselines
- Local development: Creates baselines as needed
- Global setup/teardown: Automatic baseline generation mode detection
`bash
Local development
npm run test:visual # Run with existing baselines
npm run test:visual:update # Update baselines after UI changesCI automatically handles baseline creation and comparison
`See Visual Testing CI Guide for detailed setup.
$3
Use the provided page object models for better organization:
`typescript
import { Form, Navigation, Table } from 'screenplay-playwright';const loginForm = Form.located('#login-form').withFields({
username: '#username',
password: '#password',
submit: '#login-btn',
});
const mainNav = Navigation.located('.main-nav').withLinks({
home: '#home-link',
profile: '#profile-link',
settings: '#settings-link',
});
const dataTable = Table.located('#data-table')
.withRowSelector('tbody tr')
.withCellSelector('td');
`$3
Create your own interactions:
`typescript
import { Interaction, Actor } from 'screenplay-playwright';class DoubleClick implements Interaction {
constructor(private selector: string) {}
static on(selector: string) {
return new DoubleClick(selector);
}
async performAs(actor: Actor): Promise {
const browseTheWeb = actor.abilityTo(BrowseTheWeb);
await browseTheWeb.page.dblclick(this.selector);
}
toString(): string {
return
double click on ${this.selector};
}
}
`$3
Create your own questions:
`typescript
import { Question, Actor } from 'screenplay-playwright';class CSSProperty implements Question {
constructor(
private selector: string,
private property: string
) {}
static of(selector: string, property: string) {
return new CSSProperty(selector, property);
}
async answeredBy(actor: Actor): Promise {
const browseTheWeb = actor.abilityTo(BrowseTheWeb);
const element = browseTheWeb.page.locator(this.selector);
return await element.evaluate(
(el, prop) => getComputedStyle(el).getPropertyValue(prop),
this.property
);
}
toString(): string {
return
the ${this.property} CSS property of ${this.selector};
}
}
`๐งช Testing Examples
Comprehensive examples are available in the test files:
-
tests/example.spec.ts - Basic Screenplay pattern usage examples
- tests/visual-regression.spec.ts - Visual regression testing examples
- Framework setup and configuration examples
- Custom interactions and questions
- Page object model implementations
- CI/CD integration patterns$3
`typescript
import { test } from '@playwright/test';
import {
Actor,
BrowseTheWeb,
NavigateTo,
Click,
VisualTesting,
} from '@comatoastuk/screenplay-playwright';test.describe('Example Tests', () => {
let actor: Actor;
test.beforeEach(async ({ page, context }) => {
actor = new Actor('TestUser').whoCan(BrowseTheWeb.using(page, context));
});
test('should demonstrate basic interactions', async () => {
await actor.attemptsTo(
NavigateTo.url('https://example.com'),
Click.on('#button')
);
});
test('should perform visual regression testing', async () => {
await actor.attemptsTo(NavigateTo.url('https://example.com'));
await VisualTesting.performVisualTest(actor, 'homepage');
});
});
`๐ Available Scripts
$3
-
npm run build - Compile TypeScript to JavaScript
- npm run build:watch - Compile with watch mode
- npm test - Run Playwright tests
- npm run test:headed - Run tests in headed mode
- npm run test:debug - Run tests in debug mode
- npm run test:ui - Open Playwright UI
- npm run test:report - Show test report$3
-
npm run test:visual - Run visual regression tests
- npm run test:visual:update - Update baseline screenshots
- npm run test:visual:headed - Debug visual tests in headed mode
- npm run test:visual:chromium - Run visual tests on Chromium only$3
-
npm run lint - Run ESLint
- npm run lint:fix - Fix ESLint issues
- npm run format - Format code with Prettier
- npm run clean - Clean build artifacts
- npm run docs - Generate TypeScript documentation๐ CI/CD Workflows
This project includes comprehensive GitHub Actions workflows with automatic semantic versioning:
$3
- Linting - ESLint code quality checks
- Building - TypeScript compilation
- Testing - Multi-browser testing (Chromium, Firefox, WebKit)
- Security - CodeQL analysis and dependency review
- Package Analysis - Bundle size monitoring
$3
- Semantic Versioning - Automatic version bumps based on conventional commits
- Release Notes - Auto-generated changelogs with emoji categorization
- npm Publishing - Automatic publishing to npmjs.org
- GitHub Packages - Publishing to GitHub Package Registry
- GitHub Releases - Automated releases with assets and notes
$3
Use semantic commit messages for automatic versioning:
`bash
feat: add new interaction # โ Minor release (0.1.0)
fix: resolve selector issue # โ Patch release (0.0.1)
feat!: redesign Actor API # โ Major release (1.0.0)Interactive commit helper (recommended)
npm run commit
`This launches an interactive commit helper that guides you through creating properly formatted conventional commits, ensuring consistent formatting and automatic semantic versioning.
$3
1. Add
NPM_TOKEN` to repository secrets for publishingSee Contributing Guide for detailed development workflow.
See Commitizen Guide for interactive commit help.
See Semantic Versioning Guide for detailed commit message format.
See GitHub Actions Setup Guide for detailed configuration.
See Visual Testing Setup Guide for visual regression testing setup.
See Visual Testing CI Guide for CI/CD integration.
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Run the linter and tests
6. Submit a pull request
MIT
- Playwright Documentation
- TypeScript Documentation
- Visual Regression Testing Guide
- Visual Testing Setup
- Conventional Commits
- Semantic Versioning