Skip to main content

Getting Started with UI Tests

UI tests in this framework validate web application functionality through browser automation. They use the Page Object Model pattern to create maintainable, readable tests.

Basic Test Structure

Every UI test follows this structure:
import LoginPage from "../../support/pages/LoginPage";
import HomePage from "../../support/pages/HomePage";

let loginPage: LoginPage
let homePage: HomePage

describe('Feature Name', () => {
  beforeEach(() => {
    // Setup: Load fixtures and initialize page objects
    cy.fixture('users').as('userData')
    loginPage = new LoginPage()
    homePage = new HomePage()
    cy.visit('/')
  });

  it('Should perform expected behavior', () => {
    // Test implementation
  });
});

Key Components

1

Import Page Objects

Import the page object classes you’ll use in your tests.
2

Declare Variables

Declare variables for page object instances at the describe block level.
3

Setup in beforeEach

Initialize page objects and load fixtures before each test.
4

Write Test Cases

Use page object methods to write readable test scenarios.

Complete Login Test Example

Here’s a real test from the framework:
LoginPageSpec.cy.ts
/// <reference types="cypress" />

import LoginPage from "../../support/pages/LoginPage";
import HomePage from "../../support/pages/HomePage";
import Navbar from "../../support/components/Navbar";

let loginPage: LoginPage
let homePage: HomePage
let navbar: Navbar

describe('User Authentication', () => {
  beforeEach(() => {
    cy.fixture('users').as('userData')
    cy.fixture('loginPageData').as('loginData')
    
    loginPage = new LoginPage()
    homePage = new HomePage()
    navbar = new Navbar()

    cy.visit('/')
  });

  it('Should log in successfully with valid credentials', () => {
    cy.get('@userData').then((user: any) => {
      loginPage.verifyLoginMainPage()
      loginPage.typeUsername(user.standard)
      loginPage.typePassword(user.password)
      loginPage.clickOnLoginButton()

      homePage.verifyHomePageIsVisible()

      navbar.userLogout()

      loginPage.verifyLoginMainPage()
    })
  });

  it('Should not log in with a locked user credentials', () => {
    cy.get('@userData').then((user: any) => {
      cy.get('@loginData').then((login: any) => {
        loginPage.verifyLoginMainPage()
        loginPage.typeUsername(user.locked)
        loginPage.typePassword(user.password)
        loginPage.clickOnLoginButton()
        loginPage.verifyErrorMessageIsShown
        loginPage.verifyErrorMessage(login.errorMsg)
        loginPage.closeErrorMessage()
        loginPage.verifyErrorMessageIsHidden()
      })
    })
  });
});

What This Test Does

Setup

Loads user credentials and login messages from fixtures, initializes page objects

Navigation

Visits the base URL (configured in cypress.config.ui.ts)

Actions

Uses page object methods to interact with the UI

Assertions

Verifies expected outcomes using page object verification methods

Testing with Fixtures

Fixtures provide test data without hardcoding:
users.json
{
  "standard": "standard_user",
  "locked": "locked_out_user",
  "problem": "problem_user",
  "password": "secret_sauce"
}
Using fixtures in tests:
beforeEach(() => {
  cy.fixture('users').as('userData')
})

it('Should login', () => {
  cy.get('@userData').then((user: any) => {
    loginPage.typeUsername(user.standard)
    loginPage.typePassword(user.password)
    loginPage.clickOnLoginButton()
  })
})
Using cy.fixture() with as() creates an alias that’s available throughout the test. Access it with cy.get('@aliasName').

Home Page Testing Example

Here’s how to test more complex UI interactions:
HomePageSpec.cy.ts
import LoginPage from "../../support/pages/LoginPage";
import HomePage from "../../support/pages/HomePage";

let loginPage: LoginPage
let homePage: HomePage

describe('Home Page Assertions and Verifications', () => {
  beforeEach(() => {
    cy.fixture('users').as('userData')

    loginPage = new LoginPage()
    homePage = new HomePage()
    cy.visit('/')

    // Login before each test
    cy.get('@userData').then((user: any) => {
      loginPage.userLogin(user.standard, user.password)
    })

    homePage.verifyHomePageIsVisible()
  });

  it('Should verify the A to Z selection', () => {
    homePage.selectFilterAtoZ()
    homePage.verifyFilterAtoZ()
  });

  it('Should verify the Z to A selection', () => {
    homePage.selectFilterZtoA()
    homePage.verifyFilterZtoA()
  });

  it('Should verify the price low to high selection', () => {
    homePage.selectFilterPriceLowtoHigh()
    homePage.verifyFilterPriceLowtoHigh()
  });
});

Testing Workflow

1

Authentication Hook

The beforeEach hook logs in before each test, ensuring tests start from the home page.
2

