liveBook · Manning

Companion Page Objects

As explained in BDD in Action, Second Edition

Companion Page Objects are reusable Screenplay-style page-object abstractions published alongside UI components to encapsulate interactions and enable test-code reuse.

Companion Page Objects package reusable test interactions with UI widgets and ship beside the widgets, helping teams discover and share automation without duplicating component knowledge. section 15.2.4 section 15.2.8

A practical design combines familiar Page Object structure with Screenplay-style composition:

  • Inject the widget’s host element so callers can reuse the abstraction in different contexts. section 15.2.4
  • Expose methods that return tasks for interactions and question adapters for widget state, rather than returning only raw element operations. section 15.2.4
  • Publish the test code with the widget module under a separate entry point, keeping shared test dependencies away from production code. section 15.2.8

A toaster companion

A Toaster companion can locate a toast message, derive its status from CSS classes, and provide a dismissMessage task that waits for the message, clicks it, and waits for it to disappear. Higher-level tests can compose these tasks and questions without repeating the widget’s selectors or interaction details. section 15.2.4

Listing 15.10 Companion Page Objects
import { By, Click, CssClasses, PageElement } from '@serenity-js/web'
import { QuestionAdapter, Task, Wait } from '@serenity-js/core'
import { isPresent, not } from '@serenity-js/assertions'
 
export class Toaster {
  constructor(private readonly hostElement: QuestionAdapter<PageElement>) {}
 
  message = () =>
    PageElement.located(By.css(`.toast-message`))
      .of(this.hostElement).describedAs('message');
 
  status = () =>
    CssClasses.of(this.hostElement)
      .filter(cssClass => cssClass.startsWith('toast-'))
      .map(cssClass => cssClass.replace('toast-', ''))
      .slice(0, 1)[0]
      .describedAs('toaster status') as QuestionAdapter<string>
 
  dismissMessage = () =>
    Task.where(`#actor dismisses the message`,
      Wait.until(this.message(), isPresent()),
      Click.on(this.message()),
      Wait.until(this.message(), not(isPresent())),
    )
}

Inspect the companion implementation to see host-element injection, Page Element queries, CSS-class state extraction, and a composed dismissal task. section 15.2.4

How BDD in Action, Second Edition covers it

How deep it goes
Defines host injection, state questions, and task-returning methods, but does not detail broader release or version-management procedures.
Examples
Uses a Toaster widget with message lookup, CSS-based status extraction, and a dismiss-message task.
What it assumes
Assumes familiarity with Page Objects and Screenplay concepts while showing how their responsibilities can work together.
What you can do after
Apply it via Serenity/JS to compose reusable widget interactions into higher-level tests.
How it connects
Connects Page Objects, the Screenplay Pattern, Screenplay tasks, question adapters, and UI widget libraries.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of BDD in Action, Second Edition. The section links point to the parts of the book it draws on.