Chapter 13Lesson 01~180 minutes

Page Object, Page Component, Screenplay, and Test Abstraction Patterns: Core Concepts and Mental Model

As suites grow, raw Selenium code can remain technically correct while becoming expensive to change. This lesson establishes abstraction boundaries so tests speak in user intent, UI mechanics stay localized, assertions remain diagnostically useful, and browser/session infrastructure does not leak into every page class.

Page ObjectPage ComponentScreenplayResponsibilitiesCI maintainability

Learning objectives

  • Explain the responsibility chain from test intent through task/page/component abstractions to WebDriver and the AUT.
  • Define Page Object, Page Component, task/DSL, and Screenplay-style actor/task/question structures before using them.
  • Place locators, waits, assertions, fixtures, browser creation, and evidence collection in deliberate layers.
  • Explain why page objects expose page services rather than raw DOM mechanics or a globally shared driver.
  • Use read-only inspection to prove session/page state before abstraction methods mutate the AUT.
  • Connect abstraction design to CI maintainability, browser matrices, failure triage, and change blast radius.

1. The practical problem: duplication becomes operational debt

Chapters 1–12 built reliable browser mechanics: sessions, locators, waits, contexts, actions, dialogs, modern DOMs, files/state, and browser configuration. A suite can use all of those correctly and still become fragile if every test repeats selectors, waits, navigation rules, screenshots, and business workflows.

The abstraction problem is therefore not “how do I hide Selenium?” It is “which code should know about which kind of change?” A renamed locator should not force fifty scenario edits. A new browser option should not require changing every page object. A failed business assertion should still tell the test author what outcome was wrong.

Architectural goal: localize UI mechanics without hiding failure meaning. Abstraction is successful when it reduces the number of places that must change while preserving observable cause and state.

2. Mental model: intent flows down; evidence flows back up

Responsibilities flow from intent toward browser mechanics

The following diagram visualizes the relationships described in Mental model: intent flows down; evidence flows back up. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.

flowchart TD
  T[Test scenario intent] --> K[Task / domain action]
  T --> Q[Assertion / question]
  K --> P[Page Object service]
  P --> C[Page Component]
  P --> L[Locator + wait policy]
  C --> L
  L --> W[WebDriver]
  W --> B[Browser context]
  B --> A[AUT DOM / application state]
  A --> Q
  Q --> T
  I[Test infrastructure] --> W
  I --> E[Evidence / teardown]
  E --> T

The scenario names the behavior that matters. A task or domain action may coordinate several page services. A Page Object represents services offered by one page or view. Page Components model reusable regions contained by a page. Locators and explicit waits are mechanics inside those boundaries. WebDriver still owns the browser session; infrastructure owns driver creation, evidence directories, and teardown.

Assertions and “questions” flow in the other direction: the abstraction exposes observable state and the test decides whether that state satisfies the scenario.

3. Terms and boundaries

The following table organizes the key choices and evidence for Terms and boundaries. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Term Meaning in this chapter Should not become
Page Object Object that exposes services/observable state for one page or coherent view. a bucket for every workflow, assertion, driver option, and screenshot
Page Component Object for a reusable region such as a product card, navbar, row, or modal. a second giant page class
Task/domain action Intent-oriented operation such as “add product to cart” that may coordinate page services. an opaque retry/sleep wrapper
Question/state query Readable retrieval of observable state used by a test assertion. an assertion whose failure context is hidden deep in helpers
Screenplay-style model Optional actor → ability → task → question vocabulary for scaling intent-oriented tests. a Selenium API or mandatory framework
Fixture/infrastructure Driver lifecycle, server/base URL, evidence path, test data setup/teardown. page-object responsibility

4. Page Objects model services, not HTML trivia

Selenium’s current Page Object guidance treats the object as the test-facing interface to a page. Public methods should describe what the page offers: filter_products("Cable"), product_named("Fiber Cable"), or cart_count(). The object may know locators and wait conditions, because those are implementation details the scenario should not repeat.

class CatalogPage:
    FILTER = (By.CSS_SELECTOR, "[data-testid='filter']")
    CART_COUNT = (By.ID, "cart-count")

    def filter_products(self, text: str) -> None:
        field = self._driver.find_element(*self.FILTER)
        field.clear()
        field.send_keys(text)

    def cart_count(self) -> int:
        return int(self.driver.find_element(*self.CART_COUNT).text)

The methods are not “clickFilterField” and “readStrongTag.” They expose application-oriented services/state while keeping DOM details private.

5. Assertion placement: preserve failure meaning

The Selenium project’s Page Object guidance generally keeps test assertions in the test, with a narrow exception for validating that a newly constructed page object is actually on the page it claims to represent. This keeps “what outcome should be true?” close to the scenario while allowing the page object to throw a clear contract error if its basic page precondition is absent.

Responsibility Good location Example
Page loaded/contract precondition Page Object constructor/factory wait for the catalog heading or stable root
Business expectation test/scenario assert page.cart_count() == 1
Reusable observable query Page/Page Component product.price_text()
Evidence on failure test runner/infrastructure hook screenshot + URL + capabilities

6. Page Components use composition

Real pages are composed of regions. A catalog contains product cards; a dashboard contains widgets; a table contains rows. The page can return component objects scoped to those regions. This keeps a product card’s name/price/add-button locators out of the page class and avoids an inheritance tree that tries to mirror DOM nesting.

