SDODS
Getting started

Your first UI test

Write a UI scenario with a page object and heal-aware locators, then run it in Chromium.

What you'll learn

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.

@uiA shopper can sign in

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.
The scenario

@ui@smoke· lint clean · 4 steps

https://www.saucedemo.com/chromium

Swag Labs

Login

Accepted usernames: standard_user, problem_user, performance_glitch_user, locked_out_user. Password: secret_sauce.

MaxiThis panel is a stand-in for the storefront, with the same ids and data-test attributes as the real one. Press Run scenario and watch the steps type, click and land on the products page.
  1. not run: Given I am on the login page
  2. not run: When I login with "standard_user" and "{{standardPassword}}"
  3. not run: Then the page URL should contain "/inventory.html"
  4. 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.

@uiAdding a product updates the cart

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.
The scenario

@ui@regression· lint clean · 6 steps

https://www.saucedemo.com/chromium

Swag Labs

Login

Accepted usernames: standard_user, problem_user, performance_glitch_user, locked_out_user. Password: secret_sauce.

MaxiSame storefront, a longer flow. Try editing the product name in the scenario — "Sauce Labs Bike Light" works, "Sauce Labs Hoodie" does not, and the failure message tells you why.
  1. not run: Given I am on the login page
  2. not run: When I login with "standard_user" and "{{standardPassword}}"
  3. not run: And I add "{{defaultProduct}}" to the cart
  4. not run: Then the cart badge should show 1 item
  5. not run: When I open the cart
  6. 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.

Next steps

Using an AI coding agent? Install SDODS:

Install the SDODS plugin in Claude Code: skills, subagents, commands and the MCP server.

  1. /plugin marketplace add YarlisAISolutions/sdods-skills
  2. /plugin install sdods@sdods
Learn more about using SDODS with AI tools

On this page