Skip to content

Core API

screenwright.core

Screenplay Engine -- Core Domain.

This package implements the Screenplay pattern: Actors perform Tasks by exercising Abilities through Interactions, and verify outcomes by asking Questions.

Ability

A capability that an Actor possesses.

Subclass this to create specific Abilities

class BrowseTheWeb(Ability): def init(self, driver: WebDriver) -> None: self.driver = driver

@staticmethod
def using(driver: WebDriver) -> BrowseTheWeb:
    return BrowseTheWeb(driver)
The Actor retrieves Abilities by type

driver_ability = actor.ability_to(BrowseTheWeb)

Source code in src/screenwright/core/ability.py
class Ability:
    """A capability that an Actor possesses.

    Subclass this to create specific Abilities:
        class BrowseTheWeb(Ability):
            def __init__(self, driver: WebDriver) -> None:
                self.driver = driver

            @staticmethod
            def using(driver: WebDriver) -> BrowseTheWeb:
                return BrowseTheWeb(driver)

    The Actor retrieves Abilities by type:
        driver_ability = actor.ability_to(BrowseTheWeb)
    """

    def __str__(self) -> str:
        """Human-readable name for narration.

        Override to customize. Default: class name with spaces
        inserted before uppercase letters.
        """
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)

    def __repr__(self) -> str:
        return f"{type(self).__name__}()"

__str__()

Human-readable name for narration.

Override to customize. Default: class name with spaces inserted before uppercase letters.

Source code in src/screenwright/core/ability.py
def __str__(self) -> str:
    """Human-readable name for narration.

    Override to customize. Default: class name with spaces
    inserted before uppercase letters.
    """
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)

AbilityNotFoundError

Bases: ScreenwrightError

Raised when an Actor does not have a required Ability.

This typically means the Actor was not granted the Ability before attempting an Interaction that requires it.

Source code in src/screenwright/core/exceptions.py
class AbilityNotFoundError(ScreenwrightError):
    """Raised when an Actor does not have a required Ability.

    This typically means the Actor was not granted the Ability
    before attempting an Interaction that requires it.
    """

    def __init__(self, actor_name: str, ability_type: str) -> None:
        self.actor_name = actor_name
        self.ability_type = ability_type
        super().__init__(
            f"Actor '{actor_name}' does not have the ability '{ability_type}'. "
            f"Grant it with: actor.who_can({ability_type}(...))"
        )

Actor

A named persona who performs actions against the system under test.

An Actor has Abilities (what they can do), remembers Facts (what they know), performs Tasks (high-level goals) and Interactions (atomic actions), and asks Questions (queries about state).

Example

ali = Actor.named("Ali", description="a scrum master") ali.who_can(BrowseTheWeb.using(driver)) ali.attempts_to(Navigate.to("https://example.com")) ali.should_see_that(TextOf(HEADING), is_equal_to("Welcome"))

Source code in src/screenwright/core/actor.py
class Actor:
    """A named persona who performs actions against the system under test.

    An Actor has Abilities (what they can do), remembers Facts (what
    they know), performs Tasks (high-level goals) and Interactions
    (atomic actions), and asks Questions (queries about state).

    Example:
        ali = Actor.named("Ali", description="a scrum master")
        ali.who_can(BrowseTheWeb.using(driver))
        ali.attempts_to(Navigate.to("https://example.com"))
        ali.should_see_that(TextOf(HEADING), is_equal_to("Welcome"))
    """

    def __init__(
        self,
        name: str,
        *,
        description: str | None = None,
        publisher: EventPublisher | None = None,
    ) -> None:
        self._name = name
        self._description = description
        self._abilities: dict[type[Ability], Ability] = {}
        self._facts: FactStore = {}
        self._publisher = publisher
        self._event_stack: list[str] = []  # Stack of parent event IDs

    @staticmethod
    def named(
        name: str,
        *,
        description: str | None = None,
        publisher: EventPublisher | None = None,
    ) -> Actor:
        """Create a new Actor with the given name.

        Args:
            name: The Actor's name (e.g., "Ali").
            description: Optional role description (e.g., "a scrum master").
            publisher: Optional EventPublisher for emitting domain events.

        Returns:
            A new Actor instance.
        """
        return Actor(name, description=description, publisher=publisher)

    @property
    def name(self) -> str:
        """The Actor's name."""
        return self._name

    @property
    def description(self) -> str | None:
        """The Actor's role description."""
        return self._description

    def who_can(self, *abilities: Ability) -> Actor:
        """Grant Abilities to this Actor.

        Args:
            abilities: One or more Abilities to grant.

        Returns:
            Self, for fluent chaining.
        """
        for ability in abilities:
            self._abilities[type(ability)] = ability
            self._publish(
                AbilityGranted(
                    actor_name=self._name,
                    ability_name=str(ability),
                    ability_type=type(ability).__name__,
                )
            )
        return self

    def ability_to(self, ability_type: type[T]) -> T:
        """Retrieve a previously granted Ability by type.

        Args:
            ability_type: The class of the Ability to retrieve.

        Returns:
            The Ability instance.

        Raises:
            AbilityNotFoundError: If the Actor does not have this Ability.
        """
        ability = self._abilities.get(ability_type)  # type: ignore[arg-type]
        if ability is None:
            raise AbilityNotFoundError(self._name, ability_type.__name__)
        return ability  # type: ignore[return-value]

    def has_ability_to(self, ability_type: type[Ability]) -> bool:
        """Check whether this Actor has a specific Ability.

        Args:
            ability_type: The class of the Ability to check.

        Returns:
            True if the Actor has the Ability.
        """
        return ability_type in self._abilities

    def remembers(self, **facts: Any) -> Actor:
        """Store Facts that this Actor knows.

        Args:
            facts: Key-value pairs to remember.

        Returns:
            Self, for fluent chaining.
        """
        for key, value in facts.items():
            self._facts[key] = value
            self._publish(
                FactRemembered(
                    actor_name=self._name,
                    fact_key=key,
                    fact_value_summary=summarize_fact_value(value),
                )
            )
        return self

    def recalls(self, fact_key: str) -> Any:
        """Retrieve a previously remembered Fact.

        Args:
            fact_key: The key of the Fact to recall.

        Returns:
            The stored value.

        Raises:
            FactNotFoundError: If the Actor does not remember this Fact.
        """
        if fact_key not in self._facts:
            raise FactNotFoundError(self._name, fact_key)
        return self._facts[fact_key]

    def attempts_to(self, *performables: Performable) -> None:
        """Perform one or more Tasks or Interactions.

        Args:
            performables: The Performables to execute, in order.
        """
        for performable in performables:
            performable_name = _describe(performable)
            is_task = _is_task(performable)
            parent_id = self._current_parent_id

            if is_task:
                task_event = TaskStarted(
                    actor_name=self._name,
                    task_name=performable_name,
                    parent_id=parent_id,
                )
                self._publish(task_event)
                self._event_stack.append(task_event.event_id)
            else:
                target = getattr(performable, "target", None)
                target_str = str(target) if target is not None else None
                interaction_event = InteractionStarted(
                    actor_name=self._name,
                    interaction_name=performable_name,
                    target=target_str,
                    parent_id=parent_id,
                )
                self._publish(interaction_event)
                self._event_stack.append(interaction_event.event_id)

            start_time = time.monotonic()
            try:
                performable.perform_as(self)
                duration_ms = (time.monotonic() - start_time) * 1000

                if is_task:
                    self._publish(
                        TaskCompleted(
                            actor_name=self._name,
                            task_name=performable_name,
                            duration_ms=duration_ms,
                        )
                    )
                else:
                    self._publish(
                        InteractionCompleted(
                            actor_name=self._name,
                            interaction_name=performable_name,
                            duration_ms=duration_ms,
                        )
                    )
            except Exception as exc:
                duration_ms = (time.monotonic() - start_time) * 1000

                if is_task:
                    self._publish(
                        TaskFailed(
                            actor_name=self._name,
                            task_name=performable_name,
                            error_type=type(exc).__name__,
                            error_message=str(exc),
                            duration_ms=duration_ms,
                        )
                    )
                else:
                    self._publish(
                        InteractionFailed(
                            actor_name=self._name,
                            interaction_name=performable_name,
                            error_type=type(exc).__name__,
                            error_message=str(exc),
                            duration_ms=duration_ms,
                        )
                    )
                raise
            finally:
                self._event_stack.pop()

    def should_see_that(
        self,
        question: Answerable[T],
        matcher: Callable[[T], bool] | T,
    ) -> None:
        """Ask a Question and verify the answer.

        Args:
            question: The Answerable to query.
            matcher: Either a callable predicate or an expected value
                     for equality comparison.

        Raises:
            AssertionError: If the matcher/predicate fails.
        """
        question_name = _describe(question)
        parent_id = self._current_parent_id

        asked_event = QuestionAsked(
            actor_name=self._name,
            question_name=question_name,
            parent_id=parent_id,
        )
        self._publish(asked_event)
        self._event_stack.append(asked_event.event_id)

        start_time = time.monotonic()
        try:
            answer = question.answered_by(self)
            duration_ms = (time.monotonic() - start_time) * 1000

            self._publish(
                QuestionAnswered(
                    actor_name=self._name,
                    question_name=question_name,
                    answer_summary=summarize_fact_value(answer),
                    duration_ms=duration_ms,
                )
            )

            if callable(matcher) and not isinstance(matcher, (str, int, float, bool)):
                if not matcher(answer):
                    raise AssertionError(
                        f"{self._name} expected {question_name} to match, "
                        f"but got: {answer!r}"
                    )
            elif answer != matcher:
                raise AssertionError(
                    f"{self._name} expected {question_name} to be "
                    f"{matcher!r}, but got: {answer!r}"
                )
        finally:
            self._event_stack.pop()

    @property
    def _current_parent_id(self) -> str | None:
        """The event_id of the currently active parent event, or None."""
        return self._event_stack[-1] if self._event_stack else None

    def _publish(self, event: Any) -> None:
        """Emit a domain event if a publisher is configured."""
        if self._publisher is not None:
            self._publisher.publish(event)

    def __str__(self) -> str:
        return self._name

    def __repr__(self) -> str:
        desc = f", description='{self._description}'" if self._description else ""
        return f"Actor(name='{self._name}'{desc})"

