Problem
The anvil-cmg pagination e2e spec (e2e/anvil/anvil-pagination.spec.ts) is the last anvil suite still using the legacy shared helpers in e2e/testFunctions.ts (testFirstPagePagination, filterAndTestLastPagePagination, testPaginationContent). These helpers are the same family that produced flakes in #4800, #4751, #4656, and #4828 — class-/id-based locators, generic waitForLoadState between interactions, and consolidated multi-assertion tests that obscure which step actually failed.
The current spec also bundles unrelated behaviour into three sweeping tests, so a single timing flake masks itself as a single test failure with little signal about the actual broken step.
Likely root cause (of the legacy spec's brittleness)
- Three monolithic tests cover ~8 distinct behaviours. A failure at any internal step is reported as "the whole test failed", and the next CI re-run may pass for a different reason than the original failure — making flakes hard to diagnose.
- Shared helpers in
e2e/testFunctions.ts (testFirstPagePagination:525, filterAndTestLastPagePagination:558, testPaginationContent:684) use class-based locators and rely on implicit waits between clicks.
test.fail() inside a conditional (current line 28) — flips the expected outcome based on runtime data; if the helper returns falsy for an unrelated reason, the test passes by failing, which masks real regressions.
- No content-change polling between pagination clicks — the legacy helpers either click and immediately assert, or use
waitForLoadState("load") which doesn't track the post-fetch row swap.
Proposed fix
Rewrite e2e/anvil/anvil-pagination.spec.ts using two references:
- Prior rewrite attempt:
fran/tests-pagination branch — keep its overall structure and the triggerActionAndWaitForUpdate content-poll pattern, which is the right primitive for pagination assertions.
- Code-style standard:
e2e/anvil/anvil-filters.spec.ts — apply its layout, helper conventions, and locator discipline.
Keep from the prior rewrite
- Per-behaviour tests instead of three monolithic tests — e.g.
shows page counter on first load, back disabled / next enabled on first page, page increments on each navigation, table content differs on every page, total-pages stays constant while navigating, forward button disabled on last page, updates total pages after applying a filter.
- Test-id locators:
TEST_IDS.TABLE_PAGINATION, TEST_IDS.TABLE_PAGINATION_RESULTS, TEST_IDS.TABLE_FIRST_CELL, TEST_IDS.FILTER_ITEM, TEST_IDS.FILTER_COUNT, TEST_IDS.SEARCH_ALL_FILTERS (all already defined in e2e/features/common/constants.ts).
triggerActionAndWaitForUpdate — click then expect.poll(() => locator.textContent()).not.toEqual(prev) to wait for the row swap rather than relying on waitForLoadState.
getFilterWithCountInRange helper to find a filter dynamically rather than hardcoding option names that may not exist in every dataset slice.
getPaginationRegex / getTotalPages parse helpers for the "Page X of Y" string.
Adjust to match the anvil-filters standard
- Use current constant names:
MUI_CLASSES (not MUI_CLASS), KEYBOARD_KEYS (not KEYBOARD_KEY) — the prior branch predates the rename.
- Replace hardcoded test-id strings like
getByTestId("search-all-filters") with getByTestId(TEST_IDS.SEARCH_ALL_FILTERS).
- Drop module-level
let mutated in beforeEach. anvil-filters re-derives locators per-test via helper functions (getFilters(page), filterPopover(page), etc.). Mirror that: getPagination(page), paginationButtons(page). Avoids cross-test state and lifecycle ordering surprises.
- Remove the commented-out
urlOrPredicate block at the bottom of the prior file — dead code.
- Move helpers to a top-level alphabetised block below a
/* ——— helpers ——— */ divider, matching anvil-filters.spec.ts:201. None of the helpers should be exported (the prior branch exports getFilterWithCountInRange and openSearchAllFilters unnecessarily).
- Use
dispatchEvent('click') for filter-item clicks within the popover/search-all-filters dropdown — matches the webkit/overlay workaround already adopted across the filter helpers in e2e/features/common/filters.ts.
- Re-use shared helpers from
e2e/features/common/filters.ts where applicable — e.g. closeAutocompletePopper(page) instead of a raw page.keyboard.press(KEYBOARD_KEYS.ESCAPE); namedFilterItem if the test wants to re-locate by name after a click.
- Lift magic numbers to constants at the top of the file — page size (
25, currently inline in Math.ceil(count / 25)), the iteration counts (for (let i = 0; i < 3; ...)), and the filter-count range bounds (min = 25, max = 120).
- Top-of-file constants block in the style of the
ENTITIES / FACET_NAMES block in anvil-filters.spec.ts:17-25.
- Drop conditional
test.fail() — every test should assert deterministic outcomes.
Acceptance criteria
e2e/anvil/anvil-pagination.spec.ts runs reliably across chromium, firefox, and webkit (no flakes across multiple CI runs).
- Every locator uses
getByTestId, getByRole, or a container-scoped role/text query — no top-level .Mui* class selectors.
- No dependency on the legacy
testFirstPagePagination / filterAndTestLastPagePagination / testPaginationContent helpers in e2e/testFunctions.ts. (If no other spec uses them after this rewrite, remove them.)
- Tests are split per-behaviour; each test name describes the single property under assertion.
- Helpers and constants follow the layout conventions used in
anvil-filters.spec.ts (alphabetised, non-exported, below a /* ——— helpers ——— */ divider, with a top-of-file constants block).
- Page transitions are guarded by content-change polling (
triggerActionAndWaitForUpdate or equivalent), not waitForLoadState.
Reference
Problem
The anvil-cmg pagination e2e spec (
e2e/anvil/anvil-pagination.spec.ts) is the last anvil suite still using the legacy shared helpers ine2e/testFunctions.ts(testFirstPagePagination,filterAndTestLastPagePagination,testPaginationContent). These helpers are the same family that produced flakes in #4800, #4751, #4656, and #4828 — class-/id-based locators, genericwaitForLoadStatebetween interactions, and consolidated multi-assertion tests that obscure which step actually failed.The current spec also bundles unrelated behaviour into three sweeping tests, so a single timing flake masks itself as a single test failure with little signal about the actual broken step.
Likely root cause (of the legacy spec's brittleness)
e2e/testFunctions.ts(testFirstPagePagination:525,filterAndTestLastPagePagination:558,testPaginationContent:684) use class-based locators and rely on implicit waits between clicks.test.fail()inside a conditional (current line 28) — flips the expected outcome based on runtime data; if the helper returns falsy for an unrelated reason, the test passes by failing, which masks real regressions.waitForLoadState("load")which doesn't track the post-fetch row swap.Proposed fix
Rewrite
e2e/anvil/anvil-pagination.spec.tsusing two references:fran/tests-paginationbranch — keep its overall structure and thetriggerActionAndWaitForUpdatecontent-poll pattern, which is the right primitive for pagination assertions.e2e/anvil/anvil-filters.spec.ts— apply its layout, helper conventions, and locator discipline.Keep from the prior rewrite
shows page counter on first load,back disabled / next enabled on first page,page increments on each navigation,table content differs on every page,total-pages stays constant while navigating,forward button disabled on last page,updates total pages after applying a filter.TEST_IDS.TABLE_PAGINATION,TEST_IDS.TABLE_PAGINATION_RESULTS,TEST_IDS.TABLE_FIRST_CELL,TEST_IDS.FILTER_ITEM,TEST_IDS.FILTER_COUNT,TEST_IDS.SEARCH_ALL_FILTERS(all already defined ine2e/features/common/constants.ts).triggerActionAndWaitForUpdate— click thenexpect.poll(() => locator.textContent()).not.toEqual(prev)to wait for the row swap rather than relying onwaitForLoadState.getFilterWithCountInRangehelper to find a filter dynamically rather than hardcoding option names that may not exist in every dataset slice.getPaginationRegex/getTotalPagesparse helpers for the "Page X of Y" string.Adjust to match the anvil-filters standard
MUI_CLASSES(notMUI_CLASS),KEYBOARD_KEYS(notKEYBOARD_KEY) — the prior branch predates the rename.getByTestId("search-all-filters")withgetByTestId(TEST_IDS.SEARCH_ALL_FILTERS).letmutated inbeforeEach. anvil-filters re-derives locators per-test via helper functions (getFilters(page),filterPopover(page), etc.). Mirror that:getPagination(page),paginationButtons(page). Avoids cross-test state and lifecycle ordering surprises.urlOrPredicateblock at the bottom of the prior file — dead code./* ——— helpers ——— */divider, matchinganvil-filters.spec.ts:201. None of the helpers should beexported (the prior branch exportsgetFilterWithCountInRangeandopenSearchAllFiltersunnecessarily).dispatchEvent('click')for filter-item clicks within the popover/search-all-filters dropdown — matches the webkit/overlay workaround already adopted across the filter helpers ine2e/features/common/filters.ts.e2e/features/common/filters.tswhere applicable — e.g.closeAutocompletePopper(page)instead of a rawpage.keyboard.press(KEYBOARD_KEYS.ESCAPE);namedFilterItemif the test wants to re-locate by name after a click.25, currently inline inMath.ceil(count / 25)), the iteration counts (for (let i = 0; i < 3; ...)), and the filter-count range bounds (min = 25, max = 120).ENTITIES/FACET_NAMESblock inanvil-filters.spec.ts:17-25.test.fail()— every test should assert deterministic outcomes.Acceptance criteria
e2e/anvil/anvil-pagination.spec.tsruns reliably across chromium, firefox, and webkit (no flakes across multiple CI runs).getByTestId,getByRole, or a container-scoped role/text query — no top-level.Mui*class selectors.testFirstPagePagination/filterAndTestLastPagePagination/testPaginationContenthelpers ine2e/testFunctions.ts. (If no other spec uses them after this rewrite, remove them.)anvil-filters.spec.ts(alphabetised, non-exported, below a/* ——— helpers ——— */divider, with a top-of-file constants block).triggerActionAndWaitForUpdateor equivalent), notwaitForLoadState.Reference
e2e/anvil/anvil-pagination.spec.tse2e/anvil/anvil-filters.spec.tse2e/features/common/filters.ts,e2e/features/common/constants.ts