class ProductCard:
    NAME = (By.CSS_SELECTOR, "[data-testid='product-name']")
    PRICE = (By.CSS_SELECTOR, "[data-testid='product-price']")
    ADD = (By.CSS_SELECTOR, "[data-testid='add']")

    def __init__(self, root):
        self.root = root

    def name(self) -> str:
        return self.root.find_element(*self.NAME).text

    def price_text(self) -> str:
        return self.root.find_element(*self.PRICE).text

    def add(self) -> None:
        self.root.find_element(*self.ADD).click()

A component root is still a WebElement reference and can become stale after rerender. The page should reacquire components when the DOM changes rather than caching them for the life of the session.

7. Task-oriented and Screenplay-style layers

A task layer becomes useful when the same business action spans page/component services or must read like domain language. A Screenplay-style design names an actor, the actor’s browser ability, tasks the actor performs, and questions the actor asks. This chapter uses a small framework-free sketch so learners can understand the architecture without adding a dependency.

class AddProductToCart:
    def __init__(self, product_name: str):
        self.product_name = product_name

    def perform(self, catalog: "CatalogPage") -> None:
        catalog.product_named(self.product_name).add()

class CartCount:
    def answered_by(self, catalog: "CatalogPage") -> int:
        return catalog.cart_count()

The key distinction is responsibility, not naming. The task coordinates intent; the page/component owns UI mechanics; the test owns the expectation.

8. Read-only state inspection before mutation

The following example makes the Read-only state inspection before mutation behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

import selenium
from selenium import webdriver
from selenium.webdriver.common.by import By

URL = "http://127.0.0.1:8782/"
driver = webdriver.Chrome()
try:
    driver.get(URL)
    print({
        "selenium": selenium.__version__,
        "session_id": driver.session_id,
        "browser": driver.capabilities.get("browserName"),
        "browser_version": driver.capabilities.get("browserVersion"),
        "url": driver.current_url,
        "title": driver.title,
        "products": len(driver.find_elements(By.CSS_SELECTOR, "[data-testid='product-card']")),
        "cart": driver.find_element(By.ID, "cart-count").text,
    })
finally:
    driver.quit()

This inspection proves which session/page/DOM state exists before any abstraction changes it. The page object does not create the browser; it receives a session whose provenance and teardown remain infrastructure concerns.

9. Keep state stores distinct

The following table organizes the key choices and evidence for Keep state stores distinct. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

State store Owner Abstraction rule
WebDriver session/profile test infrastructure create/quit outside page classes
AUT DOM/view state browser + application page/component methods observe or mutate through WebDriver
test data fixture/setup layer do not hide shared mutable records inside page objects
assertion expectation test/scenario keep failure meaning visible
screenshots/logs/report files evidence layer capture around failures with test/session correlation
Grid/CI configuration execution infrastructure never make a page object decide node/browser policy

10. Common misconceptions

The following table organizes the key choices and evidence for Common misconceptions. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Misconception Why it fails Better boundary
“One BasePage should contain everything.” It becomes a God object and couples unrelated pages to every helper change. small shared primitives only; compose page/components
“Store WebElements once for speed.” Modern rerenders invalidate node references. store locators; reacquire at the action/query boundary
“Assertions in helpers make tests shorter.” Failures become detached from scenario meaning. return observable state; assert in test
“Screenplay replaces Page Objects.” They solve different levels of abstraction and can coexist. tasks/questions may delegate to page/component services
“Page Object is a Selenium Python class.” It is a design pattern, not a required binding base class. plain Python classes are sufficient

11. Why this matters in DevOps

Cross-browser CI magnifies maintenance cost: every browser/version/environment can expose slightly different timing and layout behavior. Centralized locator/wait contracts reduce duplicated fixes, while visible test assertions and infrastructure evidence keep diagnosis explainable. Good abstractions also make code review meaningful: a browser-policy change belongs in infrastructure, a locator change in a page/component, and a business expectation change in the scenario.

12. Summary and next step

Page Objects localize page services and mechanics; Page Components localize reusable regions; task/DSL layers express domain actions; Screenplay-style actor/task/question structure is optional; assertions normally remain in tests; browser/test infrastructure remains outside page classes.

Knowledge check

Why should a Page Object expose “services” rather than raw DOM details?

Where should a business assertion normally live?

Why prefer composition for Page Components?

Is Screenplay a Selenium WebDriver feature?

Which layer should create and quit WebDriver?

Next lesson

Refactor duplication into explicit layers

Lesson 2 starts from a small duplicated catalog suite, introduces one Page Object and one Page Component, then adds a task abstraction and a framework-free Screenplay-style sketch.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current Selenium primary documentation on 2026-08-28. Mandatory examples pin Selenium Python 4.47.0 and Python 3.10+, use a supported locally installed Chromium-family browser with Selenium Manager, and target only loopback fixtures. Page Object, Page Component, task/DSL, and Screenplay-style structures are test-architecture patterns rather than capabilities negotiated with the browser. The mandatory path uses Python standard-library unittest for the small suite so no external test-architecture framework is required. The Screenplay-style example is intentionally framework-free and remains an organizational layer over ordinary Selenium WebDriver.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.