description property

The Actor's role description.

name property

The Actor's name.

ability_to(ability_type)

Retrieve a previously granted Ability by type.

Parameters:

Name Type Description Default
ability_type type[T]

The class of the Ability to retrieve.

required

Returns:

Type Description
T

The Ability instance.

Raises:

Type Description
AbilityNotFoundError

If the Actor does not have this Ability.

Source code in src/screenwright/core/actor.py
def ability_to(self, ability_type: type[T]) -> T:
    """Retrieve a previously granted Ability by type.

    Args:
        ability_type: The class of the Ability to retrieve.

    Returns:
        The Ability instance.

    Raises:
        AbilityNotFoundError: If the Actor does not have this Ability.
    """
    ability = self._abilities.get(ability_type)  # type: ignore[arg-type]
    if ability is None:
        raise AbilityNotFoundError(self._name, ability_type.__name__)
    return ability  # type: ignore[return-value]

attempts_to(*performables)

Perform one or more Tasks or Interactions.

Parameters:

Name Type Description Default
performables Performable

The Performables to execute, in order.

()
Source code in src/screenwright/core/actor.py
def attempts_to(self, *performables: Performable) -> None:
    """Perform one or more Tasks or Interactions.

    Args:
        performables: The Performables to execute, in order.
    """
    for performable in performables:
        performable_name = _describe(performable)
        is_task = _is_task(performable)
        parent_id = self._current_parent_id

        if is_task:
            task_event = TaskStarted(
                actor_name=self._name,
                task_name=performable_name,
                parent_id=parent_id,
            )
            self._publish(task_event)
            self._event_stack.append(task_event.event_id)
        else:
            target = getattr(performable, "target", None)
            target_str = str(target) if target is not None else None
            interaction_event = InteractionStarted(
                actor_name=self._name,
                interaction_name=performable_name,
                target=target_str,
                parent_id=parent_id,
            )
            self._publish(interaction_event)
            self._event_stack.append(interaction_event.event_id)

        start_time = time.monotonic()
        try:
            performable.perform_as(self)
            duration_ms = (time.monotonic() - start_time) * 1000

            if is_task:
                self._publish(
                    TaskCompleted(
                        actor_name=self._name,
                        task_name=performable_name,
                        duration_ms=duration_ms,
                    )
                )
            else:
                self._publish(
                    InteractionCompleted(
                        actor_name=self._name,
                        interaction_name=performable_name,
                        duration_ms=duration_ms,
                    )
                )
        except Exception as exc:
            duration_ms = (time.monotonic() - start_time) * 1000

            if is_task:
                self._publish(
                    TaskFailed(
                        actor_name=self._name,
                        task_name=performable_name,
                        error_type=type(exc).__name__,
                        error_message=str(exc),
                        duration_ms=duration_ms,
                    )
                )
            else:
                self._publish(
                    InteractionFailed(
                        actor_name=self._name,
                        interaction_name=performable_name,
                        error_type=type(exc).__name__,
                        error_message=str(exc),
                        duration_ms=duration_ms,
                    )
                )
            raise
        finally:
            self._event_stack.pop()

has_ability_to(ability_type)

Check whether this Actor has a specific Ability.

Parameters:

Name Type Description Default
ability_type type[Ability]

The class of the Ability to check.

required

Returns:

Type Description
bool

True if the Actor has the Ability.

Source code in src/screenwright/core/actor.py
def has_ability_to(self, ability_type: type[Ability]) -> bool:
    """Check whether this Actor has a specific Ability.

    Args:
        ability_type: The class of the Ability to check.

    Returns:
        True if the Actor has the Ability.
    """
    return ability_type in self._abilities

named(name, *, description=None, publisher=None) staticmethod

Create a new Actor with the given name.

Parameters:

Name Type Description Default
name str

The Actor's name (e.g., "Ali").

required
description str | None

Optional role description (e.g., "a scrum master").

None
publisher EventPublisher | None

Optional EventPublisher for emitting domain events.

None

Returns:

Type Description
Actor

A new Actor instance.

Source code in src/screenwright/core/actor.py
@staticmethod
def named(
    name: str,
    *,
    description: str | None = None,
    publisher: EventPublisher | None = None,
) -> Actor:
    """Create a new Actor with the given name.

    Args:
        name: The Actor's name (e.g., "Ali").
        description: Optional role description (e.g., "a scrum master").
        publisher: Optional EventPublisher for emitting domain events.

    Returns:
        A new Actor instance.
    """
    return Actor(name, description=description, publisher=publisher)

recalls(fact_key)

Retrieve a previously remembered Fact.

Parameters:

Name Type Description Default
fact_key str

The key of the Fact to recall.

required

Returns:

Type Description
Any

The stored value.

Raises:

Type Description
FactNotFoundError

If the Actor does not remember this Fact.

Source code in src/screenwright/core/actor.py
def recalls(self, fact_key: str) -> Any:
    """Retrieve a previously remembered Fact.

    Args:
        fact_key: The key of the Fact to recall.

    Returns:
        The stored value.

    Raises:
        FactNotFoundError: If the Actor does not remember this Fact.
    """
    if fact_key not in self._facts:
        raise FactNotFoundError(self._name, fact_key)
    return self._facts[fact_key]

remembers(**facts)

Store Facts that this Actor knows.

Parameters:

Name Type Description Default
facts Any

Key-value pairs to remember.

{}

Returns:

Type Description
Actor

Self, for fluent chaining.

Source code in src/screenwright/core/actor.py
def remembers(self, **facts: Any) -> Actor:
    """Store Facts that this Actor knows.

    Args:
        facts: Key-value pairs to remember.

    Returns:
        Self, for fluent chaining.
    """
    for key, value in facts.items():
        self._facts[key] = value
        self._publish(
            FactRemembered(
                actor_name=self._name,
                fact_key=key,
                fact_value_summary=summarize_fact_value(value),
            )
        )
    return self

should_see_that(question, matcher)

Ask a Question and verify the answer.

Parameters:

Name Type Description Default
question Answerable[T]

The Answerable to query.

required
matcher Callable[[T], bool] | T

Either a callable predicate or an expected value for equality comparison.

required

Raises:

Type Description
AssertionError

If the matcher/predicate fails.

Source code in src/screenwright/core/actor.py
def should_see_that(
    self,
    question: Answerable[T],
    matcher: Callable[[T], bool] | T,
) -> None:
    """Ask a Question and verify the answer.

    Args:
        question: The Answerable to query.
        matcher: Either a callable predicate or an expected value
                 for equality comparison.

    Raises:
        AssertionError: If the matcher/predicate fails.
    """
    question_name = _describe(question)
    parent_id = self._current_parent_id

    asked_event = QuestionAsked(
        actor_name=self._name,
        question_name=question_name,
        parent_id=parent_id,
    )
    self._publish(asked_event)
    self._event_stack.append(asked_event.event_id)

    start_time = time.monotonic()
    try:
        answer = question.answered_by(self)
        duration_ms = (time.monotonic() - start_time) * 1000

        self._publish(
            QuestionAnswered(
                actor_name=self._name,
                question_name=question_name,
                answer_summary=summarize_fact_value(answer),
                duration_ms=duration_ms,
            )
        )

        if callable(matcher) and not isinstance(matcher, (str, int, float, bool)):
            if not matcher(answer):
                raise AssertionError(
                    f"{self._name} expected {question_name} to match, "
                    f"but got: {answer!r}"
                )
        elif answer != matcher:
            raise AssertionError(
                f"{self._name} expected {question_name} to be "
                f"{matcher!r}, but got: {answer!r}"
            )
    finally:
        self._event_stack.pop()

who_can(*abilities)

Grant Abilities to this Actor.

Parameters:

Name Type Description Default
abilities Ability

One or more Abilities to grant.

()

Returns:

Type Description
Actor

Self, for fluent chaining.

Source code in src/screenwright/core/actor.py
def who_can(self, *abilities: Ability) -> Actor:
    """Grant Abilities to this Actor.

    Args:
        abilities: One or more Abilities to grant.

    Returns:
        Self, for fluent chaining.
    """
    for ability in abilities:
        self._abilities[type(ability)] = ability
        self._publish(
            AbilityGranted(
                actor_name=self._name,
                ability_name=str(ability),
                ability_type=type(ability).__name__,
            )
        )
    return self

Answerable

Bases: Protocol[T]

Anything an Actor can ask that returns a value.

Questions implement this protocol. The Actor.should_see_that() method accepts Answerables.

Source code in src/screenwright/core/answerable.py
@runtime_checkable
class Answerable(Protocol[T]):
    """Anything an Actor can ask that returns a value.

    Questions implement this protocol.
    The Actor.should_see_that() method accepts Answerables.
    """

    def answered_by(self, actor: Actor) -> T:
        """Answer this question as the given Actor.

        Args:
            actor: The Actor asking this question.

        Returns:
            The answer to the question.
        """
        ...

answered_by(actor)

Answer this question as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor asking this question.

required

Returns:

Type Description
T

The answer to the question.

Source code in src/screenwright/core/answerable.py
def answered_by(self, actor: Actor) -> T:
    """Answer this question as the given Actor.

    Args:
        actor: The Actor asking this question.

    Returns:
        The answer to the question.
    """
    ...

EventPublisher

Bases: Protocol

Output port for publishing domain events.

The core domain calls publish() to emit events. The adapter/collection layer provides the concrete implementation.

Source code in src/screenwright/core/event_publisher.py
class EventPublisher(Protocol):
    """Output port for publishing domain events.

    The core domain calls publish() to emit events.
    The adapter/collection layer provides the concrete implementation.
    """

    def publish(self, event: Event) -> None:
        """Publish a domain event.

        Args:
            event: The event to publish.
        """
        ...

