Skip to main content

Lean Page Objects Pattern

While the Screenplay Pattern is a behavioural pattern, Lean Page Objects is a structural pattern. Contrary to the more traditional definition of Page Objects, Serenity/JS Lean Page Objects are minimalistic and focused only on grouping related page elements.

Consider the below UI widget showing product search results for an imaginary online grocery store:

<ul id="product-search-results">
<li class="product-search-result">
<span class="product-name">apples</span>
<span class="product-price">£2.25</span>
<li class="product-search-result">
<span class="product-name">bananas</span>
<span class="product-price">£1.50</span>

How would you approach writing a test scenario that checks the displayed product price of an arbitrary product?

With Serenity/JS you could define a Lean Page Object describing the interesting page elements of the individual .product-search-result:

import { PageElement, By, Text } from '@serenity-js/web'

class ProductSearchResult {
static name = () =>

static price = () =>

Then, you could define another Lean Page Object to describe the container widget of product-search-results:

import { PageElement, PageElements, By } from '@serenity-js/web'

class ProductSearch {
static widget = () =>
.describedAs('product search results widget')

static results = () =>
.describedAs('product search results')

Finally, you could use Serenity/JS Page Element Query Language to define a method like resultFor that returned the required search result based on the product name:

import { Answerable } from '@serenity-js/core'
import { Text } from '@serenity-js/web'
import { includes } from '@serenity-js/assertions'

class ProductSearch {
static resultFor = (name: Answerable<string>) =>
.where(, includes(name))

// implementation of `results()` and `widget()` omitted for brevity

Once you've created a handful of simple Lean Page Objects to help you identify the correct page elements, writing a test scenario to verify the displayed price becomes trivial:

import { actorCalled } from '@serenity-js/core'
import { Ensure, equals } from '@serenity-js/assertions'

await actorCalled('Leonora').attemptsTo(

This is just one of many possible ways to structure your Lean Page Objects. Once you get used to the Serenity/JS Page Element Query Language, you'll likely find other page element structures that work for you and your project.