mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 15:16:09 -05:00
Fix E2E tests
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
expect,
|
||||
type Locator,
|
||||
type Page,
|
||||
type Response,
|
||||
} from "@playwright/test";
|
||||
import { ADMIN_ID } from "~/features/admin/admin-constants";
|
||||
import type { SeedVariation } from "~/features/api-private/routes/seed";
|
||||
@@ -171,26 +172,82 @@ async function retryPost(
|
||||
}
|
||||
|
||||
export async function submit(page: Page, testId?: string) {
|
||||
const postRes = await waitForPOSTResponse(page, async () => {
|
||||
await page.getByTestId(testId ?? "submit-button").click();
|
||||
});
|
||||
// Started before the click because the data GET can land before awaiting the
|
||||
// POST response hands control back to us, and `page.waitForResponse` only
|
||||
// sees responses arriving after it is called.
|
||||
const dataGet = watchForDataGetAfterPOST(page);
|
||||
|
||||
// Remix returns 202 from action endpoints when the action threw/returned a
|
||||
// redirect. The fetcher then drives a client-side navigation and, once
|
||||
// that completes, fires a GET against the new route's data. If we return
|
||||
// before that GET fires, a subsequent Link click can be aborted mid-flight
|
||||
// by the queued navigation (ERR_ABORTED on the new route's .data fetch),
|
||||
// leaving the test on the old page.
|
||||
if (postRes.status() === 202) {
|
||||
await page.waitForResponse(
|
||||
(res) => res.request().method() === "GET" && res.url().includes(".data"),
|
||||
);
|
||||
try {
|
||||
const postRes = await waitForPOSTResponse(page, async () => {
|
||||
await page.getByTestId(testId ?? "submit-button").click();
|
||||
});
|
||||
|
||||
// Remix returns 202 from action endpoints when the action threw/returned a
|
||||
// redirect. The fetcher then drives a client-side navigation and, once
|
||||
// that completes, fires a GET against the new route's data. If we return
|
||||
// before that GET fires, a subsequent Link click can be aborted mid-flight
|
||||
// by the queued navigation (ERR_ABORTED on the new route's .data fetch),
|
||||
// leaving the test on the old page.
|
||||
if (postRes.status() !== 202) return;
|
||||
|
||||
await dataGet.fired;
|
||||
// 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 expect(page).not.toHaveURL(/__(?:success|error)=/);
|
||||
} finally {
|
||||
dataGet.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves once a route data GET follows the POST, without missing one that
|
||||
* arrives while the caller is still awaiting the POST response.
|
||||
*/
|
||||
function watchForDataGetAfterPOST(page: Page) {
|
||||
const TIMEOUT = 15_000;
|
||||
|
||||
let postSeen = false;
|
||||
let resolveFired: () => void = () => {};
|
||||
let rejectFired: (error: Error) => void = () => {};
|
||||
|
||||
const fired = new Promise<void>((resolve, reject) => {
|
||||
resolveFired = resolve;
|
||||
rejectFired = reject;
|
||||
});
|
||||
|
||||
const onResponse = (res: Response) => {
|
||||
if (!postSeen) {
|
||||
postSeen = res.request().method() === "POST";
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.request().method() === "GET" && res.url().includes(".data")) {
|
||||
resolveFired();
|
||||
}
|
||||
};
|
||||
page.on("response", onResponse);
|
||||
|
||||
const timeout = setTimeout(
|
||||
() =>
|
||||
rejectFired(
|
||||
new Error(
|
||||
`submit: no route data GET followed the redirecting POST within ${TIMEOUT}ms`,
|
||||
),
|
||||
),
|
||||
TIMEOUT,
|
||||
);
|
||||
|
||||
return {
|
||||
fired,
|
||||
stop: () => {
|
||||
clearTimeout(timeout);
|
||||
page.off("response", onResponse);
|
||||
// Nothing awaits `fired` when the POST wasn't a redirect
|
||||
resolveFired();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function waitForPOSTResponse(page: Page, cb: () => Promise<void>) {
|
||||
const MAX_ATTEMPTS = 3;
|
||||
const PER_ATTEMPT_TIMEOUT = 10_000;
|
||||
|
||||
@@ -195,7 +195,9 @@ test.describe("SendouQ match page", () => {
|
||||
await voteNo(page);
|
||||
|
||||
await expect(page.getByText("You declined to continue")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Rejoin queue" }).click();
|
||||
await waitForPOSTResponse(page, async () => {
|
||||
await page.getByRole("button", { name: "Rejoin queue" }).click();
|
||||
});
|
||||
await expect(page).toHaveURL(SENDOUQ_LOOKING_PAGE);
|
||||
});
|
||||
|
||||
|
||||
@@ -66,20 +66,19 @@ test.describe("VoDs page", () => {
|
||||
|
||||
await submit(page);
|
||||
|
||||
const now = new Date();
|
||||
const formattedDate = `${now.getMonth() + 1}/${now.getDate()}/${now.getFullYear()}`;
|
||||
await page.getByText(formattedDate).isVisible();
|
||||
await page.getByTestId("weapon-img-4001").isVisible();
|
||||
await page.getByTestId("weapon-img-6010").isVisible();
|
||||
const formattedDate = `${VIDEO_DATE.getMonth() + 1}/${VIDEO_DATE.getDate()}/${VIDEO_DATE.getFullYear()}`;
|
||||
await expect(page.getByText(formattedDate)).toBeVisible();
|
||||
await expect(page.getByTestId("weapon-img-4001")).toBeVisible();
|
||||
await expect(page.getByTestId("weapon-img-6010")).toBeVisible();
|
||||
|
||||
await page.getByTestId("copy-timestamps-button").click();
|
||||
await page.getByText("0:00 Intro").isVisible();
|
||||
await page
|
||||
.getByText("0:20 Zink Mini Splatling / TC Hammerhead Bridge")
|
||||
.isVisible();
|
||||
await page
|
||||
.getByText("5:55 Tenta Brella / RM Museum d'Alfonsino")
|
||||
.isVisible();
|
||||
await expect(page.getByRole("dialog").getByRole("textbox")).toHaveValue(
|
||||
[
|
||||
"0:00 Intro",
|
||||
"0:20 Zink Mini Splatling / TC Hammerhead Bridge",
|
||||
"5:55 Tenta Brella / RM Museum d'Alfonsino",
|
||||
].join("\n"),
|
||||
);
|
||||
});
|
||||
|
||||
test("adds video (cast)", async ({ page }) => {
|
||||
@@ -129,9 +128,9 @@ test.describe("VoDs page", () => {
|
||||
await submit(page);
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
await page
|
||||
.getByTestId(`weapon-img-${i < 4 ? 200 : 6010}-${i}`)
|
||||
.isVisible();
|
||||
await expect(
|
||||
page.getByTestId(`weapon-img-${i < 4 ? 200 : 6010}-${i}`),
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -155,7 +154,7 @@ test.describe("VoDs page", () => {
|
||||
|
||||
await expect(page).toHaveURL(vodVideoPage(1));
|
||||
|
||||
await page.getByTestId("weapon-img-200-4").isVisible();
|
||||
await expect(page.getByTestId("weapon-img-200")).toBeVisible();
|
||||
});
|
||||
|
||||
test("operates vod filters", async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user