Page Object Pattern & Locators
In Robot Framework, the common “page object” shape is a .resource file: locators live in variables, interactions in keywords. Tests stay thin and call stable, intent-named keywords.
Why resources, not classes
| Approach | Typical use in RF | Maintenance |
|---|---|---|
.resource + variables |
One file per page/area; import in tests | Change selector in one place |
| Python page classes | Possible via libraries | Heavier; mix RF and code ownership |
| Locators inlined in tests | Quick spikes | Duplication and brittle suites |
Convention: pages/login_page.resource defines ${LOGIN_USER}, Login With Credentials, etc. Suites do Resource pages/login_page.resource and never repeat raw selectors.
Locator priority (use in this order)
data-testid/data-test— agreed with devs; survives restyling.id— stable if not generated per session.- CSS — good for structure; avoid over-long chains.
- XPath — last resort (fragile with DOM refactors).
Centralize every selector in the *** Variables *** section (or a dedicated variables file imported via Variables). Separate “what to find” (variables) from “what to do” (keywords).
SeleniumLibrary vs Browser (Playwright)
| Aspect | SeleniumLibrary | Browser library |
|---|---|---|
| Driver | WebDriver (browser drivers) | Playwright |
| Default timing | Often needs explicit waits | Auto-wait on actions/assertions |
| Network | Possible via browser APIs; more setup | Request/response hooks, routing |
| Browsers | Per-driver install | Chromium, Firefox, WebKit via Playwright |
| Multi-context | Windows/tabs via WebDriver | Strong context / page model |
| When to pick | Existing Selenium estate, strict WebDriver need | New UI suites, stability, modern apps |
Browser library reduces “wait soup” but you still model clear page keywords and stable selectors.
Example: login page resource
pages/login_page.resource (SeleniumLibrary style; swap library keywords for Browser if needed):
*** Settings ***
Library SeleniumLibrary
*** Variables ***
${LOGIN_URL} https://app.example/login
${LOC_USER} css:[data-testid="login-username"]
${LOC_PASS} css:[data-testid="login-password"]
${LOC_SUBMIT} css:[data-testid="login-submit"]
${LOC_ERR} css:[data-testid="login-error"]
*** Keywords ***
Open Login Page
Go To ${LOGIN_URL}
Wait Until Element Is Visible ${LOC_SUBMIT}
Input Credentials
[Arguments] ${username} ${password}
Wait Until Element Is Visible ${LOC_USER}
Clear Element Text ${LOC_USER}
Input Text ${LOC_USER} ${username}
Input Text ${LOC_PASS} ${password}
Submit Login
Click Element ${LOC_SUBMIT}
Login With Credentials
[Arguments] ${username} ${password}
Open Login Page
Input Credentials ${username} ${password}
Submit Login
Error Should Be Visible
Wait Until Element Is Visible ${LOC_ERR}
Example: test using page keywords
*** Settings ***
Resource pages/login_page.resource
Suite Setup Open Browser ${BASE_URL} chrome
Suite Teardown Close Browser
*** Variables ***
${BASE_URL} https://app.example
*** Test Cases ***
Valid User Can Open Login And Submit
Login With Credentials ${VALID_USER} ${VALID_PASS}
Location Should Contain /dashboard
Composition for flows
Import several page resources and compose keywords for journeys (e.g. Login With Credentials then Open Settings From Header from header.resource). Keep each keyword single-purpose; orchestrate sequences in tests or a thin flows.resource.
Checklist
| Practice | Do |
|---|---|
| Locators | Variables only; data-testid first |
| Keywords | Name by user intent, not widget IDs |
| Tests | No raw Click on CSS/XPath if a page keyword exists |
| Files | One primary .resource per page or major component |