Page Objects, Components and Macros
Keep selectors and flows in classes, reuse widgets, add your own methods.
On this page
Page Objects, Components and Macros
When the same selectors and steps appear in many scripts or tests, move them into classes:
- A page object is one page: its URL, its element aliases and its flows.
- A component is a widget used on many pages, such as a menu or a date picker.
- A macro adds your own method to every page or locator.
Page objects
// pages/LoginPage.bx
class extends="models.PageObject@bxplaywright" {
url = "/login"
elements = {
email : "input[name=email]",
password : "input[type=password]",
submit : "button[type=submit]"
}
function at() {
page.assertTitle( "Login" )
}
function loginAs( required string email, string password = "secret" ) {
page.fill( "@email", arguments.email )
.fill( "@password", arguments.password )
.click( "@submit" )
return page.on( new pages.DashboardPage() )
}
}
playwright( { baseURL : "http://localhost:8080" } ).browse( ( page ) => {
page.visit( new pages.LoginPage() )
.loginAs( "luis@ortus.com" )
.assertSee( "Dashboard" )
} )
page.visit( pageObject )goes to itsurl, binds it and runsat().page.on( pageObject )binds without navigating, so a flow can return the next page (DashboardPageis another page object).elementsbecome@nameselectors.element( "email" )returns the locator of an alias.- Inside the class,
pageis the bound page. - Page methods work on the page object too and keep returning it, so chains continue.
Components
// pages/UserMenu.bx
class extends="models.PageComponent@bxplaywright" {
selector = "nav.user"
elements = { logout : "a.logout" }
function userName() {
return text( "span" )
}
}
page.within( new pages.UserMenu(), ( menu ) => menu.click( "@logout" ) )
menu = page.component( new pages.UserMenu() )
println( menu.userName() )
Every page action and assertion is available on a component, scoped to its selector.
Macros
pw = playwright( { baseURL : "http://localhost:8080" } )
pw.macro( "loginAs", ( page, email ) => {
page.visit( "/login" ).fill( "Email", email ).click( "Sign in" )
} )
pw.macro( "shout", ( locator ) => uCase( locator.text() ), "locator" )
pw.browse( ( page ) => {
page.loginAs( "luis@ortus.com" ).assertSee( "Welcome" )
println( page.locator( "h1" ).shout() )
} )
- The handler receives the page (or locator) first, then the call arguments. Return nothing to keep chaining.
- The third argument is the target:
page(default) orlocator. - Macros are global to the runtime: register them once, for example in
beforeAll(). Remove them withremoveMacro( name, target ), or every macro of a target withremoveMacro( target = "page" ).