publish(event)

Publish a domain event.

Parameters:

Name Type Description Default
event Event

The event to publish.

required
Source code in src/screenwright/core/event_publisher.py
def publish(self, event: Event) -> None:
    """Publish a domain event.

    Args:
        event: The event to publish.
    """
    ...

FactNotFoundError

Bases: ScreenwrightError

Raised when an Actor does not remember a requested Fact.

Source code in src/screenwright/core/exceptions.py
class FactNotFoundError(ScreenwrightError):
    """Raised when an Actor does not remember a requested Fact."""

    def __init__(self, actor_name: str, fact_key: str) -> None:
        self.actor_name = actor_name
        self.fact_key = fact_key
        super().__init__(
            f"Actor '{actor_name}' does not remember a fact named '{fact_key}'. "
            f"Store it with: actor.remembers({fact_key}=value)"
        )

Interaction

Base class for class-based Interactions.

Interactions are the leaf nodes of the Screenplay action tree. They directly manipulate the system under test through an Ability.

Subclass and implement perform_as(): class Click(Interaction): def init(self, target: Target) -> None: self.target = target

    @staticmethod
    def on(target: Target) -> Click:
        return Click(target)

    def perform_as(self, actor: Actor) -> None:
        driver = actor.ability_to(BrowseTheWeb).driver
        driver.find_element(*self.target.locator).click()
Source code in src/screenwright/core/interaction.py
class Interaction:
    """Base class for class-based Interactions.

    Interactions are the leaf nodes of the Screenplay action tree.
    They directly manipulate the system under test through an Ability.

    Subclass and implement perform_as():
        class Click(Interaction):
            def __init__(self, target: Target) -> None:
                self.target = target

            @staticmethod
            def on(target: Target) -> Click:
                return Click(target)

            def perform_as(self, actor: Actor) -> None:
                driver = actor.ability_to(BrowseTheWeb).driver
                driver.find_element(*self.target.locator).click()
    """

    _is_task: bool = False

    def perform_as(self, actor: Actor) -> None:
        """Perform this interaction as the given Actor.

        Args:
            actor: The Actor performing this interaction.
        """
        raise NotImplementedError(
            f"{type(self).__name__} must implement perform_as()"
        )

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)

__str__()

Human-readable name for narration.

Source code in src/screenwright/core/interaction.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)

perform_as(actor)

Perform this interaction as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this interaction.

required
Source code in src/screenwright/core/interaction.py
def perform_as(self, actor: Actor) -> None:
    """Perform this interaction as the given Actor.

    Args:
        actor: The Actor performing this interaction.
    """
    raise NotImplementedError(
        f"{type(self).__name__} must implement perform_as()"
    )

NoActorInSpotlightError

Bases: ScreenwrightError

Raised when the Spotlight is empty but an Actor is needed.

Source code in src/screenwright/core/exceptions.py
class NoActorInSpotlightError(ScreenwrightError):
    """Raised when the Spotlight is empty but an Actor is needed."""

    def __init__(self) -> None:
        super().__init__(
            "No Actor is currently in the Spotlight. "
            "Use stage.shine_spotlight_on(actor) to set one."
        )

NullEventPublisher

An EventPublisher that discards all events.

Used as the default when no publisher is configured, and in unit tests where event collection is not needed.

Source code in src/screenwright/core/event_publisher.py
class NullEventPublisher:
    """An EventPublisher that discards all events.

    Used as the default when no publisher is configured,
    and in unit tests where event collection is not needed.
    """

    def publish(self, event: Event) -> None:
        """Discard the event."""

publish(event)

Discard the event.

Source code in src/screenwright/core/event_publisher.py
def publish(self, event: Event) -> None:
    """Discard the event."""

Performable

Bases: Protocol

Anything an Actor can perform.

Both Tasks and Interactions implement this protocol. The Actor.attempts_to() method accepts Performables.

Source code in src/screenwright/core/performable.py
@runtime_checkable
class Performable(Protocol):
    """Anything an Actor can perform.

    Both Tasks and Interactions implement this protocol.
    The Actor.attempts_to() method accepts Performables.
    """

    def perform_as(self, actor: Actor) -> None:
        """Perform this action as the given Actor.

        Args:
            actor: The Actor performing this action.
        """
        ...

perform_as(actor)

Perform this action as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this action.

required
Source code in src/screenwright/core/performable.py
def perform_as(self, actor: Actor) -> None:
    """Perform this action as the given Actor.

    Args:
        actor: The Actor performing this action.
    """
    ...

Question

Bases: Generic[T]

Base class for class-based Questions.

Subclass and implement answered_by(): class TextOf(Question[str]): def init(self, target: Target) -> None: self.target = target

    def answered_by(self, actor: Actor) -> str:
        driver = actor.ability_to(BrowseTheWeb).driver
        return driver.find_element(*self.target.locator).text
Source code in src/screenwright/core/question.py
class Question(Generic[T]):
    """Base class for class-based Questions.

    Subclass and implement answered_by():
        class TextOf(Question[str]):
            def __init__(self, target: Target) -> None:
                self.target = target

            def answered_by(self, actor: Actor) -> str:
                driver = actor.ability_to(BrowseTheWeb).driver
                return driver.find_element(*self.target.locator).text
    """

    def answered_by(self, actor: Actor) -> T:
        """Answer this question as the given Actor.

        Args:
            actor: The Actor asking this question.

        Returns:
            The answer.
        """
        raise NotImplementedError(
            f"{type(self).__name__} must implement answered_by()"
        )

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)

__str__()

Human-readable name for narration.

Source code in src/screenwright/core/question.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)

answered_by(actor)

Answer this question as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor asking this question.

required

Returns:

Type Description
T

The answer.

Source code in src/screenwright/core/question.py
def answered_by(self, actor: Actor) -> T:
    """Answer this question as the given Actor.

    Args:
        actor: The Actor asking this question.

    Returns:
        The answer.
    """
    raise NotImplementedError(
        f"{type(self).__name__} must implement answered_by()"
    )

Scene dataclass

A single test scenario viewed through the theatre metaphor.

A Scene begins when a scenario starts and ends when it completes. During a Scene, Actors enter the Stage, perform their parts, and exit.

Attributes:

Name Type Description
name str

The scenario name.

feature_name str

The feature this scene belongs to.

tags list[str]

BDD tags associated with this scenario.

Source code in src/screenwright/core/scene.py
@dataclass
class Scene:
    """A single test scenario viewed through the theatre metaphor.

    A Scene begins when a scenario starts and ends when it completes.
    During a Scene, Actors enter the Stage, perform their parts, and exit.

    Attributes:
        name: The scenario name.
        feature_name: The feature this scene belongs to.
        tags: BDD tags associated with this scenario.
    """

    name: str
    feature_name: str = ""
    tags: list[str] = field(default_factory=list)

ScreenwrightError

Bases: Exception

Base exception for all Screenwright errors.

Source code in src/screenwright/core/exceptions.py
class ScreenwrightError(Exception):
    """Base exception for all Screenwright errors."""

Spotlight

Tracks the currently active Actor.

The Spotlight is managed by the Stage. When a Scene involves multiple Actors, the Spotlight shifts to indicate who is currently acting.

Source code in src/screenwright/core/spotlight.py
class Spotlight:
    """Tracks the currently active Actor.

    The Spotlight is managed by the Stage. When a Scene involves
    multiple Actors, the Spotlight shifts to indicate who is
    currently acting.
    """

    def __init__(self) -> None:
        self._current: Actor | None = None

    @property
    def current(self) -> Actor:
        """The Actor currently in the Spotlight.

        Raises:
            NoActorInSpotlightError: If no Actor is in the Spotlight.
        """
        if self._current is None:
            raise NoActorInSpotlightError()
        return self._current

    @property
    def has_actor(self) -> bool:
        """Whether any Actor is currently in the Spotlight."""
        return self._current is not None

    def shine_on(self, actor: Actor) -> Actor | None:
        """Shift the Spotlight to a new Actor.

        Args:
            actor: The Actor to place in the Spotlight.

        Returns:
            The previous Actor, or None if the Spotlight was empty.
        """
        previous = self._current
        self._current = actor
        return previous

    def clear(self) -> None:
        """Remove any Actor from the Spotlight."""
        self._current = None

current property

The Actor currently in the Spotlight.

Raises:

Type Description
NoActorInSpotlightError

If no Actor is in the Spotlight.

has_actor property

Whether any Actor is currently in the Spotlight.

clear()

Remove any Actor from the Spotlight.

Source code in src/screenwright/core/spotlight.py
def clear(self) -> None:
    """Remove any Actor from the Spotlight."""
    self._current = None

shine_on(actor)

Shift the Spotlight to a new Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor to place in the Spotlight.

required

Returns:

Type Description
Actor | None

The previous Actor, or None if the Spotlight was empty.

Source code in src/screenwright/core/spotlight.py
def shine_on(self, actor: Actor) -> Actor | None:
    """Shift the Spotlight to a new Actor.

    Args:
        actor: The Actor to place in the Spotlight.

    Returns:
        The previous Actor, or None if the Spotlight was empty.
    """
    previous = self._current
    self._current = actor
    return previous

Stage

The environment in which Actors perform during a test session.

The Stage manages the lifecycle of Actors, determines which Actor is in the Spotlight, and coordinates Scene (scenario) boundaries.

Attributes:

Name Type Description
publisher

The EventPublisher used to emit domain events.

Source code in src/screenwright/core/stage.py
class Stage:
    """The environment in which Actors perform during a test session.

    The Stage manages the lifecycle of Actors, determines which Actor
    is in the Spotlight, and coordinates Scene (scenario) boundaries.

    Attributes:
        publisher: The EventPublisher used to emit domain events.
    """

    def __init__(self, publisher: EventPublisher | None = None) -> None:
        self._publisher: EventPublisher = publisher or NullEventPublisher()
        self._spotlight = Spotlight()
        self._actors: dict[str, Actor] = {}
        self._current_scene: Scene | None = None

    @property
    def current_scene(self) -> Scene | None:
        """The currently active Scene, or None."""
        return self._current_scene

    def new_scene(
        self, name: str, feature_name: str = "", tags: list[str] | None = None
    ) -> Scene:
        """Start a new Scene (test scenario).

        Clears any existing Actors from the previous Scene.

        Args:
            name: The scenario name.
            feature_name: The feature this scenario belongs to.
            tags: BDD tags for the scenario.

        Returns:
            The new Scene.
        """
        self.clear()
        self._current_scene = Scene(
            name=name,
            feature_name=feature_name,
            tags=tags or [],
        )
        return self._current_scene

    def actor_named(self, name: str, *, description: str | None = None) -> Actor:
        """Get or create an Actor on this Stage.

        If an Actor with this name already exists, returns them.
        Otherwise, creates a new Actor, places them on Stage,
        and shifts the Spotlight to them.

        Args:
            name: The Actor's name.
            description: Optional role description.

        Returns:
            The Actor (new or existing).
        """
        if name in self._actors:
            actor = self._actors[name]
            self.shine_spotlight_on(actor)
            return actor

        actor = Actor(name, description=description, publisher=self._publisher)
        self._actors[name] = actor

        self._publisher.publish(
            ActorEnteredStage(
                actor_name=name,
                description=description,
            )
        )

        self.shine_spotlight_on(actor)
        return actor

    def in_the_spotlight(self) -> Actor:
        """The Actor currently in the Spotlight.

        Raises:
            NoActorInSpotlightError: If no Actor is in the Spotlight.
        """
        return self._spotlight.current

    def shine_spotlight_on(self, actor: Actor) -> None:
        """Shift the Spotlight to a specific Actor.

        Args:
            actor: The Actor to illuminate.
        """
        previous = self._spotlight.shine_on(actor)
        previous_name = previous.name if previous is not None else None

        if previous is not actor:
            self._publisher.publish(
                SpotlightChanged(
                    previous_actor=previous_name,
                    current_actor=actor.name,
                )
            )

    def clear(self) -> None:
        """Remove all Actors from the Stage and clear the Spotlight.

        Called at the end of each Scene (scenario).
        """
        for actor_name in list(self._actors.keys()):
            self._publisher.publish(ActorExitedStage(actor_name=actor_name))
        self._actors.clear()
        self._spotlight.clear()
        self._current_scene = None

    @property
    def actors(self) -> dict[str, Actor]:
        """All Actors currently on Stage."""
        return dict(self._actors)

actors property

All Actors currently on Stage.

current_scene property

The currently active Scene, or None.

actor_named(name, *, description=None)

Get or create an Actor on this Stage.

If an Actor with this name already exists, returns them. Otherwise, creates a new Actor, places them on Stage, and shifts the Spotlight to them.

Parameters:

Name Type Description Default
name str

The Actor's name.

required
description str | None

Optional role description.

None

Returns:

Type Description
Actor

The Actor (new or existing).

Source code in src/screenwright/core/stage.py
def actor_named(self, name: str, *, description: str | None = None) -> Actor:
    """Get or create an Actor on this Stage.

    If an Actor with this name already exists, returns them.
    Otherwise, creates a new Actor, places them on Stage,
    and shifts the Spotlight to them.

    Args:
        name: The Actor's name.
        description: Optional role description.

    Returns:
        The Actor (new or existing).
    """
    if name in self._actors:
        actor = self._actors[name]
        self.shine_spotlight_on(actor)
        return actor

    actor = Actor(name, description=description, publisher=self._publisher)
    self._actors[name] = actor

    self._publisher.publish(
        ActorEnteredStage(
            actor_name=name,
            description=description,
        )
    )

    self.shine_spotlight_on(actor)
    return actor

clear()

Remove all Actors from the Stage and clear the Spotlight.

Called at the end of each Scene (scenario).

Source code in src/screenwright/core/stage.py
def clear(self) -> None:
    """Remove all Actors from the Stage and clear the Spotlight.

    Called at the end of each Scene (scenario).
    """
    for actor_name in list(self._actors.keys()):
        self._publisher.publish(ActorExitedStage(actor_name=actor_name))
    self._actors.clear()
    self._spotlight.clear()
    self._current_scene = None

in_the_spotlight()

The Actor currently in the Spotlight.

Raises:

Type Description
NoActorInSpotlightError

If no Actor is in the Spotlight.

Source code in src/screenwright/core/stage.py
def in_the_spotlight(self) -> Actor:
    """The Actor currently in the Spotlight.

    Raises:
        NoActorInSpotlightError: If no Actor is in the Spotlight.
    """
    return self._spotlight.current

new_scene(name, feature_name='', tags=None)

Start a new Scene (test scenario).

Clears any existing Actors from the previous Scene.

Parameters:

Name Type Description Default
name str

The scenario name.

required
feature_name str

The feature this scenario belongs to.

''
tags list[str] | None

BDD tags for the scenario.

None

Returns:

Type Description
Scene

The new Scene.

Source code in src/screenwright/core/stage.py
def new_scene(
    self, name: str, feature_name: str = "", tags: list[str] | None = None
) -> Scene:
    """Start a new Scene (test scenario).

    Clears any existing Actors from the previous Scene.

    Args:
        name: The scenario name.
        feature_name: The feature this scenario belongs to.
        tags: BDD tags for the scenario.

    Returns:
        The new Scene.
    """
    self.clear()
    self._current_scene = Scene(
        name=name,
        feature_name=feature_name,
        tags=tags or [],
    )
    return self._current_scene

shine_spotlight_on(actor)

Shift the Spotlight to a specific Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor to illuminate.

required
Source code in src/screenwright/core/stage.py
def shine_spotlight_on(self, actor: Actor) -> None:
    """Shift the Spotlight to a specific Actor.

    Args:
        actor: The Actor to illuminate.
    """
    previous = self._spotlight.shine_on(actor)
    previous_name = previous.name if previous is not None else None

    if previous is not actor:
        self._publisher.publish(
            SpotlightChanged(
                previous_actor=previous_name,
                current_actor=actor.name,
            )
        )

Task

Base class for class-based Tasks.

Subclass and implement perform_as(): class Login(Task): def init(self, username: str, password: str) -> None: self.username = username self.password = password

    def perform_as(self, actor: Actor) -> None:
        actor.attempts_to(
            Navigate.to("/login"),
            Enter.the_value(self.username).into(USERNAME_FIELD),
            Enter.the_value(self.password).into(PASSWORD_FIELD),
            Click.on(LOGIN_BUTTON),
        )
Source code in src/screenwright/core/task.py
class Task:
    """Base class for class-based Tasks.

    Subclass and implement perform_as():
        class Login(Task):
            def __init__(self, username: str, password: str) -> None:
                self.username = username
                self.password = password

            def perform_as(self, actor: Actor) -> None:
                actor.attempts_to(
                    Navigate.to("/login"),
                    Enter.the_value(self.username).into(USERNAME_FIELD),
                    Enter.the_value(self.password).into(PASSWORD_FIELD),
                    Click.on(LOGIN_BUTTON),
                )
    """

    _is_task: bool = True

    def perform_as(self, actor: Actor) -> None:
        """Perform this task as the given Actor.

        Args:
            actor: The Actor performing this task.
        """
        raise NotImplementedError(f"{type(self).__name__} must implement perform_as()")

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)

__str__()

Human-readable name for narration.

Source code in src/screenwright/core/task.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)

perform_as(actor)

Perform this task as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this task.

required
Source code in src/screenwright/core/task.py
def perform_as(self, actor: Actor) -> None:
    """Perform this task as the given Actor.

    Args:
        actor: The Actor performing this task.
    """
    raise NotImplementedError(f"{type(self).__name__} must implement perform_as()")

ability

Ability base class -- capabilities that enable Actors to interact with systems.

Ability

A capability that an Actor possesses.

Subclass this to create specific Abilities

class BrowseTheWeb(Ability): def init(self, driver: WebDriver) -> None: self.driver = driver

@staticmethod
def using(driver: WebDriver) -> BrowseTheWeb:
    return BrowseTheWeb(driver)
The Actor retrieves Abilities by type

driver_ability = actor.ability_to(BrowseTheWeb)

Source code in src/screenwright/core/ability.py
class Ability:
    """A capability that an Actor possesses.

    Subclass this to create specific Abilities:
        class BrowseTheWeb(Ability):
            def __init__(self, driver: WebDriver) -> None:
                self.driver = driver

            @staticmethod
            def using(driver: WebDriver) -> BrowseTheWeb:
                return BrowseTheWeb(driver)

    The Actor retrieves Abilities by type:
        driver_ability = actor.ability_to(BrowseTheWeb)
    """

    def __str__(self) -> str:
        """Human-readable name for narration.

        Override to customize. Default: class name with spaces
        inserted before uppercase letters.
        """
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)

    def __repr__(self) -> str:
        return f"{type(self).__name__}()"
__str__()

Human-readable name for narration.

Override to customize. Default: class name with spaces inserted before uppercase letters.

Source code in src/screenwright/core/ability.py
def __str__(self) -> str:
    """Human-readable name for narration.

    Override to customize. Default: class name with spaces
    inserted before uppercase letters.
    """
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)

actor

Actor -- the central aggregate root of the Screenplay pattern.

Actor

A named persona who performs actions against the system under test.

An Actor has Abilities (what they can do), remembers Facts (what they know), performs Tasks (high-level goals) and Interactions (atomic actions), and asks Questions (queries about state).

Example

ali = Actor.named("Ali", description="a scrum master") ali.who_can(BrowseTheWeb.using(driver)) ali.attempts_to(Navigate.to("https://example.com")) ali.should_see_that(TextOf(HEADING), is_equal_to("Welcome"))

