@comatoastuk/screenplay-playwright
v1.4.0
Published
Screenplay pattern implementation using Playwright for test automation
Maintainers
Readme
Screenplay Pattern with 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
🎭 What is the Screenplay Pattern?
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
🚀 Quick Start
Installation
# Install from npm
npm install @comatoastuk/screenplay-playwright
# Or install from GitHub Packages
npm install @comatoastuk/screenplay-playwright --registry=https://npm.pkg.github.comRunning Tests
# Run all tests
npm test
# Run tests in headed mode
npm run test:headed
# Run tests with debug mode
npm run test:debug
# Open test UI
npm run test:ui
# View test report
npm run test:report
# Visual 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 onlyBuilding the Project
# Build TypeScript
npm run build
# Build and watch for changes
npm run build:watch
# Clean build artifacts
npm run clean📝 Basic Usage
Creating an Actor
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)
);Performing Interactions
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')
);Asking Questions
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());Performing Tasks
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',
})
);Visual Regression Testing
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
Actors
Actors represent users of your system. They have abilities and can perform activities:
const alice = Cast.theActorNamed('Alice').whoCan(
BrowseTheWeb.using(page, context)
);
// Actors can remember information
alice.remember('userRole', 'admin');
const role = alice.recall('userRole');Abilities
Abilities define what actors can do. The main ability is BrowseTheWeb:
const browseTheWeb = BrowseTheWeb.using(page, context);
actor.whoCan(browseTheWeb);Interactions
Direct interactions with the system:
// 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);Questions
Query the current state of the system:
// 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);Tasks
High-level business activities composed of multiple interactions:
// 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<void> {
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
Visual Regression Testing
The framework includes comprehensive visual regression testing capabilities:
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
# Local development
npm run test:visual # Run with existing baselines
npm run test:visual:update # Update baselines after UI changes
# CI automatically handles baseline creation and comparisonSee Visual Testing CI Guide for detailed setup.
Page Object Models
Use the provided page object models for better organization:
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');Custom Interactions
Create your own interactions:
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<void> {
const browseTheWeb = actor.abilityTo(BrowseTheWeb);
await browseTheWeb.page.dblclick(this.selector);
}
toString(): string {
return `double click on ${this.selector}`;
}
}Custom Questions
Create your own questions:
import { Question, Actor } from 'screenplay-playwright';
class CSSProperty implements Question<string> {
constructor(
private selector: string,
private property: string
) {}
static of(selector: string, property: string) {
return new CSSProperty(selector, property);
}
async answeredBy(actor: Actor): Promise<string> {
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 examplestests/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
Example Test Structure
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
Core Scripts
npm run build- Compile TypeScript to JavaScriptnpm run build:watch- Compile with watch modenpm test- Run Playwright testsnpm run test:headed- Run tests in headed modenpm run test:debug- Run tests in debug modenpm run test:ui- Open Playwright UInpm run test:report- Show test report
Visual Testing Scripts
npm run test:visual- Run visual regression testsnpm run test:visual:update- Update baseline screenshotsnpm run test:visual:headed- Debug visual tests in headed modenpm run test:visual:chromium- Run visual tests on Chromium only
Development Scripts
npm run lint- Run ESLintnpm run lint:fix- Fix ESLint issuesnpm run format- Format code with Prettiernpm run clean- Clean build artifactsnpm run docs- Generate TypeScript documentation
🚀 CI/CD Workflows
This project includes comprehensive GitHub Actions workflows with automatic semantic versioning:
Continuous Integration
- 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
Automated Release & Versioning
- 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
Conventional Commits
Use semantic commit messages for automatic versioning:
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 commitThis launches an interactive commit helper that guides you through creating properly formatted conventional commits, ensuring consistent formatting and automatic semantic versioning.
Setup Requirements
- Add
NPM_TOKENto repository secrets for publishing - Enable GitHub Pages for documentation deployment
- Use conventional commit messages for automatic versioning
See 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.
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the linter and tests
- Submit a pull request
📄 License
MIT
