Skip to content

test(e2e): port eyes.feature to Playwright - #74869

Open
stephenliang wants to merge 1 commit into
stagingfrom
stephen/port-eyes-playwright
Open

test(e2e): port eyes.feature to Playwright#74869
stephenliang wants to merge 1 commit into
stagingfrom
stephen/port-eyes-playwright

Conversation

@stephenliang

@stephenliang stephenliang commented Aug 25, 2026

Copy link
Copy Markdown
Member

Ports the 7 scenarios in dashboard/test/ui/features/eyes.feature to Playwright, and fixes the shared instructions-overlay dismissal they exposed.

Port

Each Cucumber scenario becomes a @visual test carrying the Eyes checkpoints, plus an axe scan where the result is the same on every engine. New page objects: MatchLevel, TextCompressionLevel. New helpers: tests/shared/dialogs.ts, waitForHeaderFadeIn().

Why the RTL surfaces have no axe scan

axe's color-contrast and scrollable-region-focusable verdicts differ between engines even when the computed styles are identical, so there is no shared baseline to assert and a per-engine one would only be recording the discrepancy. Both differences were measured rather than assumed:

Rule Engine What differs
scrollable-region-focusable firefox only #workspace-header overflows its box by 4px (scrollHeight 25 vs clientHeight 21); chromium and webkit see no overflow at all.
color-contrast webkit only #runButton reported at 3.07:1 against the 4.5:1 AA requirement. chromium and firefox report identical color, background-color, font-size (16px) and font-weight (600), and pass it.

The contrast failure is real and is not a test artifact. White #ffffff on #f46800 computes to 3.07:1, below the 4.5:1 WCAG AA requirement for normal-weight text. webkit is correct here and the other two engines are under-reporting. This affects the primary Run button on legacy Blockly labs, not just RTL, so it wants its own fix rather than a baseline entry.

Shared POM fix

LegacyBlocklyLab.waitForReady() located the instructions overlay's OK button by accessible name. InstructionsCsfMiddleCol.jsx renders that name from i18n.dialogOK(), so it never matched under /lang/ar-sa and every dismissal fell through to a coordinate click that the level's own instructions panel intercepts. It now anchors on the <hr> in the same overlayVisible-gated block, which is locale-independent.

This file has 17 consumers, so the 12 sibling specs that exercise it were re-run — see below.

Where the new code lives

Two pieces of this port belong to existing owners rather than to new shared utilities.

The level-content dialog is a LevelDialogComponent in tests/components/, composed onto LessonLevelPage beside introVideoModal, matching the five dialog components already there. It replaces a tests/shared/dialogs.ts module of free functions.

The header fade-in is HeaderComponent.waitForFadeIn(), beside the header's other waits, and the font-relayout wait moves off shared/stability.ts to HeaderComponent.waitForSettled(). shared/stability.ts is left with only engine-agnostic primitives. The two header signals stay separate deliberately: waitForFadeIn is a single locator assertion and sits on the lab-boot path that every lab spec runs, while waitForSettled polls page.evaluate and is too costly to put there.

Links

  • Jira:

Testing story

Verified against test-studio.code.org. The eyes spec on its own is clean:

Lane Command Result
a11y --repeat-each=5, chromium + firefox + webkit 90/90
visual VISUAL_PROVIDER=playwright 21/21

Every remaining axe baseline is asserted identically on all three engines, so the a11y lane carries no per-browser lookup table and no browserName branching.

Because this changes page objects that other specs share, the 14 specs touching them were also run at --repeat-each=3 on three browsers: 568/576. The 8 remaining failures are webkit-dominated 90s timeouts and browser crashes under local resource pressure — the runs left 11GB of core dumps behind on a machine with 4GB free. An unmodified HEAD scores 565/576 on the same suite and hardware, so this branch is at or slightly above the baseline rather than regressing it. These need a CI run on healthier hardware to be conclusive.

Deployment notes

The feature is tagged @playwright to record that it has been ported, but that tag does not yet stop Cucumber from running it: rake test:eyes_ui selects features with grep -lr '@eyes' and applies no @playwright exclusion. So these scenarios run in both suites and keep consuming Applitools checkpoints until that task learns the tag. Pre-existing and repo-wide — initial_page_views2.feature and initial_page_views3.feature already have the same @eyes @playwright shape. Worth a follow-up ticket; deliberately not changed here.

@stephenliang
stephenliang marked this pull request as ready for review August 25, 2026 17:44
@stephenliang
stephenliang force-pushed the stephen/port-eyes-playwright branch 2 times, most recently from 6249019 to e9fd531 Compare August 25, 2026 18:04
Port 7 scenarios from dashboard/test/ui/features/eyes.feature. Each scenario
becomes a @visual test carrying the Eyes checkpoints, plus an axe scan where the
result is the same on every engine.

The RTL surfaces get no axe scan. axe's color-contrast and
scrollable-region-focusable verdicts differ between engines on identical
computed styles, so a shared baseline is not available and a per-engine one
would only record the discrepancy. Two examples, both measured: firefox alone
sees #workspace-header overflow its box by 4px, and webkit alone reports
#runButton at 3.07:1 against the 4.5:1 AA requirement while chromium and firefox
pass it. That contrast failure is real (white on #f46800) and wants a separate
fix; it is not a test artifact.

Also fix the shared instructions-overlay dismissal in LegacyBlocklyLab: it
located the OK button by accessible name, which InstructionsCsfMiddleCol.jsx
renders from i18n.dialogOK(), so it never matched under /lang/ar-sa and fell
through to a coordinate click the instructions panel intercepts. It now anchors
on the same block's <hr> instead.

Two pieces of this port belong to existing owners rather than to new shared
utilities. The level-content dialog is now LevelDialogComponent, composed onto
LessonLevelPage beside introVideoModal. The header fade-in is now
HeaderComponent.waitForFadeIn, beside the other header waits; the font-relayout
wait moves off shared/stability.ts to HeaderComponent.waitForSettled. The two
header signals stay separate on purpose: waitForFadeIn is a single locator
assertion and sits on the lab-boot path, while waitForSettled polls
page.evaluate and is too costly to put there.

The feature is tagged @playwright to record that it has been ported. Note this
does not yet stop Cucumber from running it: rake test:eyes_ui selects features
with `grep -lr '@eyes'` and applies no @playwright exclusion.

Verified against test-studio.code.org. The eyes spec alone is clean: a11y 90/90
at 5x across chromium/firefox/webkit, visual 21/21. Across the 14 specs touching
the shared page objects, 568/576 at 3x on three browsers; the 8 remaining are
webkit-dominated crashes and 90s timeouts under local resource pressure, and an
unmodified HEAD scores 565/576 on the same suite and hardware.

Source: dashboard/test/ui/features/eyes.feature

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@stephenliang
stephenliang force-pushed the stephen/port-eyes-playwright branch from e9fd531 to 13df9aa Compare August 25, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant