Screenplay Pattern
Concept
Screenplay models tests as actors performing tasks using abilities, rather than pages owning actions.
Actor → perform(Task) → Interaction → Browser / API
Actor → ask(Question) → Interaction → Browser / API
Three building blocks:
| Block |
Role |
| Actor |
Named persona (e.g. Alice) with abilities |
| Task |
High-level user goal (e.g. Login, PlaceOrder) |
| Interaction |
Low-level browser/API call (e.g. Click, Enter) |
| Question |
Read state to assert on (e.g. CurrentUrl, PageTitle) |
| Ability |
What the actor can do (e.g. BrowseTheWeb, CallAnAPI) |
Structure
Ability
class BrowseTheWeb:
def __init__(self, page: Page) -> None:
self.page = page
@classmethod
def using(cls, page: Page) -> "BrowseTheWeb":
return cls(page)
Interaction
class Click:
def __init__(self, locator: str) -> None:
self._locator = locator
def perform_as(self, actor: "Actor") -> None:
actor.ability(BrowseTheWeb).page.click(self._locator)
Task
class Login:
def __init__(self, username: str, password: str) -> None:
self._username = username
self._password = password
def perform_as(self, actor: "Actor") -> None:
actor.perform(
Enter.the_value(self._username).into('[data-testid="username"]'),
Enter.the_value(self._password).into('[data-testid="password"]'),
Click('[data-testid="submit"]'),
)
Actor
class Actor:
def __init__(self, name: str) -> None:
self.name = name
self._abilities: dict = {}
def can(self, *abilities) -> "Actor":
for ability in abilities:
self._abilities[type(ability)] = ability
return self
def ability(self, ability_type: type):
return self._abilities[ability_type]
def perform(self, *tasks) -> None:
for task in tasks:
task.perform_as(self)
def ask(self, question) -> any:
return question.answered_by(self)
Test
def test_login_success(page: Page) -> None:
alice = Actor("Alice").can(BrowseTheWeb.using(page))
alice.perform(Login("alice@example.com", "secret"))
assert alice.ask(CurrentUrl()) == "/dashboard"
Benefits vs Page Object Model
| Criterion |
POM |
Screenplay |
| Reusability |
Page-level |
Task / Interaction level |
| Readability |
Action names on pages |
Intent-driven task names |
| Composability |
Limited |
High — tasks compose tasks |
| Complexity |
Lower |
Higher initial setup |
| Parallel safety |
Per-page instance |
Per-actor instance |
When to Use
| Scenario |
Use Screenplay |
| Large test suite with many shared flows |
Yes |
| Multiple actor types (admin, user, guest) |
Yes |
| Tests need to read like business requirements |
Yes |
| Small project with 10–20 tests |
No — POM is sufficient |
| API-only testing |
No |
Risks
| Risk |
Mitigation |
| Over-abstraction for simple flows |
Use POM or direct calls for trivial tests |
| Verbose setup for each actor |
Shared actor builder fixtures |
| Deep call stack makes debugging hard |
Log each interaction in perform_as |