Skip to content

E2E: Improve Playwright test patterns and assertions #63036

@choo121600

Description

@choo121600

Motivation

Thanks to the amazing efforts of the community, the UI E2E test suite has reached solid coverage across many areas of the Airflow UI. The work tracked in the previous meta issue #59028 successfully introduced Playwright-based tests and implemented a wide range of scenarios 🎉

However, as the test suite has grown, we have started to observe some inconsistencies in testing patterns and practices across different files.

By improving consistency first, it will become easier to extend coverage, stabilize flaky tests, and maintain the E2E suite going forward.

Description

Our e2e test suite has grown over time, and we'd like to align it more closely with
Playwright's recommended best practices.

These changes are not intended to alter test coverage. The goal is to make tests more readable, more stable in CI, and easier to maintain.

This issue tracks several improvements that can be addressed independently.

Patterns to improve

The following patterns appear in multiple tests and can be improved to better align with Playwright best practices.

1. page.waitForFunction() with DOM queries → locator-based waiting

Using document.querySelector() inside waitForFunction() bypasses Playwright's built-in auto-waiting and retry logic.

// current pattern
await page.waitForFunction(() => {
  const rows = document.querySelectorAll("table tbody tr");
  return rows.length > 0;
});

// preferred
await expect(page.locator("table tbody tr")).not.toHaveCount(0);

2. page.waitForTimeout() → state-based waiting

Fixed timeouts can slow tests and may introduce flakiness in CI.

Each occurrence should be reviewed individually. In some cases the wait may be intentional
(e.g., animations or debounced inputs), so the correct replacement may vary depending on context.

// current pattern
await page.waitForTimeout(500);

// preferred — wait for the expected UI state
await expect(element).toBeVisible();

3. waitForLoadState("networkidle") → wait for specific UI state

Playwright discourages using networkidle as a general waiting strategy:
https://playwright.dev/docs/api/class-frame#frame-wait-for-load-state

It can be unreliable for modern SPAs that use polling, websockets, or other long-lived connections.

// current pattern
await page.waitForLoadState("networkidle");

// preferred — wait for the UI state you expect
await expect(page.getByRole("table")).toBeVisible();

4. Manual assertions → web-first assertions

Assertions such as expect(await locator.isVisible()).toBe(true) check the condition once.
Playwright’s web-first assertions retry automatically until the condition is met or a timeout occurs.

// current pattern
expect(await page.getByText("welcome").isVisible()).toBe(true);
const count = await rows.count();
expect(count).toBeGreaterThan(0);

// preferred
await expect(page.getByText("welcome")).toBeVisible();
await expect(rows).not.toHaveCount(0);

5. page.evaluate() for DOM manipulation → observe UI state instead

Tests should observe application state rather than modifying the DOM directly.

// current pattern
await page.evaluate(() => {
  document.querySelector(".backdrop")?.remove();
});

// preferred
await expect(page.locator(".backdrop")).toBeHidden();

6. CSS :has-text() → user-facing locators

Playwright recommends user-facing locators such as getByRole() or getByText().
These tend to be more resilient to markup changes and better reflect how users interact with the UI.

// current pattern
page.locator('th:has-text("DAG ID")')
page.locator('button:has-text("Filter")')

// preferred
page.getByRole("columnheader", { name: "DAG ID" })
page.getByRole("button", { name: "Filter" })

How to test in local

breeze testing ui-e2e-tests --test-pattern <spec file name> --browser <browser name> --workers 2
  • <spec file name>: the test file you are working on (e.g. connections.spec.ts)
  • <browser name>: one of chromium, firefox, or webkit

eg.

breeze testing ui-e2e-tests --test-pattern "connections.spec.ts" --browser firefox --workers 2

Notes for contributors

  • All files are under airflow-core/src/airflow/ui/tests/e2e/
  • See tests/e2e/README.md for instructions on running tests locally.
  • Keeping PRs small and focused (for example, one pattern or one spec at a time) will make reviews easier.
  • Not every instance is a straightforward replacement — please review the surrounding context and choose the appropriate approach.
  • If you plan to work on a file, feel free to leave a comment so others know it is being worked on.
  • For discussions related to this effort, contributors are welcome to join the Slack channel #sig-ui-e2e-tests.

Some of these tasks may also be good first contributions for people interested in improving the UI test suite 🙌

Files to review

The following files may contain patterns that can be improved.

Page Objects

Specs

References

Note:

Still flaky parts

Ignore spec now

  • "/dag-runs-tab.spec.ts",
  • "/dag-runs.spec.ts",
  • "/dag-grid-view.spec.ts",
  • "/task-logs.spec.ts",
  • "/dag-tasks.spec.ts",
  • "/variable.spec.ts",

After Work

Committer

  • I acknowledge that I am a maintainer/committer of the Apache Airflow project.

Metadata

Metadata

Assignees

Labels

area:UIRelated to UI/UX. For Frontend Developers.kind:featureFeature Requestskind:metaHigh-level information important to the community

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions