Fix E2E test

This commit is contained in:
Kalle
2026-08-22 15:43:16 +03:00
parent 54c931f4fa
commit ab2c323abf
2 changed files with 32 additions and 5 deletions

View File

@@ -22,6 +22,7 @@ import {
useFetchers,
useHref,
useLoaderData,
useLocation,
useMatches,
useNavigate,
useNavigation,
@@ -445,6 +446,7 @@ function HydrationTestIndicator() {
const navigation = useNavigation();
const revalidator = useRevalidator();
const fetchers = useFetchers();
const location = useLocation();
if (!isHydrated) return null;
@@ -469,6 +471,10 @@ function HydrationTestIndicator() {
data-testid="hydrated"
data-router-idle={routerIdle ? "true" : undefined}
data-router-busy={routerIdle ? undefined : busy.join(" | ")}
// the rendered search, trailing the browser's own by a commit: only
// once the toast params are gone from here have the forms keyed on
// the location (see SendouForm) finished remounting
data-location-search={location.search}
/>
);
}

View File

@@ -331,11 +331,15 @@ export async function submit(page: Page, target?: string | Locator) {
await button.click();
});
// Toast flash params are stripped right after via a replace navigation
// (without revalidation); wait for it so it can't abort a later click.
await page.waitForURL((url) => !/__(?:success|error)=/.test(url.href), {
timeout: 5_000,
});
// An action's toast redirect adds flash params that a replace navigation
// strips right after (without revalidation), remounting every form on the
// page twice. Waiting on the rendered search rather than the browser's URL
// covers the commit those remounts land in, which trails the history entry
// — otherwise the second remount tears down whatever the test opens next.
await page.waitForSelector(
'[data-testid="hydrated"]:not([data-location-search*="__success"]):not([data-location-search*="__error"])',
{ state: "attached", timeout: 5_000 },
);
}
export async function waitForPOSTResponse(page: Page, cb: () => Promise<void>) {
@@ -344,6 +348,22 @@ export async function waitForPOSTResponse(page: Page, cb: () => Promise<void>) {
const MAX_ATTEMPTS = 3;
const PER_ATTEMPT_TIMEOUT = 10_000;
// The busy marker only appears once React commits the submission, which on a
// loaded machine lands well after the POST left the browser. Watching for it
// from before the click keeps the idle of the *previous* render from being
// read as the action having settled below.
const routerWentBusy = page
.waitForFunction(
() =>
document
.querySelector('[data-testid="hydrated"]')
?.getAttribute("data-router-idle") !== "true",
undefined,
{ timeout: PER_ATTEMPT_TIMEOUT },
)
// a POST that no fetcher or navigation drives never turns the router busy
.catch(() => {});
// React Aria buttons fire their handler on press end. Occasionally a click
// registers the press start (the button goes `:active`) but the press never
// completes into a submit, so no POST fires — e.g. when a re-render lands
@@ -367,6 +387,7 @@ export async function waitForPOSTResponse(page: Page, cb: () => Promise<void>) {
// The POST's revalidation (and any redirect it drives) is still in flight;
// an interaction landing mid-flight aborts it, and routes that opt out of
// revalidation on navigation (e.g. to.$id) then keep the stale data.
await routerWentBusy;
await expectRouterIdle(page);
return response!;