Source code in src/screenwright/core/actor.py
class Actor:
    """A named persona who performs actions against the system under test.

    An Actor has Abilities (what they can do), remembers Facts (what
    they know), performs Tasks (high-level goals) and Interactions
    (atomic actions), and asks Questions (queries about state).

    Example:
        ali = Actor.named("Ali", description="a scrum master")
        ali.who_can(BrowseTheWeb.using(driver))
        ali.attempts_to(Navigate.to("https://example.com"))
        ali.should_see_that(TextOf(HEADING), is_equal_to("Welcome"))
    """

    def __init__(
        self,
        name: str,
        *,
        description: str | None = None,
        publisher: EventPublisher | None = None,
    ) -> None:
        self._name = name
        self._description = description
        self._abilities: dict[type[Ability], Ability] = {}
        self._facts: FactStore = {}
        self._publisher = publisher
        self._event_stack: list[str] = []  # Stack of parent event IDs

    @staticmethod
    def named(
        name: str,
        *,
        description: str | None = None,
        publisher: EventPublisher | None = None,
    ) -> Actor:
        """Create a new Actor with the given name.

        Args:
            name: The Actor's name (e.g., "Ali").
            description: Optional role description (e.g., "a scrum master").
            publisher: Optional EventPublisher for emitting domain events.

        Returns:
            A new Actor instance.
        """
        return Actor(name, description=description, publisher=publisher)

    @property
    def name(self) -> str:
        """The Actor's name."""
        return self._name

    @property
    def description(self) -> str | None:
        """The Actor's role description."""
        return self._description

    def who_can(self, *abilities: Ability) -> Actor:
        """Grant Abilities to this Actor.

        Args:
            abilities: One or more Abilities to grant.

        Returns:
            Self, for fluent chaining.
        """
        for ability in abilities:
            self._abilities[type(ability)] = ability
            self._publish(
                AbilityGranted(
                    actor_name=self._name,
                    ability_name=str(ability),
                    ability_type=type(ability).__name__,
                )
            )
        return self

    def ability_to(self, ability_type: type[T]) -> T:
        """Retrieve a previously granted Ability by type.

        Args:
            ability_type: The class of the Ability to retrieve.

        Returns:
            The Ability instance.

        Raises:
            AbilityNotFoundError: If the Actor does not have this Ability.
        """
        ability = self._abilities.get(ability_type)  # type: ignore[arg-type]
        if ability is None:
            raise AbilityNotFoundError(self._name, ability_type.__name__)
        return ability  # type: ignore[return-value]

    def has_ability_to(self, ability_type: type[Ability]) -> bool:
        """Check whether this Actor has a specific Ability.

        Args:
            ability_type: The class of the Ability to check.

        Returns:
            True if the Actor has the Ability.
        """
        return ability_type in self._abilities

    def remembers(self, **facts: Any) -> Actor:
        """Store Facts that this Actor knows.

        Args:
            facts: Key-value pairs to remember.

        Returns:
            Self, for fluent chaining.
        """
        for key, value in facts.items():
            self._facts[key] = value
            self._publish(
                FactRemembered(
                    actor_name=self._name,
                    fact_key=key,
                    fact_value_summary=summarize_fact_value(value),
                )
            )
        return self

    def recalls(self, fact_key: str) -> Any:
        """Retrieve a previously remembered Fact.

        Args:
            fact_key: The key of the Fact to recall.

        Returns:
            The stored value.

        Raises:
            FactNotFoundError: If the Actor does not remember this Fact.
        """
        if fact_key not in self._facts:
            raise FactNotFoundError(self._name, fact_key)
        return self._facts[fact_key]

    def attempts_to(self, *performables: Performable) -> None:
        """Perform one or more Tasks or Interactions.

        Args:
            performables: The Performables to execute, in order.
        """
        for performable in performables:
            performable_name = _describe(performable)
            is_task = _is_task(performable)
            parent_id = self._current_parent_id

            if is_task:
                task_event = TaskStarted(
                    actor_name=self._name,
                    task_name=performable_name,
                    parent_id=parent_id,
                )
                self._publish(task_event)
                self._event_stack.append(task_event.event_id)
            else:
                target = getattr(performable, "target", None)
                target_str = str(target) if target is not None else None
                interaction_event = InteractionStarted(
                    actor_name=self._name,
                    interaction_name=performable_name,
                    target=target_str,
                    parent_id=parent_id,
                )
                self._publish(interaction_event)
                self._event_stack.append(interaction_event.event_id)

            start_time = time.monotonic()
            try:
                performable.perform_as(self)
                duration_ms = (time.monotonic() - start_time) * 1000

                if is_task:
                    self._publish(
                        TaskCompleted(
                            actor_name=self._name,
                            task_name=performable_name,
                            duration_ms=duration_ms,
                        )
                    )
                else:
                    self._publish(
                        InteractionCompleted(
                            actor_name=self._name,
                            interaction_name=performable_name,
                            duration_ms=duration_ms,
                        )
                    )
            except Exception as exc:
                duration_ms = (time.monotonic() - start_time) * 1000

                if is_task:
                    self._publish(
                        TaskFailed(
                            actor_name=self._name,
                            task_name=performable_name,
                            error_type=type(exc).__name__,
                            error_message=str(exc),
                            duration_ms=duration_ms,
                        )
                    )
                else:
                    self._publish(
                        InteractionFailed(
                            actor_name=self._name,
                            interaction_name=performable_name,
                            error_type=type(exc).__name__,
                            error_message=str(exc),
                            duration_ms=duration_ms,
                        )
                    )
                raise
            finally:
                self._event_stack.pop()

    def should_see_that(
        self,
        question: Answerable[T],
        matcher: Callable[[T], bool] | T,
    ) -> None:
        """Ask a Question and verify the answer.

        Args:
            question: The Answerable to query.
            matcher: Either a callable predicate or an expected value
                     for equality comparison.

        Raises:
            AssertionError: If the matcher/predicate fails.
        """
        question_name = _describe(question)
        parent_id = self._current_parent_id

        asked_event = QuestionAsked(
            actor_name=self._name,
            question_name=question_name,
            parent_id=parent_id,
        )
        self._publish(asked_event)
        self._event_stack.append(asked_event.event_id)

        start_time = time.monotonic()
        try:
            answer = question.answered_by(self)
            duration_ms = (time.monotonic() - start_time) * 1000

            self._publish(
                QuestionAnswered(
                    actor_name=self._name,
                    question_name=question_name,
                    answer_summary=summarize_fact_value(answer),
                    duration_ms=duration_ms,
                )
            )

            if callable(matcher) and not isinstance(matcher, (str, int, float, bool)):
                if not matcher(answer):
                    raise AssertionError(
                        f"{self._name} expected {question_name} to match, "
                        f"but got: {answer!r}"
                    )
            elif answer != matcher:
                raise AssertionError(
                    f"{self._name} expected {question_name} to be "
                    f"{matcher!r}, but got: {answer!r}"
                )
        finally:
            self._event_stack.pop()

    @property
    def _current_parent_id(self) -> str | None:
        """The event_id of the currently active parent event, or None."""
        return self._event_stack[-1] if self._event_stack else None

    def _publish(self, event: Any) -> None:
        """Emit a domain event if a publisher is configured."""
        if self._publisher is not None:
            self._publisher.publish(event)

    def __str__(self) -> str:
        return self._name

    def __repr__(self) -> str:
        desc = f", description='{self._description}'" if self._description else ""
        return f"Actor(name='{self._name}'{desc})"
description property

The Actor's role description.

name property

The Actor's name.

ability_to(ability_type)

Retrieve a previously granted Ability by type.

Parameters:

Name Type Description Default
ability_type type[T]

The class of the Ability to retrieve.

required

Returns:

Type Description
T

The Ability instance.

Raises:

Type Description
AbilityNotFoundError

If the Actor does not have this Ability.

Source code in src/screenwright/core/actor.py
def ability_to(self, ability_type: type[T]) -> T:
    """Retrieve a previously granted Ability by type.

    Args:
        ability_type: The class of the Ability to retrieve.

    Returns:
        The Ability instance.

    Raises:
        AbilityNotFoundError: If the Actor does not have this Ability.
    """
    ability = self._abilities.get(ability_type)  # type: ignore[arg-type]
    if ability is None:
        raise AbilityNotFoundError(self._name, ability_type.__name__)
    return ability  # type: ignore[return-value]
attempts_to(*performables)

Perform one or more Tasks or Interactions.

Parameters:

Name Type Description Default
performables Performable

The Performables to execute, in order.

()
Source code in src/screenwright/core/actor.py
def attempts_to(self, *performables: Performable) -> None:
    """Perform one or more Tasks or Interactions.

    Args:
        performables: The Performables to execute, in order.
    """
    for performable in performables:
        performable_name = _describe(performable)
        is_task = _is_task(performable)
        parent_id = self._current_parent_id

        if is_task:
            task_event = TaskStarted(
                actor_name=self._name,
                task_name=performable_name,
                parent_id=parent_id,
            )
            self._publish(task_event)
            self._event_stack.append(task_event.event_id)
        else:
            target = getattr(performable, "target", None)
            target_str = str(target) if target is not None else None
            interaction_event = InteractionStarted(
                actor_name=self._name,
                interaction_name=performable_name,
                target=target_str,
                parent_id=parent_id,
            )
            self._publish(interaction_event)
            self._event_stack.append(interaction_event.event_id)

        start_time = time.monotonic()
        try:
            performable.perform_as(self)
            duration_ms = (time.monotonic() - start_time) * 1000

            if is_task:
                self._publish(
                    TaskCompleted(
                        actor_name=self._name,
                        task_name=performable_name,
                        duration_ms=duration_ms,
                    )
                )
            else:
                self._publish(
                    InteractionCompleted(
                        actor_name=self._name,
                        interaction_name=performable_name,
                        duration_ms=duration_ms,
                    )
                )
        except Exception as exc:
            duration_ms = (time.monotonic() - start_time) * 1000

            if is_task:
                self._publish(
                    TaskFailed(
                        actor_name=self._name,
                        task_name=performable_name,
                        error_type=type(exc).__name__,
                        error_message=str(exc),
                        duration_ms=duration_ms,
                    )
                )
            else:
                self._publish(
                    InteractionFailed(
                        actor_name=self._name,
                        interaction_name=performable_name,
                        error_type=type(exc).__name__,
                        error_message=str(exc),
                        duration_ms=duration_ms,
                    )
                )
            raise
        finally:
            self._event_stack.pop()
