Skip to content

test: improve E2E test quality across recipe specs#3629

Draft
itsjustriley wants to merge 11 commits intomainfrom
fix/e2e-test-quality
Draft

test: improve E2E test quality across recipe specs#3629
itsjustriley wants to merge 11 commits intomainfrom
fix/e2e-test-quality

Conversation

@itsjustriley
Copy link
Copy Markdown
Contributor

@itsjustriley itsjustriley commented Mar 26, 2026

WHY are these changes introduced?

Fixes https://github.com/Shopify/developer-tools-team/issues/1155
Fixes https://github.com/Shopify/developer-tools-team/issues/1153
Fixes https://github.com/Shopify/developer-tools-team/issues/1151
Fixes https://github.com/Shopify/developer-tools-team/issues/1150
Fixes https://github.com/Shopify/developer-tools-team/issues/1116
Fixes https://github.com/Shopify/developer-tools-team/issues/1152
Fixes https://github.com/Shopify/developer-tools-team/issues/1154
Fixes https://github.com/Shopify/developer-tools-team/issues/1118
Fixes https://github.com/Shopify/developer-tools-team/issues/1135
Fixes https://github.com/Shopify/developer-tools-team/issues/1136

E2E recipe specs had duplicated constants, race conditions, missing documentation, and insufficient assertions that could mask real failures.

WHAT is this pull request doing?

HOW to test your changes?

  • npx playwright test e2e/specs/recipes/custom-cart-method.spec.ts --workers=1
  • npx playwright test e2e/specs/recipes/metaobjects.spec.ts --workers=1
  • npx playwright test e2e/specs/recipes/b2b.spec.ts --workers=1
  • npx playwright test e2e/specs/recipes/third-party-api.spec.ts --workers=1
  • npx playwright test e2e/specs/recipes/subscriptions-auth.spec.ts --workers=1
  • npx vitest run packages/cli/src/commands/hydrogen/upgrade-e2e.test.ts --pool=forks --poolOptions.forks.singleFork

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or noteworthy changes
  • I've added tests to cover my changes
  • I've added or updated the documentation

itsjustriley and others added 2 commits March 26, 2026 11:20
#1155: Extract shared KNOWN_PRODUCT constant to e2e/fixtures/known-products.ts,
eliminating duplication across 5 spec files. Each file imports with a
context-appropriate alias (KNOWN_REGULAR_PRODUCT, KNOWN_PRODUCT_WITH_VARIANTS).

#1153: Replace bare getAttribute('href') calls with expect.poll() in
custom-cart-method.spec.ts for auto-retry after async cart updates.

#1151: Add case-insensitive flag to negative locale assertion regex in
markets-utils.ts. Add comment explaining the |\/$  branch in waitForURL
(handles default locale where pathPrefix is '/').

#1150: Consolidate redundant page.goto('/') calls into beforeEach in B2B
Location Selector tests. Add comment explaining why CustomerLocations
uses raw graphql.query() (recipe-specific query, no generated types).

#1116: Improve metaobjects spec: replace CSS child selector with
getByRole('region'), extract getStoreLinks() helper for 4 repeated
locator patterns, replace page.waitForURL with expect(page).toHaveURL,
strengthen keyboard tests with Tab navigation instead of .focus().

#1152: Replace .or() assertion in subscriptions-auth cancel test with
definitive expect(cancelButton).not.toBeVisible(). Add mock data drift
risk documentation to subscriptions handler comment.

#1154: Add comment clarifying tag-based parent-product filter mechanism
in combined-listings spec.

#1118: Add test.describe.configure({retries: 1}) for third-party-api
tests (live external API dependency). Add explicit 15s timeout to
expect.poll() calls via EXTERNAL_API_TIMEOUT_IN_MS constant.

#1135: Add skip/success summary log at end of E2E upgrade test to
surface changelog data quality drift.

#1136: Verify cumulative intermediate dependencies after upgrade, not
just toRelease dependencies. Exercises getCumulativeRelease logic for
multi-version jumps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract maxTabPresses to MAX_TAB_PRESSES_TO_REACH_CONTENT constant
- Remove unnecessary scrollIntoViewIfNeeded before Tab navigation
- Remove dead getCancellingButton method from SubscriptionsUtil
- Add cumulative devDependencies verification in upgrade E2E test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@shopify
Copy link
Copy Markdown
Contributor

shopify bot commented Mar 26, 2026

Oxygen deployed a preview of your fix/e2e-test-quality branch. Details:

Storefront Status Preview link Deployment details Last update (UTC)
Skeleton (skeleton.hydrogen.shop) ✅ Successful (Logs) Preview deployment Inspect deployment March 27, 202611:41 AM

Learn more about Hydrogen's GitHub integration.

