Dealing with Dynamic and Async UI

πŸ“‹ Table of Contents β–Ύ
  1. Working with Dynamic and Async UI
  2. Common Mistakes

Modern front-ends often load data asynchronously, animate elements, or replace parts of the DOM on interaction. Your selectors must work with this dynamic behaviour rather than fighting it.

Working with Dynamic and Async UI

Playwright’s auto-waiting and locator model already handle many async cases, but you still need to design selectors that target stable anchors in the UI. Combining good selectors with appropriate expectations avoids flaky tests.

// dynamic-async-ui.spec.ts
import { test, expect } from '@playwright/test';

test('loads search results dynamically', async ({ page }) => {
  await page.goto('https://demo.myshop.com/search');

  await page.getByRole('textbox', { name: 'Search products' }).fill('headphones');
  await page.getByRole('button', { name: 'Search' }).click();

  const results = page.getByTestId('search-results');
  await expect(results).toBeVisible();
  await expect(results.getByRole('article')).toHaveCountGreaterThan(0);
});
Note: Assertions like toBeVisible and toHaveText will automatically wait up to the configured timeout for the condition to become true.
Tip: Whenever possible, assert on stable end states (for example, “results list visible with N items”) instead of intermediate animation states.
Warning: Adding arbitrary waitForTimeout calls to “fix” flakiness usually hides deeper problems; rely on conditions and locators instead.

If elements appear only after network calls complete, you can also assert on network behaviour or use page.waitForResponse combined with selectors to check that UI and API results are aligned.

Common Mistakes

Mistake 1 β€” Using fixed sleeps instead of conditions

This wastes time and is unreliable.

❌ Wrong: Calling page.waitForTimeout(5000) after every action.

βœ… Correct: Wait for specific selectors or expectations that represent readiness.

Mistake 2 β€” Selecting transient elements instead of stable containers

Animations can break these tests.

❌ Wrong: Targeting temporary loading spinners as your main selector.

βœ… Correct: Use containers or final content as the primary target for assertions.

🧠 Test Yourself

How should you handle dynamic UI when writing selectors?