Implementation:Webdriverio Webdriverio Page Object Class Pattern
Metadata
| Field | Value |
|---|---|
| Page ID | Page_Object_Class_Pattern |
| Wiki | Webdriverio_Webdriverio |
| Type | Implementation (Pattern Doc) |
| Domains | Testing, Design_Patterns |
| Knowledge Sources | Repo (https://github.com/webdriverio/webdriverio), Doc (https://webdriver.io/docs/pageobjects) |
| Related Principles | Principle: Page_Object_Model |
Overview
Interface specification for implementing the Page Object Model pattern in WebdriverIO test projects. The pattern consists of a base Page class with an open(path) method, and subclasses that define element getters using $('selector') and action methods. Page objects are exported as singletons. Tests import these singletons and call their methods.
Description
The Page Object Class Pattern in WebdriverIO is implemented through a class inheritance hierarchy:
- A base Page class defines the
open(path)method that delegates tobrowser.url(path). - Page-specific subclasses extend the base class and define element getters using
$('selector')(withoutawait) and action methods that compose element interactions. - Each page module exports a singleton instance (
export default new PageClass()). - Test specs import these singletons and invoke their methods using
await.
The getter pattern leverages WebdriverIO's ChainablePromiseElement -- calling $('#username') returns a thenable wrapper that resolves the element only when an action is performed. This means element lookups are always fresh and never stale.
Source
| File | Lines | Description |
|---|---|---|
examples/pageobject/pageobjects/page.js |
L1-5 | Base Page class with open(path) method |
examples/pageobject/pageobjects/form.page.js |
L1-24 | FormPage subclass with element getters and action methods |
examples/pageobject/specs/form.spec.js |
L1-26 | Test spec using FormPage singleton |
Pattern Interface
Base Page Class
// examples/pageobject/pageobjects/page.js
export default class Page {
open (path) {
return browser.url(path)
}
}
Interface contract:
- Input:
path(string) -- the URL path segment to navigate to. - Output: Returns the result of
browser.url(path), a Promise that resolves when navigation completes. - Behavior: Navigates the browser to the given path. Subclasses call
super.open('specific-path')to navigate to their page.
Page Subclass
// examples/pageobject/pageobjects/form.page.js
import Page from './page.js'
class FormPage extends Page {
/**
* define elements
*/
get username () { return $('#username') }
get password () { return $('#password') }
get submitButton () { return $('#login button[type=submit]') }
get flash () { return $('#flash') }
/**
* define or overwrite page methods
*/
open () {
return super.open('login')
}
submit () {
return this.submitButton.click()
}
}
export default new FormPage()
Interface contract:
- Element getters: Each getter returns a
ChainablePromiseElementvia$('selector'). Noawaitis used in the getter body.- Input: None (getter property).
- Output:
ChainablePromiseElement-- a thenable that resolves to a WebdriverIO Element when acted upon.
- Action methods: Compose element interactions into domain-specific actions (e.g.,
submit()).- Input: Method-specific parameters (none for
submit()). - Output: Promise from the underlying WebdriverIO command.
- Input: Method-specific parameters (none for
- Singleton export:
export default new FormPage()provides a shared instance.
Test Usage
// examples/pageobject/specs/form.spec.js
import FormPage from '../pageobjects/form.page.js'
describe('auth form', () => {
it('should deny access with wrong creds', async () => {
await FormPage.open()
await FormPage.username.addValue('foo')
await FormPage.password.addValue('bar')
await FormPage.submit()
await expect(FormPage.flash).toHaveText(
expect.stringContaining('Your username is invalid!')
)
})
it('should allow access with correct creds', async () => {
await FormPage.open()
await FormPage.username.addValue('tomsmith')
await FormPage.password.addValue('SuperSecretPassword!')
await FormPage.submit()
await FormPage.flash.waitForDisplayed()
await expect(FormPage.flash).toHaveText(
expect.stringContaining('You logged into a secure area!')
)
})
})
Test contract:
- Input: Page object method calls with test data.
- Output: Assertions on element state using
expect-webdriveriomatchers. - Pattern: Open page, interact with elements, assert on outcomes.
Directory Convention
| Directory | Purpose |
|---|---|
pageobjects/ |
Page object classes (base + page-specific) |
specs/ |
Test specification files |
wdio.conf.js |
WebdriverIO configuration at project root |
I/O Contract Summary
| Component | Input | Output |
|---|---|---|
Page.open(path) |
URL path string | Promise (navigation complete) |
Element getter (get username()) |
None | ChainablePromiseElement
|
Action method (submit()) |
Method-specific params | Promise (action complete) |
| Singleton export | None | Page object instance |
Usage Examples
Creating a new page object
import Page from './page.js'
class DashboardPage extends Page {
get welcomeMessage () { return $('[data-testid="welcome"]') }
get logoutButton () { return $('button.logout') }
get navItems () { return $$('.nav-item') }
open () {
return super.open('dashboard')
}
logout () {
return this.logoutButton.click()
}
}
export default new DashboardPage()
Using multiple page objects in a test
import LoginPage from '../pageobjects/login.page.js'
import DashboardPage from '../pageobjects/dashboard.page.js'
describe('user flow', () => {
it('should login and see dashboard', async () => {
await LoginPage.open()
await LoginPage.username.addValue('admin')
await LoginPage.password.addValue('secret')
await LoginPage.submit()
await expect(DashboardPage.welcomeMessage).toBeDisplayed()
await expect(DashboardPage.welcomeMessage).toHaveTextContaining('Welcome')
})
})
Related Pages
- implements Principle: Page_Object_Model