itsjustriley and others added 9 commits March 26, 2026 12:13
The cancel test was asserting `not.toBeVisible()` after clicking the
cancel button, but never verified the button was visible before clicking.
This means the test could vacuously pass if the mock data drifted and
the cancel button was never rendered.

Adding `await expect(cancelButton).toBeVisible()` before the click
ensures the test fails if the button is absent, following the E2E
testing guideline of asserting presence before asserting absence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Widen validateDependencyVersion depType param from union to string,
  fixing the blocking type mismatch when called with template literals
  for cumulative dependency context
- Add comment documenting known limitation where cumulative dep loop
  may produce false failures when multiple intermediate releases update
  the same dependency
- Add comments explaining why the if(actualVersion) guard intentionally
  skips deps not present in the upgraded project
- Add diagnostic Tab-loop-exhaustion assertions to metaobjects keyboard
  navigation tests for clearer failure messages
- Rename KNOWN_PRODUCT to KNOWN_SKELETON_PRODUCT to eliminate aliasing
  ceremony at every import site

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test loop iterated through intermediate releases (newest-first from
changelog order) and validated each release's expected dep version
independently. When multiple intermediate releases updated the same dep
(e.g., react-router), the loop would assert against the earlier version
even though the production code correctly installs the latest.

Fix: build last-write-wins maps (iterating oldest-first) to determine
the final expected version for each dep, then validate once. This
mirrors the production getCumulativeRelease logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ents

#1125: PaginatedResourceSection renders region wrapper when ariaLabel
is provided even without resourcesClassName. Previously the ariaLabel
prop was silently ignored unless className was also passed.

#1139: Add aria-live="polite" region to PaginatedResourceSection for
loading state and product count announcements (WCAG 4.1.3). Also add
aria-live announcement to infinite-scroll recipe ProductsGrid for IO
auto-loaded content.

#1140: Infinite scroll recipe: separate IO ref target from focusable
NextLink into a non-focusable <div aria-hidden="true"> sentinel.
Prevents keyboard tabbing from triggering IO and creating an infinite
loading cycle (WCAG 2.1.1, 2.2.2).

#1115: Metaobjects recipe: change <h1> to <h2> in SectionHero,
SectionStores, and SectionStoreProfile to fix heading hierarchy
(WCAG 1.3.1). Replace <div role="region"> with <section> in
Sections.tsx per first rule of ARIA.

#1111: Markets country selector: add Escape key handler to close
dropdown; add tabIndex={-1} to hidden form inputs to prevent them
from appearing in tab sequence.

#1114: Note — subscriptions ProductPrice and infinite-scroll
ProductsGrid hardcoded ARIA attributes are documented as pre-existing
patterns that require recipe regeneration to align with ariaLabel prop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Round 1 review fixes from Gray, John, Tina:

- Remove redundant tabIndex={-1} on hidden inputs (all agents)
- Remove role="group" on <details> to preserve native disclosure
  semantics (Tina: WCAG 4.1.2)
- Fix SectionStores nested h2→h3 for store name headings
  (Tina: WCAG 1.3.1 heading hierarchy)
- Fix SectionStoreProfile h5→h3 for Address/Opening Hours
  sub-headings (Tina: WCAG 1.3.1 heading level skip)
- Extract PaginationStatus component to defer aria-live
  announcements until after initial render (Tina: WCAG 4.1.3)
- Use explicit '1px' in sentinel style (John: clarity)
- Add changeset for skeleton template changes (Gray: blocking)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap decorative unicode arrows (↑/↓) in infinite-scroll patch with
aria-hidden="true" to prevent screen readers from announcing "upwards
arrow" / "downwards arrow". This matches the existing pattern in the
skeleton template's PaginatedResourceSection.

Use the idiomatic HTMLDetailsElement.open property instead of
removeAttribute('open') in CountrySelector's Escape key handler,
since the <details> element exposes a dedicated boolean property
for controlling its open/closed state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply the deferred aria-live pattern (useState('') + useEffect) to the
infinite-scroll ProductsGrid, matching the PaginationStatus approach in
PaginatedResourceSection. This prevents potential unprompted screen reader
announcements on initial mount.

Replace `state: any` with the actual Pagination state type shape to
restore TypeScript safety in the recipe patch.

Update changeset description to reflect the full PR scope: heading
hierarchy fixes, CountrySelector Escape key, IO sentinel separation,
and aria-live announcements — not just PaginatedResourceSection changes.

Add clarifying comment in CountrySelector explaining that `<input
type="hidden">` elements are not focusable per HTML spec, addressing
the concern raised in issue #1111 about hidden input tabIndex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The helper was only used once, on the root URL after page.goto('/').
A direct pathname assertion is more readable and precise — it verifies
the exact expected state (pathname is '/') rather than a negation
(pathname doesn't match a locale pattern).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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