has_ability_to(ability_type)

Check whether this Actor has a specific Ability.

Parameters:

Name Type Description Default
ability_type type[Ability]

The class of the Ability to check.

required

Returns:

Type Description
bool

True if the Actor has the Ability.

Source code in src/screenwright/core/actor.py
def has_ability_to(self, ability_type: type[Ability]) -> bool:
    """Check whether this Actor has a specific Ability.

    Args:
        ability_type: The class of the Ability to check.

    Returns:
        True if the Actor has the Ability.
    """
    return ability_type in self._abilities
named(name, *, description=None, publisher=None) staticmethod

Create a new Actor with the given name.

Parameters:

Name Type Description Default
name str

The Actor's name (e.g., "Ali").

required
description str | None

Optional role description (e.g., "a scrum master").

None
publisher EventPublisher | None

Optional EventPublisher for emitting domain events.

None

Returns:

Type Description
Actor

A new Actor instance.

Source code in src/screenwright/core/actor.py
@staticmethod
def named(
    name: str,
    *,
    description: str | None = None,
    publisher: EventPublisher | None = None,
) -> Actor:
    """Create a new Actor with the given name.

    Args:
        name: The Actor's name (e.g., "Ali").
        description: Optional role description (e.g., "a scrum master").
        publisher: Optional EventPublisher for emitting domain events.

    Returns:
        A new Actor instance.
    """
    return Actor(name, description=description, publisher=publisher)
recalls(fact_key)

Retrieve a previously remembered Fact.

Parameters:

Name Type Description Default
fact_key str

The key of the Fact to recall.

required

Returns:

Type Description
Any

The stored value.

Raises:

Type Description
FactNotFoundError

If the Actor does not remember this Fact.

Source code in src/screenwright/core/actor.py
def recalls(self, fact_key: str) -> Any:
    """Retrieve a previously remembered Fact.

    Args:
        fact_key: The key of the Fact to recall.

    Returns:
        The stored value.

    Raises:
        FactNotFoundError: If the Actor does not remember this Fact.
    """
    if fact_key not in self._facts:
        raise FactNotFoundError(self._name, fact_key)
    return self._facts[fact_key]
remembers(**facts)

Store Facts that this Actor knows.

Parameters:

Name Type Description Default
facts Any

Key-value pairs to remember.

{}

Returns:

Type Description
Actor

Self, for fluent chaining.

Source code in src/screenwright/core/actor.py
def remembers(self, **facts: Any) -> Actor:
    """Store Facts that this Actor knows.

    Args:
        facts: Key-value pairs to remember.

    Returns:
        Self, for fluent chaining.
    """
    for key, value in facts.items():
        self._facts[key] = value
        self._publish(
            FactRemembered(
                actor_name=self._name,
                fact_key=key,
                fact_value_summary=summarize_fact_value(value),
            )
        )
    return self
should_see_that(question, matcher)

Ask a Question and verify the answer.

Parameters:

Name Type Description Default
question Answerable[T]

The Answerable to query.

required
matcher Callable[[T], bool] | T

Either a callable predicate or an expected value for equality comparison.

required

Raises:

Type Description
AssertionError

If the matcher/predicate fails.

Source code in src/screenwright/core/actor.py
def should_see_that(
    self,
    question: Answerable[T],
    matcher: Callable[[T], bool] | T,
) -> None:
    """Ask a Question and verify the answer.

    Args:
        question: The Answerable to query.
        matcher: Either a callable predicate or an expected value
                 for equality comparison.

    Raises:
        AssertionError: If the matcher/predicate fails.
    """
    question_name = _describe(question)
    parent_id = self._current_parent_id

    asked_event = QuestionAsked(
        actor_name=self._name,
        question_name=question_name,
        parent_id=parent_id,
    )
    self._publish(asked_event)
    self._event_stack.append(asked_event.event_id)

    start_time = time.monotonic()
    try:
        answer = question.answered_by(self)
        duration_ms = (time.monotonic() - start_time) * 1000

        self._publish(
            QuestionAnswered(
                actor_name=self._name,
                question_name=question_name,
                answer_summary=summarize_fact_value(answer),
                duration_ms=duration_ms,
            )
        )

        if callable(matcher) and not isinstance(matcher, (str, int, float, bool)):
            if not matcher(answer):
                raise AssertionError(
                    f"{self._name} expected {question_name} to match, "
                    f"but got: {answer!r}"
                )
        elif answer != matcher:
            raise AssertionError(
                f"{self._name} expected {question_name} to be "
                f"{matcher!r}, but got: {answer!r}"
            )
    finally:
        self._event_stack.pop()
who_can(*abilities)

Grant Abilities to this Actor.

Parameters:

Name Type Description Default
abilities Ability

One or more Abilities to grant.

()

Returns:

Type Description
Actor

Self, for fluent chaining.

Source code in src/screenwright/core/actor.py
def who_can(self, *abilities: Ability) -> Actor:
    """Grant Abilities to this Actor.

    Args:
        abilities: One or more Abilities to grant.

    Returns:
        Self, for fluent chaining.
    """
    for ability in abilities:
        self._abilities[type(ability)] = ability
        self._publish(
            AbilityGranted(
                actor_name=self._name,
                ability_name=str(ability),
                ability_type=type(ability).__name__,
            )
        )
    return self

answerable

Answerable protocol -- the common interface for Questions.

Answerable

Bases: Protocol[T]

Anything an Actor can ask that returns a value.

Questions implement this protocol. The Actor.should_see_that() method accepts Answerables.

Source code in src/screenwright/core/answerable.py
@runtime_checkable
class Answerable(Protocol[T]):
    """Anything an Actor can ask that returns a value.

    Questions implement this protocol.
    The Actor.should_see_that() method accepts Answerables.
    """

    def answered_by(self, actor: Actor) -> T:
        """Answer this question as the given Actor.

        Args:
            actor: The Actor asking this question.

        Returns:
            The answer to the question.
        """
        ...
answered_by(actor)

Answer this question as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor asking this question.

required

Returns:

Type Description
T

The answer to the question.

Source code in src/screenwright/core/answerable.py
def answered_by(self, actor: Actor) -> T:
    """Answer this question as the given Actor.

    Args:
        actor: The Actor asking this question.

    Returns:
        The answer to the question.
    """
    ...

event_publisher

EventPublisher protocol -- output port for domain event emission.

EventPublisher

Bases: Protocol

Output port for publishing domain events.

The core domain calls publish() to emit events. The adapter/collection layer provides the concrete implementation.

Source code in src/screenwright/core/event_publisher.py
class EventPublisher(Protocol):
    """Output port for publishing domain events.

    The core domain calls publish() to emit events.
    The adapter/collection layer provides the concrete implementation.
    """

    def publish(self, event: Event) -> None:
        """Publish a domain event.

        Args:
            event: The event to publish.
        """
        ...
publish(event)

Publish a domain event.

Parameters:

Name Type Description Default
event Event

The event to publish.

required
Source code in src/screenwright/core/event_publisher.py
def publish(self, event: Event) -> None:
    """Publish a domain event.

    Args:
        event: The event to publish.
    """
    ...

NullEventPublisher

An EventPublisher that discards all events.

Used as the default when no publisher is configured, and in unit tests where event collection is not needed.

Source code in src/screenwright/core/event_publisher.py
class NullEventPublisher:
    """An EventPublisher that discards all events.

    Used as the default when no publisher is configured,
    and in unit tests where event collection is not needed.
    """

    def publish(self, event: Event) -> None:
        """Discard the event."""
publish(event)

Discard the event.

Source code in src/screenwright/core/event_publisher.py
def publish(self, event: Event) -> None:
    """Discard the event."""

exceptions

Domain exceptions for the Screenplay Engine.

AbilityNotFoundError

Bases: ScreenwrightError

Raised when an Actor does not have a required Ability.

This typically means the Actor was not granted the Ability before attempting an Interaction that requires it.

Source code in src/screenwright/core/exceptions.py
class AbilityNotFoundError(ScreenwrightError):
    """Raised when an Actor does not have a required Ability.

    This typically means the Actor was not granted the Ability
    before attempting an Interaction that requires it.
    """

    def __init__(self, actor_name: str, ability_type: str) -> None:
        self.actor_name = actor_name
        self.ability_type = ability_type
        super().__init__(
            f"Actor '{actor_name}' does not have the ability '{ability_type}'. "
            f"Grant it with: actor.who_can({ability_type}(...))"
        )

FactNotFoundError

Bases: ScreenwrightError

Raised when an Actor does not remember a requested Fact.

Source code in src/screenwright/core/exceptions.py
class FactNotFoundError(ScreenwrightError):
    """Raised when an Actor does not remember a requested Fact."""

    def __init__(self, actor_name: str, fact_key: str) -> None:
        self.actor_name = actor_name
        self.fact_key = fact_key
        super().__init__(
            f"Actor '{actor_name}' does not remember a fact named '{fact_key}'. "
            f"Store it with: actor.remembers({fact_key}=value)"
        )

NoActorInSpotlightError

Bases: ScreenwrightError

Raised when the Spotlight is empty but an Actor is needed.

Source code in src/screenwright/core/exceptions.py
class NoActorInSpotlightError(ScreenwrightError):
    """Raised when the Spotlight is empty but an Actor is needed."""

    def __init__(self) -> None:
        super().__init__(
            "No Actor is currently in the Spotlight. "
            "Use stage.shine_spotlight_on(actor) to set one."
        )

ScreenwrightError

Bases: Exception

Base exception for all Screenwright errors.

Source code in src/screenwright/core/exceptions.py
class ScreenwrightError(Exception):
    """Base exception for all Screenwright errors."""

fact

