QA & Testing · Guide

UI Automation

Driving a real browser: Selenium and Playwright, page objects, and the discipline that keeps tests from going flaky.

— min read QA & Testing

The Expensive Layer

UI tests are the only ones that prove a user can actually complete a journey — and the slowest, most fragile tests you will own. Both facts are true, which is why the answer is "a few, kept in very good repair".

Everything in a browser is asynchronous, so most UI test failures are timing rather than logic. The craft is expressing intent in a way that survives a redesign, and waiting for the right condition rather than for a number of seconds.

Selenium

Selenium WebDriver is the long-standing standard: a W3C protocol, every major language binding, every browser, and the automation layer underneath a great deal of enterprise tooling.

StrengthCost
Universal browser and language supportWaiting is your problem to solve
A genuine standard, not one vendorMore setup — drivers, versions, grid
Huge ecosystem and hiring poolSlower feedback loop than modern tools
Selenium Grid for parallel runsInfrastructure to maintain
sleep(3) is the single biggest source of flaky Selenium suites. It is too short on a loaded CI machine and wasted time everywhere else. Use explicit waits for a condition — element visible, request finished, text present.

Playwright

Playwright takes the newer approach: one API across Chromium, Firefox and WebKit, with auto-waiting built into every action. Clicking waits for the element to exist, be visible, be stable and be enabled — which removes most of the wait code a Selenium suite carries.

// auto-waits for the button, and for the assertion to become true
await page.getByRole('button', { name: 'Check out' }).click();
await expect(page.getByText('Order confirmed')).toBeVisible();

// intercept the network instead of depending on a live backend
await page.route('**/api/prices', route =>
  route.fulfill({ json: { total: 4200 } })
);
FeatureWhy it matters
Auto-waitingRemoves the main cause of flakiness
Trace viewerA failed CI run replays with DOM snapshots
Network interceptionTest the UI without depending on a backend
Parallel isolated contextsFast, and no state leaking between tests

Locator strategy matters more than the tool. Prefer role and accessible namegetByRole('button', { name: 'Save' }) — then test ids. CSS paths tied to layout break on the next redesign, and XPath through five divs breaks on the one after.

Page Object Model

Without structure, every test contains selectors, and a renamed field means editing forty files. The page object model puts the selectors and interactions for one screen behind a small class, so tests read as intent.

class CheckoutPage {
  constructor(page) { this.page = page; }

  async payWith(card) {
    await this.page.getByLabel('Card number').fill(card.number);
    await this.page.getByRole('button', { name: 'Pay' }).click();
  }

  confirmation() { return this.page.getByText('Order confirmed'); }
}

// the test says what, the page object knows how
await checkout.payWith(validCard);
await expect(checkout.confirmation()).toBeVisible();
Keep assertions in the tests, not in the page objects. A page object that asserts becomes a second test suite nobody can read, and the same method starts meaning different things to different tests.

Fixing Flaky Tests

A flaky test passes and fails on the same code. It is worse than a failing test, because it teaches the team that red means nothing.

CauseFix
Fixed sleepsWait for a condition, not a duration
Shared test accounts or dataCreate fresh data per test
Test order dependenceReset state; run in random order to expose it
Animations and transitionsDisable them in the test environment
Live third-party servicesIntercept the network and serve a fixture
Race on loadAssert on the settled state, not an intermediate one
Never fix flakiness with an automatic retry. Retries hide the defect — sometimes a real race in the product — and the suite quietly stops being evidence of anything. Quarantine the test, find the cause, then bring it back.

Interview Questions

Why are UI tests kept few?

They are the slowest and most fragile layer. They prove a journey works end to end, which nothing else does, but a large UI suite is slow, flaky and eventually ignored.

Selenium or Playwright?

Selenium is the W3C standard with the widest language and browser reach and a huge ecosystem. Playwright auto-waits on every action, intercepts network and ships a trace viewer, which removes most flakiness by default.

What is the best locator strategy?

Role and accessible name first, then dedicated test ids. CSS paths and XPath tied to structure break on the next redesign, and they also say nothing about intent.

What does the page object model buy you?

One place per screen holding selectors and interactions, so tests express intent and a renamed field is a single edit rather than forty.

Why is a flaky test worse than a failing one?

It destroys trust in the whole suite. Once red sometimes means nothing, people rerun until green and real failures pass through unnoticed.

Why not auto-retry flaky tests?

A retry hides the cause, which is sometimes a genuine race condition in the product. Quarantine, diagnose and fix, then return the test to the suite.

Quick Quiz

1. The most common cause of flaky UI tests is…
2. Playwright differs from Selenium mainly by…
3. The most durable locator is…
4. Assertions belong in…
5. Auto-retrying a flaky test…