Action and Verification

Each test performs an action (select filter) then verifies the result.
3

Test Isolation

Each test runs independently - the beforeEach ensures a clean state.

Common UI Test Patterns

Pattern 1: Form Submission

it('Should submit form with valid data', () => {
  cy.get('@formData').then((data: any) => {
    formPage.fillField('name', data.name)
    formPage.fillField('email', data.email)
    formPage.submitForm()
    formPage.verifySuccessMessage()
  })
})

Pattern 2: Navigation Flow

it('Should navigate through checkout process', () => {
  homePage.addProductToCart(0)
  homePage.goToCart()
  cartPage.verifyProductInCart()
  cartPage.proceedToCheckout()
  checkoutPage.verifyCheckoutPageVisible()
})

Pattern 3: Error Validation

it('Should show error for invalid input', () => {
  loginPage.typeUsername('invalid')
  loginPage.typePassword('wrong')
  loginPage.clickOnLoginButton()
  loginPage.verifyErrorMessageIsShown()
  loginPage.verifyErrorMessage('Invalid credentials')
})

Pattern 4: State Verification

it('Should persist cart items after logout', () => {
  homePage.addProductToCart(0)
  homePage.addProductToCart(1)
  navbar.userLogout()
  loginPage.userLogin(user.standard, user.password)
  homePage.verifyCartCount(2)
})

Using Component Objects

For reusable UI components like navigation bars:
import Navbar from "../../support/components/Navbar";

let navbar: Navbar

beforeEach(() => {
  navbar = new Navbar()
})

it('Should logout successfully', () => {
  navbar.userLogout()
  loginPage.verifyLoginMainPage()
})
Component objects work like page objects but represent reusable UI components that appear across multiple pages.

Advanced Testing Techniques

Testing Dynamic Content

it('Should verify sorted product names', () => {
  homePage.selectFilterAtoZ()
  
  cy.get('[data-test="inventory-item"]').then(($els) => {
    const names = [...$els].map(el => el.innerText.trim())
    const sorted = [...names].sort((a, b) => a.localeCompare(b))
    expect(names).to.deep.equal(sorted)
  })
})

Testing with Multiple Data Sets

it('Should login with multiple valid users', () => {
  const users = ['standard', 'problem', 'performance', 'visual']
  
  cy.get('@userData').then((user: any) => {
    users.forEach(userType => {
      loginPage.typeUsername(user[userType])
      loginPage.typePassword(user.password)
      loginPage.clickOnLoginButton()
      homePage.verifyHomePageIsVisible()
      navbar.userLogout()
    })
  })
})

Custom Commands in Tests

If you have custom commands defined in support/commands.ts:
// In support/commands.ts
Cypress.Commands.add('login', (username: string, password: string) => {
  cy.get('#user-name').type(username)
  cy.get('#password').type(password)
  cy.get('#login-button').click()
})

// In test file
it('Should login using custom command', () => {
  cy.login('standard_user', 'secret_sauce')
  homePage.verifyHomePageIsVisible()
})

Best Practices

Never use cy.get() directly in tests - always go through page objects:
// ✅ Good
loginPage.typeUsername('user')

// ❌ Bad
cy.get('#user-name').type('user')
Each test should be able to run alone:
// ✅ Good - each test sets up its own state
beforeEach(() => {
  cy.visit('/')
  loginPage.userLogin(user.standard, user.password)
})

// ❌ Bad - tests depend on order
it('Login', () => { /* login */ })
it('Add to cart', () => { /* assumes logged in */ })
Test names should describe behavior, not implementation:
// ✅ Good
it('Should display error for locked user credentials', () => {})

// ❌ Bad
it('Test login with locked user', () => {})
Always use fixtures for test data:
// ✅ Good
cy.fixture('users').then((user) => {
  loginPage.typeUsername(user.standard)
})

// ❌ Bad
loginPage.typeUsername('standard_user')

Debugging UI Tests

Using Cypress Debug Tools

it('Should debug test flow', () => {
  loginPage.typeUsername('user')
  cy.pause()  // Pause test execution
  loginPage.typePassword('pass')
  cy.debug()  // Log debug info to console
  loginPage.clickOnLoginButton()
})

Using .then() for Debugging

cy.get('[data-test="inventory-item"]')
  .then(($el) => {
    console.log('Element:', $el)
    console.log('Text:', $el.text())
  })
Use Cypress Test Runner’s time-travel feature to hover over commands and see the application state at that point in time.

Running UI Tests

npm run cy:open:ui

Next Steps

Creating Page Objects

Build your own page object classes

Using Fixtures

Master test data management

Running Tests

Learn all test execution options

Test Organization

Structure your test suite effectively

Build docs developers (and LLMs) love