Your first UI test
Write a UI scenario with a page object and heal-aware locators, then run it in Chromium.
How a UI scenario maps to a page object with step decorators, how heal-aware locators are declared, and how to run the scenario headless or headed in Chromium.
Try one before you write one
Sign-in is the door to the whole store, so Demo Shop runs this scenario on every pull request. The
panel below is a stand-in for the storefront carrying the same ids and data-test attributes as
the real one, and the steps below really drive it: press Run scenario and watch the fields
fill, the button click and the browser land on /inventory.html.
Sign-in is the door to the whole store. If it breaks, nothing else matters, so Demo Shop runs this one on every pull request, on all three browsers.
Done means
- A known shopper reaches the products page.
- The browser really lands on /inventory.html, not a half-loaded login screen.
- The check is fast enough to sit in front of every merge.
@ui@smoke· lint clean · 4 steps
Swag Labs
LoginAccepted usernames: standard_user, problem_user, performance_glitch_user, locked_out_user. Password: secret_sauce.
- not run: Given I am on the login page
- not run: When I login with "standard_user" and "{{standardPassword}}"
- not run: Then the page URL should contain "/inventory.html"
- not run: And the inventory title should be "Products"
Run the same scenario on your machine:
sdods run -p demo-shop -e staging -l ui -b chromium -t @smoke
The feature
projects/demo-shop/features/ui/login.feature:
@ui @smoke
Feature: Login
Scenario: A standard user logs in
Given I am on the login page
When I login with "standard_user" and "{{standardPassword}}"
Then the page URL should contain "/inventory.html"{{standardPassword}} comes from vars in the environment file, which in turn reads ${DEMO_SHOP_PASSWORD:-secret_sauce}.
The page object
projects/demo-shop/pages/LoginPage.ts:
@Fixture<typeof test>('loginPage')
export class LoginPage extends BasePage {
readonly username = this.heal.locator(this.page.locator('#user-name'), {
label: 'Username',
placeholder: 'Username',
testId: 'username',
description: 'username input',
});
readonly password = this.heal.locator(this.page.locator('#password'), {
placeholder: 'Password',
testId: 'password',
description: 'password input',
});
readonly submit = this.heal.locator(this.page.locator('#login-button'), {
role: 'button',
name: 'Login',
testId: 'login-button',
description: 'login button',
});
@Given('I am on the login page')
async open() {
await this.goto('login'); // route name from sdods.project.yaml
}
@When('I login with {string} and {string}')
async login(user: string, password: string) {
await this.username.fill(user);
await this.password.fill(password);
await this.submit.click();
}
}The page object is exposed as a fixture in steps/fixtures.ts, and @Fixture('loginPage') binds the decorated steps to it. this.heal.locator keeps your primary locator and records the context SDODS uses when that locator breaks; see Self-healing locators.
A longer flow
A shopper adding a product is two pages and one piece of state that has to survive the trip. The feature file says the product by name; the page object works out the selector.
The cart badge is the shop’s promise that it heard you. Nadia wants it proven end to end: add a product on the listing page, and find that product in the cart.
Done means
- Adding a product raises the badge to exactly one item.
- The cart page lists the product that was added, by name.
- The scenario cleans up after itself: it starts from a signed-out browser every time.
@ui@regression· lint clean · 6 steps
Swag Labs
LoginAccepted usernames: standard_user, problem_user, performance_glitch_user, locked_out_user. Password: secret_sauce.
- not run: Given I am on the login page
- not run: When I login with "standard_user" and "{{standardPassword}}"
- not run: And I add "{{defaultProduct}}" to the cart
- not run: Then the cart badge should show 1 item
- not run: When I open the cart
- not run: Then the cart should list "{{defaultProduct}}"
Run the same scenario on your machine:
sdods run -p demo-shop -e staging -l ui -b chromium -t @regression
Run it
bun run sdods run -p demo-shop -e staging -l ui -b chromium -t @smoke
bun run sdods run -p demo-shop -e staging -l ui -b chromium -t @smoke --headed
bun run sdods run -p demo-shop -e staging -l ui -b chromium --ui # interactive UI mode@smoke uses the scenario screenshot policy by default: one capture at the start and one at the end. Switch to @regression and you get before and after captures for every step.