How to Use the Screenplay Pattern for Test Automation
Learn how to structure test automation around actors, abilities, tasks, interactions, and questions, with a runnable Serenity/JS example and practical adoption guidance.
The Screenplay Pattern structures an automated test around an actor pursuing a goal. Give the actor the abilities needed to use the system, express meaningful work as tasks, keep direct operations in interactions, and verify results with questions and assertions. Use the pattern when those layers make scenarios easier to understand or reuse; a simple test may not need all of them.
This guide uses Serenity/JS with Playwright Test for runnable JavaScript. Screenplay is a design pattern, not a test runner or a requirement to use Cucumber. Serenity BDD provides a Java implementation; Serenity/JS provides JavaScript APIs and documents integration with Playwright Test. Pick the implementation that fits your language and existing runner.
1. Understand the five building blocks
| Building block | Purpose | Example |
|---|---|---|
| Actor | Represents a user or another participant pursuing a goal. | A customer searching for a product. |
| Ability | Gives an actor access to an interface or integration. | Using a browser, API client, or database connection. |
| Interaction | Performs a low-level operation against the system. | Clicking a button or entering text. |
| Task | Names meaningful work and coordinates interactions or other tasks. | Searching for a product or submitting an order. |
| Question | Retrieves information from the system or execution environment. | Reading a page heading or checking whether an item is visible. |
These terms describe responsibilities, not mandatory class counts. A task can contain several interactions; a question can expose the state a test needs to assert. Serenity/JS describes the pattern as a stage performance metaphor in which actors perform activities while interacting with the system. See the official Serenity/JS Screenplay Pattern guide and Serenity BDD fundamentals.
2. Start with the goal and observable outcome
Before writing selectors, state what the participant is trying to accomplish and what visible or queryable result would prove success. For example:
- Goal: A shopper finds a product and adds it to the cart.
- Observable result: The cart contains that product.
- Actor: A shopper.
- Ability: Browser interaction.
- Task: Find and add the product.
- Question: Which product names are in the cart?
This keeps the test narrative about behavior rather than a list of clicks. Use additional actors when the scenario genuinely involves distinct roles, such as an administrator approving a request created by a customer.
3. Set up Serenity/JS with Playwright Test
Serenity/JS supports integration with Playwright Test, so an existing Playwright Test runner can remain in place while Screenplay APIs express the scenario. The exact dependency versions and setup APIs can change; follow the current Serenity/JS Playwright Test integration guide when setting up a project.
In a JavaScript project, install Playwright Test and the Serenity/JS modules used by the integration. For example, the relevant package names are:
npm install --save-dev @playwright/test @serenity-js/core @serenity-js/playwright
Install a compatible set of current versions and configure Playwright browsers according to the Playwright documentation. The example below assumes a local test page at http://localhost:3000 with accessible labels for search and add-to-cart controls, and a cart region whose accessible name is “Shopping cart.” Adapt the URL and selectors to the application under test.
4. Write a complete Screenplay test
This example defines a task for searching and adding a product, then asks a question about the cart and asserts the result. It uses Playwright locators through the Serenity/JS Playwright ability.
import { test, expect } from '@playwright/test';
import { actorCalled } from '@serenity-js/core';
import { BrowseTheWebWithPlaywright } from '@serenity-js/playwright';
import { By, Click, Enter, Navigate, PageElement, Text } from '@serenity-js/web';
import { Task } from '@serenity-js/core';
const SearchForAndAddProduct = (productName) => Task.where(
`#actor searches for and adds ${ productName }`,
Enter.theValue(productName).into(
PageElement.located(By.css('[aria-label="Search products"]')),
),
Click.on(
PageElement.located(By.css('[aria-label="Search"]')),
),
Click.on(
PageElement.located(By.css(`[data-product-name="${ productName }"] [aria-label="Add to cart"]`)),
),
);
const CartContents = () => Text.of(
PageElement.located(By.css('[aria-label="Shopping cart"]')),
);
test('a shopper can add a product to the cart', async ({ page }) => {
const shopper = actorCalled('shopper')
.whoCan(BrowseTheWebWithPlaywright.using(page));
await shopper.attemptsTo(
Navigate.to('http://localhost:3000'),
SearchForAndAddProduct('Everest guide'),
);
const cartText = await shopper.asks(CartContents());
expect(cartText).toContain('Everest guide');
});
Serenity/JS APIs and imports evolve, so treat this as a compact implementation example and check the current official integration documentation for the versions in your project. In particular, use locator strategies supported by the installed Serenity/JS modules and your page’s accessible markup. Prefer stable accessible names or test identifiers over brittle positional selectors.
What each layer does here
shopperis the actor, with a browser ability backed by the Playwright page fixture.SearchForAndAddProductis a task named in terms of user intent; its steps are direct interactions.CartContentsis a question that retrieves page state.expectmakes the required outcome explicit in the test runner.
The task is useful if it gives a repeated or meaningful workflow a stable name. If it is only a wrapper around a single operation and makes the test harder to scan, inline it or use a simpler test structure.
5. Organize tasks, interactions, and questions
Tasks describe workflow steps
Name tasks using terms a product owner or tester would recognize: SearchForProduct, SubmitApplication, or ApproveRefund. A task can compose smaller tasks and interactions. Keep it focused enough that its name still describes what it does, and avoid embedding assertions that belong in the scenario’s outcome checks unless the assertion is intrinsic to that task’s contract.
Interactions perform direct operations
Interactions carry operations such as navigation, clicking, typing, or sending a request. Centralizing recurring low-level mechanics can reduce selector duplication, but avoid building generic layers that obscure which control the test uses. A selector change should be easy to trace to the affected behavior.
Questions retrieve the evidence for an assertion
Questions should retrieve the smallest useful piece of state: a heading, status, count, API result, or domain value. Then assert the expected value in the test. This makes it clear what the system returned and what the test requires. Browser text is only one possibility: actors can have API or database abilities when those checks fit the behavior and test architecture.
Abilities define integration boundaries
Give an actor the capabilities needed for its scenario. A browser ability can support UI behavior; an API ability can set up or inspect state; a database ability may be appropriate for targeted verification. Keep these boundaries explicit so a task’s dependencies are understandable. Serenity BDD and Serenity/JS document browser and other integration approaches; consult the implementation documentation for concrete APIs.
6. Keep the pattern proportionate
Screenplay is useful when its names make scenarios clearer, workflows are repeated, or different actors and integrations need an explicit model. It adds concepts and code to learn and maintain, so its benefit is not automatic. Official framework materials present readability and maintainability as goals, not guaranteed measured outcomes.
- Use a direct test for a one-off behavior if the extra layers add no clarity.
- Extract a task when it names meaningful work or prevents meaningful duplication.
- Keep a question close to the expected outcome it supports.
- Do not create one class per click just to satisfy the pattern’s vocabulary.
- Refactor gradually: begin with one scenario or repeated workflow, then assess whether the resulting test is easier for the team to change.
Community discussions raise concerns about learning curve and over-structuring, but anecdotes do not establish how teams generally perform. Choose based on whether the abstractions help your codebase and team.
7. Use Screenplay with your existing runner
Screenplay is not synonymous with Cucumber. Serenity/JS documents Playwright Test integration, and Serenity BDD materials demonstrate Java usage in JUnit and Cucumber contexts. Keep the runner your team already understands unless another requirement calls for a change. The pattern concerns how test actions and observations are modeled; runner choice concerns execution, fixtures, reporting, and lifecycle.
For Java, start with the official Serenity BDD Screenplay fundamentals and its first-scenario material. For JavaScript, use the current Serenity/JS Playwright Test guide. Compare options using language fit, runner integration, needed browser/API/database capabilities, documentation freshness, and the amount of abstraction your team can maintain.
8. Capture browser evidence for debugging
Screenshots can help diagnose a failed UI scenario by preserving what the browser rendered at a particular point. In a Playwright test, the direct approach is to capture the page after the relevant action or when an assertion fails. Keep screenshots tied to useful failure evidence, and consider whether pages contain sensitive user or test data before storing artifacts.
import { test, expect } from '@playwright/test';
test('capture a browser state for debugging', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.getByRole('heading', { name: 'Products' }).waitFor();
await page.screenshot({ path: 'artifacts/products.png', fullPage: true });
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
});
This direct browser capture is useful when the test already owns a browser session and needs its exact state. If you need a separate screenshot of a public page without setting up browser capture infrastructure, ScreenshotNeo is a website screenshot API and MCP server for developers. Its capture options include full-page screenshots, element capture, device and viewport settings, and custom wait behavior. See the ScreenshotNeo site and API documentation.
9. Troubleshooting common problems
| Problem | Likely cause | Fix |
|---|---|---|
| Test code has many tiny task classes | Abstractions were created for every operation, even when they add no meaning. | Keep low-level steps together where useful; extract only workflows that improve naming, reuse, or change isolation. |
| A task name does not match its behavior | The task grew beyond its original responsibility. | Split it around meaningful user goals, or rename it to describe the full workflow. |
| Assertion fails even though the action appeared to work | The test may read state too early, inspect the wrong region, or assert stale text. | Wait for a meaningful application state, verify the question’s locator, and assert the exact observable outcome. |
| Locator cannot find an element | The selector does not match current markup, the page is not ready, or the locator is ambiguous. | Inspect the rendered page, prefer accessible roles/names or stable test IDs, and scope the locator to the relevant region. |
| Serenity/JS import or method is unavailable | Example code and installed package versions may not match. | Check the current official documentation for the installed versions and keep Serenity/JS packages on compatible versions. |
| Adding Screenplay requires changing the runner | The implementation’s runner integration was confused with the pattern itself. | Review the framework’s integration for your current runner; Serenity/JS documents Playwright Test integration. |
| Tests are slower after introducing abstractions | Extra work may come from the browser flow, waits, or setup, rather than the naming structure itself. | Measure where time is spent, remove redundant navigation and waits, and keep tasks from repeating setup. |
10. Performance, reliability, and cost
The Screenplay Pattern itself does not establish a speed improvement or regression. Runtime is determined by what the test does: browser startup, application response, external dependencies, setup, waits, and parallel execution. Measure your own suite rather than assuming the pattern changes runtime.
For reliability, wait for application conditions that matter instead of adding arbitrary delays, use stable locators, isolate test data, and ensure questions inspect the intended state. Keep assertions specific enough to identify failures without tying the test to incidental layout details.
There is no universal cost figure for adopting Screenplay. Consider implementation and maintenance effort, team familiarity, and whether reusable tasks reduce repeated work. The source material reviewed does not provide attributable adoption, speed, defect, or maintenance statistics, so none are claimed here.
11. Or skip the browser setup
For a website screenshot outside the browser session owned by your test, call ScreenshotNeo’s API. The response is an image or PDF according to the request options. This minimal cURL example saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
See the ScreenshotNeo API documentation for the full request options. Cookie banners are accepted like a visitor and removed along with known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.
12. Frequently asked questions
Do I need Cucumber to use Screenplay?
No. Screenplay is a way to structure tests. Implementations can work with different runners; Serenity/JS documents integration with Playwright Test.
Can an actor use both a browser and an API?
Yes, when the implementation provides those abilities and the scenario needs them. Keep the integration capabilities explicit and use each for a clear purpose.
Should every test use all five building blocks?
No. Use the layers that clarify the scenario. A straightforward test can remain straightforward if additional classes would only add ceremony.
Where can I read more?
Start with the official Serenity BDD Screenplay fundamentals or the Serenity/JS pattern guide. Manning’s catalog identifies a chapter on scalable test automation with Screenplay in BDD in Action, Second Edition; check the publisher’s listing for current details.


