A Python Screenplay pattern framework with cinematic test reporting.
Screenwright brings the Screenplay pattern to Python testing. Actors perform Tasks, exercise Abilities through Interactions, and verify outcomes by asking Questions -- all while generating cinematic HTML reports with Material Design 3 dark neon theming.
- Screenplay Pattern Engine -- Actor, Task, Interaction, Question, Ability, and Fact primitives with full event sourcing
- pytest-bdd Integration -- Auto-registered plugin with Stage and Actor fixtures, zero configuration required
- Cinematic HTML Reports -- Dashboard overview with summary cards, feature lists, and scenario rows; theatre-style scenario presentations with actor entrances, ability badges, interaction arrows, and narration subtitles
- Material Design 3 Theming -- YAML-based theme configuration with tonal palette generation, neon glow effects, and customizable personas
- Typed Domain Events -- 17 frozen dataclass events with JSON serialization, enabling full traceability of test execution
- Fully Typed -- py.typed marker with strict mypy compliance
pip install screenwrightFor development:
pip install screenwright[dev]# features/search.feature
Feature: Web Search
Scenario: Search for a term
Given Ali can browse the web
When Ali searches for "Screenplay pattern"
Then Ali should see results containing "Screenplay"# tests/step_defs/test_search_steps.py
from pytest_bdd import scenario, given, when, then, parsers
from screenwright import Actor, Ability, Interaction, Question, task
# -- Abilities --
class BrowseTheWeb(Ability):
def __init__(self, driver):
self.driver = driver
@staticmethod
def using(driver):
return BrowseTheWeb(driver)
# -- Interactions --
class SearchFor(Interaction):
def __init__(self, term):
self.term = term
@staticmethod
def the_term(term):
return SearchFor(term)
def perform_as(self, actor):
driver = actor.ability_to(BrowseTheWeb).driver
driver.find_element("name", "q").send_keys(self.term)
driver.find_element("name", "q").submit()
# -- Questions --
class SearchResults(Question):
def answered_by(self, actor):
driver = actor.ability_to(BrowseTheWeb).driver
return driver.find_element("id", "results").text
# -- Steps --
@scenario("../features/search.feature", "Search for a term")
def test_search():
pass
@given("Ali can browse the web")
def ali_can_browse(actor, stage):
ali = stage.actor_named("Ali")
ali.who_can(BrowseTheWeb.using(create_driver()))
@when(parsers.parse('Ali searches for "{term}"'))
def ali_searches(stage, term):
ali = stage.shines_spotlight_on("Ali")
ali.attempts_to(SearchFor.the_term(term))
@then(parsers.parse('Ali should see results containing "{text}"'))
def ali_sees_results(stage, text):
ali = stage.shines_spotlight_on("Ali")
ali.should_see_that(SearchResults(), lambda answer: text in answer)pytest tests/ --screenwright-report=report.htmlOpen report.html to view the cinematic test report.
Full documentation is available at screenwright.dev.
Screenwright is released under the Apache License 2.0.