Fact type alias and helpers for Actor memory.

summarize_fact_value(value, max_length=50)

Create a safe summary of a fact value for event emission.

Truncates long strings and avoids exposing sensitive data in full.

Parameters:

Name Type Description Default
value Any

The fact value to summarize.

required
max_length int

Maximum length of the summary string.

50

Returns:

Type Description
str

A truncated string representation.

Source code in src/screenwright/core/fact.py
def summarize_fact_value(value: Any, max_length: int = 50) -> str:
    """Create a safe summary of a fact value for event emission.

    Truncates long strings and avoids exposing sensitive data in full.

    Args:
        value: The fact value to summarize.
        max_length: Maximum length of the summary string.

    Returns:
        A truncated string representation.
    """
    text = repr(value)
    if len(text) > max_length:
        return text[: max_length - 3] + "..."
    return text

interaction

Interaction -- low-level, atomic actions an Actor performs.

Interaction

Base class for class-based Interactions.

Interactions are the leaf nodes of the Screenplay action tree. They directly manipulate the system under test through an Ability.

Subclass and implement perform_as(): class Click(Interaction): def init(self, target: Target) -> None: self.target = target

    @staticmethod
    def on(target: Target) -> Click:
        return Click(target)

    def perform_as(self, actor: Actor) -> None:
        driver = actor.ability_to(BrowseTheWeb).driver
        driver.find_element(*self.target.locator).click()
Source code in src/screenwright/core/interaction.py
class Interaction:
    """Base class for class-based Interactions.

    Interactions are the leaf nodes of the Screenplay action tree.
    They directly manipulate the system under test through an Ability.

    Subclass and implement perform_as():
        class Click(Interaction):
            def __init__(self, target: Target) -> None:
                self.target = target

            @staticmethod
            def on(target: Target) -> Click:
                return Click(target)

            def perform_as(self, actor: Actor) -> None:
                driver = actor.ability_to(BrowseTheWeb).driver
                driver.find_element(*self.target.locator).click()
    """

    _is_task: bool = False

    def perform_as(self, actor: Actor) -> None:
        """Perform this interaction as the given Actor.

        Args:
            actor: The Actor performing this interaction.
        """
        raise NotImplementedError(
            f"{type(self).__name__} must implement perform_as()"
        )

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)
__str__()

Human-readable name for narration.

Source code in src/screenwright/core/interaction.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)
perform_as(actor)

Perform this interaction as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this interaction.

required
Source code in src/screenwright/core/interaction.py
def perform_as(self, actor: Actor) -> None:
    """Perform this interaction as the given Actor.

    Args:
        actor: The Actor performing this interaction.
    """
    raise NotImplementedError(
        f"{type(self).__name__} must implement perform_as()"
    )

performable

Performable protocol -- the common interface for Tasks and Interactions.

Performable

Bases: Protocol

Anything an Actor can perform.

Both Tasks and Interactions implement this protocol. The Actor.attempts_to() method accepts Performables.

Source code in src/screenwright/core/performable.py
@runtime_checkable
class Performable(Protocol):
    """Anything an Actor can perform.

    Both Tasks and Interactions implement this protocol.
    The Actor.attempts_to() method accepts Performables.
    """

    def perform_as(self, actor: Actor) -> None:
        """Perform this action as the given Actor.

        Args:
            actor: The Actor performing this action.
        """
        ...
perform_as(actor)

Perform this action as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this action.

required
Source code in src/screenwright/core/performable.py
def perform_as(self, actor: Actor) -> None:
    """Perform this action as the given Actor.

    Args:
        actor: The Actor performing this action.
    """
    ...

question

Question -- queries about the observable state of the system.

Question

Bases: Generic[T]

Base class for class-based Questions.

Subclass and implement answered_by(): class TextOf(Question[str]): def init(self, target: Target) -> None: self.target = target

    def answered_by(self, actor: Actor) -> str:
        driver = actor.ability_to(BrowseTheWeb).driver
        return driver.find_element(*self.target.locator).text
Source code in src/screenwright/core/question.py
class Question(Generic[T]):
    """Base class for class-based Questions.

    Subclass and implement answered_by():
        class TextOf(Question[str]):
            def __init__(self, target: Target) -> None:
                self.target = target

            def answered_by(self, actor: Actor) -> str:
                driver = actor.ability_to(BrowseTheWeb).driver
                return driver.find_element(*self.target.locator).text
    """

    def answered_by(self, actor: Actor) -> T:
        """Answer this question as the given Actor.

        Args:
            actor: The Actor asking this question.

        Returns:
            The answer.
        """
        raise NotImplementedError(
            f"{type(self).__name__} must implement answered_by()"
        )

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)
__str__()

Human-readable name for narration.

Source code in src/screenwright/core/question.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)
answered_by(actor)

Answer this question as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor asking this question.

required

Returns:

Type Description
T

The answer.

Source code in src/screenwright/core/question.py
def answered_by(self, actor: Actor) -> T:
    """Answer this question as the given Actor.

    Args:
        actor: The Actor asking this question.

    Returns:
        The answer.
    """
    raise NotImplementedError(
        f"{type(self).__name__} must implement answered_by()"
    )

question(func)

Decorator to create a Question from a function.

The decorated function receives the Actor as its first argument and returns a value:

@question
def current_url(actor: Actor) -> str:
    return actor.ability_to(BrowseTheWeb).driver.current_url
Usage

actor.should_see_that(current_url(), is_equal_to("https://example.com"))

Parameters:

Name Type Description Default
func Callable[..., Any]

The function to wrap as a Question.

required

Returns:

Type Description
Callable[..., _FunctionalQuestion[Any]]

A factory function that creates an Answerable Question.

Source code in src/screenwright/core/question.py
def question(func: Callable[..., Any]) -> Callable[..., _FunctionalQuestion[Any]]:
    """Decorator to create a Question from a function.

    The decorated function receives the Actor as its first argument
    and returns a value:

        @question
        def current_url(actor: Actor) -> str:
            return actor.ability_to(BrowseTheWeb).driver.current_url

    Usage:
        actor.should_see_that(current_url(), is_equal_to("https://example.com"))

    Args:
        func: The function to wrap as a Question.

    Returns:
        A factory function that creates an Answerable Question.
    """

    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> _FunctionalQuestion[Any]:
        return _FunctionalQuestion(func, args, kwargs)

    return wrapper

scene

Scene -- a single test scenario execution.

Scene dataclass

A single test scenario viewed through the theatre metaphor.

A Scene begins when a scenario starts and ends when it completes. During a Scene, Actors enter the Stage, perform their parts, and exit.

Attributes:

Name Type Description
name str

The scenario name.

feature_name str

The feature this scene belongs to.

tags list[str]

BDD tags associated with this scenario.

Source code in src/screenwright/core/scene.py
@dataclass
class Scene:
    """A single test scenario viewed through the theatre metaphor.

    A Scene begins when a scenario starts and ends when it completes.
    During a Scene, Actors enter the Stage, perform their parts, and exit.

    Attributes:
        name: The scenario name.
        feature_name: The feature this scene belongs to.
        tags: BDD tags associated with this scenario.
    """

    name: str
    feature_name: str = ""
    tags: list[str] = field(default_factory=list)

spotlight

Spotlight -- tracks the currently active Actor on the Stage.

Spotlight

Tracks the currently active Actor.

The Spotlight is managed by the Stage. When a Scene involves multiple Actors, the Spotlight shifts to indicate who is currently acting.

Source code in src/screenwright/core/spotlight.py
class Spotlight:
    """Tracks the currently active Actor.

    The Spotlight is managed by the Stage. When a Scene involves
    multiple Actors, the Spotlight shifts to indicate who is
    currently acting.
    """

    def __init__(self) -> None:
        self._current: Actor | None = None

    @property
    def current(self) -> Actor:
        """The Actor currently in the Spotlight.

        Raises:
            NoActorInSpotlightError: If no Actor is in the Spotlight.
        """
        if self._current is None:
            raise NoActorInSpotlightError()
        return self._current

    @property
    def has_actor(self) -> bool:
        """Whether any Actor is currently in the Spotlight."""
        return self._current is not None

    def shine_on(self, actor: Actor) -> Actor | None:
        """Shift the Spotlight to a new Actor.

        Args:
            actor: The Actor to place in the Spotlight.

        Returns:
            The previous Actor, or None if the Spotlight was empty.
        """
        previous = self._current
        self._current = actor
        return previous

    def clear(self) -> None:
        """Remove any Actor from the Spotlight."""
        self._current = None
current property

The Actor currently in the Spotlight.

Raises:

Type Description
NoActorInSpotlightError

If no Actor is in the Spotlight.

has_actor property

Whether any Actor is currently in the Spotlight.

clear()

Remove any Actor from the Spotlight.

Source code in src/screenwright/core/spotlight.py
def clear(self) -> None:
    """Remove any Actor from the Spotlight."""
    self._current = None
shine_on(actor)

Shift the Spotlight to a new Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor to place in the Spotlight.

required

Returns:

Type Description
Actor | None

The previous Actor, or None if the Spotlight was empty.

Source code in src/screenwright/core/spotlight.py
def shine_on(self, actor: Actor) -> Actor | None:
    """Shift the Spotlight to a new Actor.

    Args:
        actor: The Actor to place in the Spotlight.

    Returns:
        The previous Actor, or None if the Spotlight was empty.
    """
    previous = self._current
    self._current = actor
    return previous

stage

Stage -- manages Actor lifecycle and the Spotlight for a test session.

Stage

The environment in which Actors perform during a test session.

The Stage manages the lifecycle of Actors, determines which Actor is in the Spotlight, and coordinates Scene (scenario) boundaries.

Attributes:

Name Type Description
publisher

The EventPublisher used to emit domain events.

Source code in src/screenwright/core/stage.py
class Stage:
    """The environment in which Actors perform during a test session.

    The Stage manages the lifecycle of Actors, determines which Actor
    is in the Spotlight, and coordinates Scene (scenario) boundaries.

    Attributes:
        publisher: The EventPublisher used to emit domain events.
    """

    def __init__(self, publisher: EventPublisher | None = None) -> None:
        self._publisher: EventPublisher = publisher or NullEventPublisher()
        self._spotlight = Spotlight()
        self._actors: dict[str, Actor] = {}
        self._current_scene: Scene | None = None

    @property
    def current_scene(self) -> Scene | None:
        """The currently active Scene, or None."""
        return self._current_scene

    def new_scene(
        self, name: str, feature_name: str = "", tags: list[str] | None = None
    ) -> Scene:
        """Start a new Scene (test scenario).

        Clears any existing Actors from the previous Scene.

        Args:
            name: The scenario name.
            feature_name: The feature this scenario belongs to.
            tags: BDD tags for the scenario.

        Returns:
            The new Scene.
        """
        self.clear()
        self._current_scene = Scene(
            name=name,
            feature_name=feature_name,
            tags=tags or [],
        )
        return self._current_scene

    def actor_named(self, name: str, *, description: str | None = None) -> Actor:
        """Get or create an Actor on this Stage.

        If an Actor with this name already exists, returns them.
        Otherwise, creates a new Actor, places them on Stage,
        and shifts the Spotlight to them.

        Args:
            name: The Actor's name.
            description: Optional role description.

        Returns:
            The Actor (new or existing).
        """
        if name in self._actors:
            actor = self._actors[name]
            self.shine_spotlight_on(actor)
            return actor

        actor = Actor(name, description=description, publisher=self._publisher)
        self._actors[name] = actor

        self._publisher.publish(
            ActorEnteredStage(
                actor_name=name,
                description=description,
            )
        )

        self.shine_spotlight_on(actor)
        return actor

    def in_the_spotlight(self) -> Actor:
        """The Actor currently in the Spotlight.

        Raises:
            NoActorInSpotlightError: If no Actor is in the Spotlight.
        """
        return self._spotlight.current

    def shine_spotlight_on(self, actor: Actor) -> None:
        """Shift the Spotlight to a specific Actor.

        Args:
            actor: The Actor to illuminate.
        """
        previous = self._spotlight.shine_on(actor)
        previous_name = previous.name if previous is not None else None

        if previous is not actor:
            self._publisher.publish(
                SpotlightChanged(
                    previous_actor=previous_name,
                    current_actor=actor.name,
                )
            )

    def clear(self) -> None:
        """Remove all Actors from the Stage and clear the Spotlight.

        Called at the end of each Scene (scenario).
        """
        for actor_name in list(self._actors.keys()):
            self._publisher.publish(ActorExitedStage(actor_name=actor_name))
        self._actors.clear()
        self._spotlight.clear()
        self._current_scene = None

    @property
    def actors(self) -> dict[str, Actor]:
        """All Actors currently on Stage."""
        return dict(self._actors)
actors property

All Actors currently on Stage.

current_scene property

The currently active Scene, or None.

actor_named(name, *, description=None)

Get or create an Actor on this Stage.

If an Actor with this name already exists, returns them. Otherwise, creates a new Actor, places them on Stage, and shifts the Spotlight to them.

Parameters:

Name Type Description Default
name str

The Actor's name.

required
description str | None

Optional role description.

None

Returns:

Type Description
Actor

The Actor (new or existing).

Source code in src/screenwright/core/stage.py
def actor_named(self, name: str, *, description: str | None = None) -> Actor:
    """Get or create an Actor on this Stage.

    If an Actor with this name already exists, returns them.
    Otherwise, creates a new Actor, places them on Stage,
    and shifts the Spotlight to them.

    Args:
        name: The Actor's name.
        description: Optional role description.

    Returns:
        The Actor (new or existing).
    """
    if name in self._actors:
        actor = self._actors[name]
        self.shine_spotlight_on(actor)
        return actor

    actor = Actor(name, description=description, publisher=self._publisher)
    self._actors[name] = actor

    self._publisher.publish(
        ActorEnteredStage(
            actor_name=name,
            description=description,
        )
    )

    self.shine_spotlight_on(actor)
    return actor
clear()

Remove all Actors from the Stage and clear the Spotlight.

Called at the end of each Scene (scenario).

Source code in src/screenwright/core/stage.py
def clear(self) -> None:
    """Remove all Actors from the Stage and clear the Spotlight.

    Called at the end of each Scene (scenario).
    """
    for actor_name in list(self._actors.keys()):
        self._publisher.publish(ActorExitedStage(actor_name=actor_name))
    self._actors.clear()
    self._spotlight.clear()
    self._current_scene = None
in_the_spotlight()

The Actor currently in the Spotlight.

Raises:

Type Description
NoActorInSpotlightError

If no Actor is in the Spotlight.

Source code in src/screenwright/core/stage.py
def in_the_spotlight(self) -> Actor:
    """The Actor currently in the Spotlight.

    Raises:
        NoActorInSpotlightError: If no Actor is in the Spotlight.
    """
    return self._spotlight.current
new_scene(name, feature_name='', tags=None)

Start a new Scene (test scenario).

Clears any existing Actors from the previous Scene.

Parameters:

Name Type Description Default
name str

The scenario name.

required
feature_name str

The feature this scenario belongs to.

''
tags list[str] | None

BDD tags for the scenario.

None

Returns:

Type Description
Scene

The new Scene.

Source code in src/screenwright/core/stage.py
def new_scene(
    self, name: str, feature_name: str = "", tags: list[str] | None = None
) -> Scene:
    """Start a new Scene (test scenario).

    Clears any existing Actors from the previous Scene.

    Args:
        name: The scenario name.
        feature_name: The feature this scenario belongs to.
        tags: BDD tags for the scenario.

    Returns:
        The new Scene.
    """
    self.clear()
    self._current_scene = Scene(
        name=name,
        feature_name=feature_name,
        tags=tags or [],
    )
    return self._current_scene
shine_spotlight_on(actor)

Shift the Spotlight to a specific Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor to illuminate.

required
Source code in src/screenwright/core/stage.py
def shine_spotlight_on(self, actor: Actor) -> None:
    """Shift the Spotlight to a specific Actor.

    Args:
        actor: The Actor to illuminate.
    """
    previous = self._spotlight.shine_on(actor)
    previous_name = previous.name if previous is not None else None

    if previous is not actor:
        self._publisher.publish(
            SpotlightChanged(
                previous_actor=previous_name,
                current_actor=actor.name,
            )
        )

task

Task -- high-level, business-meaningful goals an Actor performs.

Task

Base class for class-based Tasks.

Subclass and implement perform_as(): class Login(Task): def init(self, username: str, password: str) -> None: self.username = username self.password = password

    def perform_as(self, actor: Actor) -> None:
        actor.attempts_to(
            Navigate.to("/login"),
            Enter.the_value(self.username).into(USERNAME_FIELD),
            Enter.the_value(self.password).into(PASSWORD_FIELD),
            Click.on(LOGIN_BUTTON),
        )
Source code in src/screenwright/core/task.py
class Task:
    """Base class for class-based Tasks.

    Subclass and implement perform_as():
        class Login(Task):
            def __init__(self, username: str, password: str) -> None:
                self.username = username
                self.password = password

            def perform_as(self, actor: Actor) -> None:
                actor.attempts_to(
                    Navigate.to("/login"),
                    Enter.the_value(self.username).into(USERNAME_FIELD),
                    Enter.the_value(self.password).into(PASSWORD_FIELD),
                    Click.on(LOGIN_BUTTON),
                )
    """

    _is_task: bool = True

    def perform_as(self, actor: Actor) -> None:
        """Perform this task as the given Actor.

        Args:
            actor: The Actor performing this task.
        """
        raise NotImplementedError(f"{type(self).__name__} must implement perform_as()")

    def __str__(self) -> str:
        """Human-readable name for narration."""
        name = type(self).__name__
        result: list[str] = []
        for char in name:
            if char.isupper() and result:
                result.append(" ")
            result.append(char)
        return "".join(result)
__str__()

Human-readable name for narration.

Source code in src/screenwright/core/task.py
def __str__(self) -> str:
    """Human-readable name for narration."""
    name = type(self).__name__
    result: list[str] = []
    for char in name:
        if char.isupper() and result:
            result.append(" ")
        result.append(char)
    return "".join(result)
perform_as(actor)

Perform this task as the given Actor.

Parameters:

Name Type Description Default
actor Actor

The Actor performing this task.

required
Source code in src/screenwright/core/task.py
def perform_as(self, actor: Actor) -> None:
    """Perform this task as the given Actor.

    Args:
        actor: The Actor performing this task.
    """
    raise NotImplementedError(f"{type(self).__name__} must implement perform_as()")

task(func)

Decorator to create a Task from a function.

The decorated function receives the Actor as its first argument:

@task
def login(actor: Actor, username: str, password: str) -> None:
    actor.attempts_to(
        Navigate.to("/login"),
        Enter.the_value(username).into(USERNAME_FIELD),
    )
Usage

actor.attempts_to(login(username="admin", password="secret"))

Parameters:

Name Type Description Default
func Callable[..., Any]

The function to wrap as a Task.

required

Returns:

Type Description
Callable[..., _FunctionalTask]

A factory function that creates a Performable Task.

Source code in src/screenwright/core/task.py
def task(func: Callable[..., Any]) -> Callable[..., _FunctionalTask]:
    """Decorator to create a Task from a function.

    The decorated function receives the Actor as its first argument:

        @task
        def login(actor: Actor, username: str, password: str) -> None:
            actor.attempts_to(
                Navigate.to("/login"),
                Enter.the_value(username).into(USERNAME_FIELD),
            )

    Usage:
        actor.attempts_to(login(username="admin", password="secret"))

    Args:
        func: The function to wrap as a Task.

    Returns:
        A factory function that creates a Performable Task.
    """

    @functools.wraps(func)
    def wrapper(*args: Any, **kwargs: Any) -> _FunctionalTask:
        return _FunctionalTask(func, args, kwargs)

    return wrapper