mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-25 13:03:59 -05:00
1293 lines
37 KiB
TypeScript
1293 lines
37 KiB
TypeScript
import { NZAP_TEST_ID } from "~/db/seed/constants";
|
||
import { ADMIN_DISCORD_ID } from "~/features/admin/admin-constants";
|
||
import { updateNoScreenSchema } from "~/features/settings/settings-schemas";
|
||
import {
|
||
NOTIFICATIONS_URL,
|
||
SETTINGS_PAGE,
|
||
tournamentAdminPage,
|
||
tournamentBracketsPage,
|
||
tournamentMatchPage,
|
||
tournamentPage,
|
||
tournamentTeamsPage,
|
||
userResultsPage,
|
||
} from "~/utils/urls";
|
||
import {
|
||
expect,
|
||
impersonate,
|
||
isNotVisible,
|
||
navigate,
|
||
seed,
|
||
selectUser,
|
||
startBracket,
|
||
submit,
|
||
test,
|
||
waitForPOSTResponse,
|
||
} from "./helpers/playwright";
|
||
import { createFormHelpers } from "./helpers/playwright-form";
|
||
import {
|
||
backToBracket,
|
||
expectScore,
|
||
goToTab,
|
||
navigateToMatch,
|
||
reportResult,
|
||
undoLastReport,
|
||
} from "./helpers/tournament-match";
|
||
|
||
test.describe("Tournament bracket", () => {
|
||
test("sets active roster as regular member", async ({ page }) => {
|
||
const tournamentId = 1;
|
||
// User 37 is owner of team 10 (seed 10) which has 5 players
|
||
// Team 10 vs Team 9 (seed 9) is match 2 in WB Round 1
|
||
const matchId = 2;
|
||
await startBracket(page, tournamentId);
|
||
|
||
await impersonate(page, 37);
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
|
||
await expect(page.getByTestId("active-roster-needed-text")).toBeVisible();
|
||
|
||
// Team 10 (5 players) is opponentTwo in match 2 → bravo side.
|
||
// The roster tab opens in editing mode by default when active roster is missing.
|
||
await goToTab(page, "rosters");
|
||
await page.getByTestId("player-checkbox-bravo-0").click();
|
||
await page.getByTestId("player-checkbox-bravo-1").click();
|
||
await page.getByTestId("player-checkbox-bravo-2").click();
|
||
await page.getByTestId("player-checkbox-bravo-3").click();
|
||
await submit(page, "save-active-roster-button-bravo");
|
||
|
||
// did it persist?
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
await isNotVisible(page.getByTestId("active-roster-needed-text"));
|
||
|
||
await goToTab(page, "rosters");
|
||
await page.getByTestId("edit-active-roster-button-bravo").click();
|
||
// Swap player 3 out for player 4
|
||
await page.getByTestId("player-checkbox-bravo-3").click();
|
||
await page.getByTestId("player-checkbox-bravo-4").click();
|
||
await submit(page, "save-active-roster-button-bravo");
|
||
|
||
await expect(
|
||
page.getByTestId("edit-active-roster-button-bravo"),
|
||
).toBeVisible();
|
||
});
|
||
|
||
// 1) Report winner of N-ZAP's first match
|
||
// 2) Report winner of the adjacent match by using admin powers
|
||
// 3) Report one match on the only losers side match available
|
||
// 4) Try to reopen N-ZAP's first match and fail
|
||
// 5) Undo score of first losers match
|
||
// 6) Try to reopen N-ZAP's first match and succeed
|
||
// 7) As N-ZAP, undo all scores and switch to different team sweeping
|
||
test("reports score and sees bracket update", async ({ page }) => {
|
||
test.slow();
|
||
const tournamentId = 2;
|
||
await startBracket(page);
|
||
|
||
await impersonate(page);
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
// 1)
|
||
await navigateToMatch(page, 5);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
// 2)
|
||
await impersonate(page);
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
await navigateToMatch(page, 6);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
// 3)
|
||
await navigateToMatch(page, 18);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, setEnds: false });
|
||
await backToBracket(page);
|
||
|
||
// 4)
|
||
await navigateToMatch(page, 5);
|
||
await goToTab(page, "admin");
|
||
await isNotVisible(page.getByTestId("reopen-match-button"));
|
||
await backToBracket(page);
|
||
|
||
// 5)
|
||
await navigateToMatch(page, 18);
|
||
await goToTab(page, "action");
|
||
await undoLastReport(page);
|
||
await expectScore(page, [0, 0]);
|
||
await backToBracket(page);
|
||
|
||
// 6)
|
||
await navigateToMatch(page, 5);
|
||
await goToTab(page, "admin");
|
||
await submit(page, "reopen-match-button");
|
||
await expectScore(page, [1, 0]);
|
||
|
||
// 7)
|
||
await impersonate(page, NZAP_TEST_ID);
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
await navigateToMatch(page, 5);
|
||
await goToTab(page, "action");
|
||
await undoLastReport(page);
|
||
await expectScore(page, [0, 0]);
|
||
await reportResult(page, { mapsToReport: 2, winner: 2 });
|
||
await backToBracket(page);
|
||
await expect(
|
||
page.locator("[data-round-id='5'] [data-participant-id='102']"),
|
||
).toBeVisible();
|
||
});
|
||
|
||
test("adds a sub mid tournament (from non checked in team)", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 1;
|
||
await startBracket(page, tournamentId);
|
||
|
||
// captain of the first team
|
||
await impersonate(page, 5);
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("add-sub-button").click();
|
||
await page.getByTestId("copy-invite-link-button").click();
|
||
|
||
const inviteLinkProd: string = await page.evaluate(
|
||
"navigator.clipboard.readText()",
|
||
);
|
||
const inviteLink = inviteLinkProd.replace(
|
||
"https://sendou.ink",
|
||
"http://localhost:6173",
|
||
);
|
||
|
||
await impersonate(page, NZAP_TEST_ID);
|
||
await navigate({
|
||
page,
|
||
url: inviteLink,
|
||
});
|
||
|
||
await submit(page);
|
||
await expect(page).toHaveURL(/brackets/);
|
||
});
|
||
|
||
test("completes and finalizes a small tournament with badge assigning", async ({
|
||
page,
|
||
}) => {
|
||
test.slow();
|
||
|
||
const tournamentId = 2;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_OUT");
|
||
|
||
for (let id = 103; id < 117; id++) {
|
||
await page.getByLabel("Team", { exact: true }).selectOption(String(id));
|
||
await submit(page);
|
||
}
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await page.getByTestId("finalize-tournament-button").click();
|
||
|
||
await page.getByLabel("Receiving team").first().selectOption("101");
|
||
await page.getByLabel("Receiving team").last().selectOption("102");
|
||
|
||
await submit(page, "confirm-button");
|
||
|
||
await page.getByTestId("results-tab").click();
|
||
// seed performance rating shows up after tournament is finalized
|
||
await expect(page.getByTestId("spr-header")).toBeVisible();
|
||
|
||
await navigate({
|
||
page,
|
||
url: userResultsPage({ discordId: ADMIN_DISCORD_ID }),
|
||
});
|
||
|
||
await expect(
|
||
page.getByTestId("tournament-name-cell").getByText("In The Zone 22"),
|
||
).toBeVisible();
|
||
|
||
await navigate({
|
||
page,
|
||
url: NOTIFICATIONS_URL,
|
||
});
|
||
|
||
await expect(page.getByTestId("notification-item").first()).toContainText(
|
||
"New badge",
|
||
);
|
||
});
|
||
|
||
test("completes and finalizes a small tournament (RR->SE w/ underground bracket)", async ({
|
||
page,
|
||
}) => {
|
||
test.setTimeout(150_000);
|
||
|
||
const tournamentId = 3;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_OUT");
|
||
|
||
for (let id = 202; id < 210; id++) {
|
||
await page.getByLabel("Team", { exact: true }).selectOption(String(id));
|
||
await submit(page);
|
||
}
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({
|
||
tournamentId,
|
||
}),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
for (const id of [2, 4, 6, 7, 8, 9, 10, 11, 12]) {
|
||
await navigateToMatch(page, id);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
}
|
||
|
||
// captain of one of the underground bracket teams
|
||
await impersonate(page, 57);
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByRole("tab", { name: "Underground" }).click();
|
||
await submit(page, "check-in-bracket-button");
|
||
|
||
await impersonate(page);
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_IN");
|
||
await page.getByLabel("Team", { exact: true }).selectOption("216");
|
||
await page
|
||
.getByLabel("Bracket", { exact: true })
|
||
.selectOption("Underground bracket");
|
||
await submit(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId, bracketIdx: 2 }),
|
||
});
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 13);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 3 });
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId, bracketIdx: 1 }),
|
||
});
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
for (const matchId of [14, 15, 16, 17]) {
|
||
await navigateToMatch(page, matchId);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 3 });
|
||
|
||
await backToBracket(page);
|
||
}
|
||
await page.getByTestId("finalize-tournament-button").click();
|
||
await page.getByTestId("assign-badges-later-switch").click();
|
||
await submit(page, "confirm-button");
|
||
|
||
// after finalizing the tournament, the admin tab disappears so the
|
||
// reopen action is no longer reachable
|
||
await navigateToMatch(page, 14);
|
||
await isNotVisible(page.getByRole("tab", { name: "Admin" }));
|
||
await isNotVisible(page.getByTestId("reopen-match-button"));
|
||
await backToBracket(page);
|
||
});
|
||
|
||
test("shows tournament results on user profile after finalized tournament", async ({
|
||
page,
|
||
}) => {
|
||
test.slow();
|
||
const tournamentId = 4;
|
||
|
||
await seed(page, "SMALL_SOS");
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_OUT");
|
||
for (const teamId of ["303", "304"]) {
|
||
await page.getByLabel("Team", { exact: true }).selectOption(teamId);
|
||
await submit(page);
|
||
}
|
||
|
||
await page.getByTestId("edit-event-info-button").click();
|
||
for (let i = 0; i < 3; i++) {
|
||
await page.getByTestId("delete-bracket-button").last().click();
|
||
}
|
||
await page.getByTestId("placements-input").last().fill("1,2");
|
||
await submit(page);
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await page.getByRole("tab", { name: "Great White" }).click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 2);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 3 });
|
||
await backToBracket(page);
|
||
|
||
await page.getByTestId("finalize-tournament-button").click();
|
||
await page.getByRole("button", { name: "Finalize" }).click();
|
||
|
||
await page.getByTestId("results-tab").click();
|
||
await page.getByTestId("result-team-name").first().click();
|
||
await page.getByTestId("team-member-name").first().click();
|
||
|
||
await page.getByTestId("user-seasons-tab").click();
|
||
await expect(page.getByTestId("seasons-tournament-result")).toBeVisible();
|
||
|
||
await page.getByTestId("user-results-tab").click();
|
||
await expect(
|
||
page.getByTestId("tournament-name-cell").first(),
|
||
).toContainText("Swim or Sink 101");
|
||
|
||
await page.getByTestId("mates-button").first().click();
|
||
await expect(
|
||
page.locator('[data-testid="mates-cell-placement-0"] li'),
|
||
).toHaveCount(3);
|
||
});
|
||
|
||
test("changes SOS format and progresses with it & adds a member to another team", async ({
|
||
page,
|
||
}) => {
|
||
test.slow();
|
||
const tournamentId = 4;
|
||
|
||
await seed(page, "SMALL_SOS");
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("edit-event-info-button").click();
|
||
await page.getByTestId("delete-bracket-button").last().click();
|
||
await page.getByTestId("placements-input").last().fill("3,4");
|
||
|
||
await submit(page);
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
for (const matchId of [1, 2, 3, 4, 5, 6]) {
|
||
await navigateToMatch(page, matchId);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
}
|
||
|
||
await page.getByRole("tab", { name: "Hammerhead" }).click();
|
||
await isNotVisible(page.getByTestId("brackets-viewer"));
|
||
|
||
await page.getByRole("tab", { name: "Mako" }).click();
|
||
await expect(page.getByTestId("brackets-viewer")).toBeVisible();
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 7);
|
||
await expect(page.getByTestId("back-to-bracket-button")).toBeVisible();
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
await page.getByLabel("Action").selectOption("ADD_MEMBER");
|
||
await page.getByLabel("Team", { exact: true }).selectOption("303"); // a team in the Mako bracket
|
||
await selectUser({
|
||
labelName: "User",
|
||
userName: "Sendou",
|
||
page,
|
||
});
|
||
await submit(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentTeamsPage(tournamentId),
|
||
});
|
||
|
||
await expect(
|
||
page.getByTestId("team-member-name").getByText("Sendou"),
|
||
).toHaveCount(2);
|
||
});
|
||
|
||
test("conducts a tournament with many starting brackets", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 4;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("edit-event-info-button").click();
|
||
await page.getByTestId("delete-bracket-button").last().click();
|
||
|
||
for (const toggle of await page
|
||
.getByTestId("follow-up-bracket-switch")
|
||
.all()) {
|
||
await toggle.click();
|
||
}
|
||
|
||
await page.getByLabel("Format").first().selectOption("Single-elimination");
|
||
await page.getByLabel("Format").nth(1).selectOption("Single-elimination");
|
||
await page.getByLabel("Format").nth(2).selectOption("Swiss");
|
||
await page.getByLabel("Format").nth(3).selectOption("Swiss");
|
||
|
||
await submit(page);
|
||
|
||
await page.getByText("Seeds").click();
|
||
await page.getByTestId("set-starting-brackets").click();
|
||
|
||
for (let i = 0; i < 16; i++) {
|
||
let bracketName: string;
|
||
if (i < 4) {
|
||
bracketName = "Groups stage";
|
||
} else if (i < 8) {
|
||
bracketName = "Great White";
|
||
} else if (i < 12) {
|
||
bracketName = "Hammerhead";
|
||
} else {
|
||
bracketName = "Mako";
|
||
}
|
||
|
||
await page
|
||
.getByTestId("starting-bracket-select")
|
||
.nth(i)
|
||
.selectOption(bracketName);
|
||
}
|
||
|
||
await submit(page, "set-starting-brackets-submit-button");
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
for (const bracketName of [
|
||
"Groups stage",
|
||
"Great White",
|
||
"Hammerhead",
|
||
"Mako",
|
||
]) {
|
||
await page.getByRole("tab", { name: bracketName }).click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
}
|
||
|
||
await expect(page.locator('[data-match-id="11"]')).toBeVisible();
|
||
});
|
||
|
||
test("organizer edits a match after it is done", async ({ page }) => {
|
||
const tournamentId = 3;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 2);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
|
||
await goToTab(page, "admin");
|
||
await page.getByTestId("edit-result-0-button").click();
|
||
// Swap player 3 out for player 4 on the alpha (winner) team
|
||
await page.getByTestId("edit-result-player-checkbox-alpha-3").click();
|
||
await page.getByTestId("edit-result-player-checkbox-alpha-4").click();
|
||
// Toggle KO so we can verify the edit went through (RR collects KO).
|
||
await page.getByLabel("KO").check();
|
||
await submit(page, "save-result-0-button");
|
||
|
||
// Edit returns to read-only view, now showing the KO label
|
||
await expect(page.getByTestId("edit-result-0-button")).toBeVisible();
|
||
await expect(page.getByText(/\(KO\)/).first()).toBeVisible();
|
||
});
|
||
|
||
test("changes to picked map pool & best of", async ({ page }) => {
|
||
const tournamentId = 4;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("edit-event-info-button").click();
|
||
|
||
await page.getByRole("button", { name: "Clear" }).click();
|
||
await page.getByLabel("Template").selectOption("preset:CB");
|
||
|
||
await submit(page);
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await page.getByTestId("increase-map-count-button").first().click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
// Bo5 of clam blitz: one mode icon + ×5 count text
|
||
await expect(page.getByTestId("mode-progress-CB")).toBeVisible();
|
||
await expect(page.getByText("×5")).toBeVisible();
|
||
});
|
||
|
||
test("reopens round robin match and changes score", async ({ page }) => {
|
||
test.slow();
|
||
const tournamentId = 3;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
// needs also to be completed so 9 unlocks
|
||
await navigateToMatch(page, 7);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
// set situation where match A is completed and its participants also completed their follow up matches B & C
|
||
// and then we go back and change the winner of A
|
||
await navigateToMatch(page, 8);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 9);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 10);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 8);
|
||
await goToTab(page, "admin");
|
||
await submit(page, "reopen-match-button");
|
||
await goToTab(page, "action");
|
||
await undoLastReport(page);
|
||
await reportResult(page, {
|
||
mapsToReport: 2,
|
||
winner: 2,
|
||
setEnds: true,
|
||
});
|
||
});
|
||
|
||
test("reopening round robin match does not lock already-unlocked matches (issue #2690)", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 3;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
// Use Group B which has 4 teams and 2 matches per round
|
||
// Group B Round 1: Match 7 (Whatcha Say vs Come Together), Match 8 (We Are Champions vs Please Mr Postman)
|
||
// Group B Round 2: Match 9 (Please Mr Postman vs Come Together), Match 10 (Whatcha Say vs We Are Champions)
|
||
|
||
// Complete R1 matches in group B (matches 7 and 8) to unlock R2 matches
|
||
await navigateToMatch(page, 7);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 8);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
// Match 9 is R2 in group B - should now be unlocked since R1 is complete
|
||
// Start it but don't complete it
|
||
await navigateToMatch(page, 9);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, setEnds: false });
|
||
await backToBracket(page);
|
||
|
||
// Reopen match 7 (R1 match) - simulating a score misreport correction
|
||
await navigateToMatch(page, 7);
|
||
await goToTab(page, "admin");
|
||
await submit(page, "reopen-match-button");
|
||
await backToBracket(page);
|
||
|
||
// Verify the R2 match that was already in progress is still playable
|
||
// Before the fix, this would become locked and unplayable
|
||
await navigateToMatch(page, 9);
|
||
await expectScore(page, [1, 0]);
|
||
await goToTab(page, "action");
|
||
await expect(page.getByTestId("winner-radio-1")).toBeVisible();
|
||
});
|
||
|
||
test("locks/unlocks matches & sets match as casted", async ({ page }) => {
|
||
test.slow();
|
||
|
||
const tournamentId = 2;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentPage(tournamentId),
|
||
});
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_OUT");
|
||
|
||
for (let id = 103; id < 115; id++) {
|
||
await page.getByLabel("Team", { exact: true }).selectOption(String(id));
|
||
await submit(page);
|
||
}
|
||
|
||
await page.getByLabel("Twitch accounts").fill("test");
|
||
await submit(page, "save-cast-twitch-accounts-button");
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 3);
|
||
await goToTab(page, "admin");
|
||
// Picking a chip auto-submits the cast channel; lock the match afterwards.
|
||
await waitForPOSTResponse(page, async () => {
|
||
await page.locator('label[for$="-test"]').click();
|
||
});
|
||
await submit(page, "cast-info-submit-button");
|
||
await backToBracket(page);
|
||
|
||
await navigateToMatch(page, 2);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
await expect(page.getByText("🔒 CAST")).toBeVisible();
|
||
await navigateToMatch(page, 3);
|
||
await goToTab(page, "admin");
|
||
// Lock state is signalled by the toggle being "Unlock" instead of "Lock"
|
||
await expect(page.getByRole("button", { name: "Unlock" })).toBeVisible();
|
||
await submit(page, "cast-info-submit-button");
|
||
await expect(page.getByTestId("stage-banner")).toBeVisible();
|
||
|
||
// Cast channel "test" persists across unlock; the bracket badge flips
|
||
// from 🔒 CAST to 🔴 LIVE once the match is unlocked and ongoing.
|
||
await backToBracket(page);
|
||
await expect(page.getByText("🔴 LIVE")).toBeVisible();
|
||
});
|
||
|
||
test("resets bracket", async ({ page }) => {
|
||
const tournamentId = 1;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await isNotVisible(page.locator('[data-match-id="1"]'));
|
||
await navigateToMatch(page, 2);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
await page
|
||
.getByLabel('Type bracket name ("Main bracket") to confirm')
|
||
.fill("Main bracket");
|
||
await submit(page, "reset-bracket-button");
|
||
|
||
await page.getByLabel("Action").selectOption("CHECK_IN");
|
||
await page.getByLabel("Team", { exact: true }).selectOption("1");
|
||
await submit(page);
|
||
|
||
await page.getByTestId("brackets-tab").click();
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
// bye is gone
|
||
await expect(page.locator('[data-match-id="1"]')).toBeVisible();
|
||
});
|
||
|
||
test("user no screen setting affects tournament match", async ({ page }) => {
|
||
const tournamentId = 4;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: SETTINGS_PAGE,
|
||
});
|
||
|
||
const form = createFormHelpers(page, updateNoScreenSchema);
|
||
await form.check("newValue");
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
await expect(page.getByTestId("screen-banned")).toBeVisible();
|
||
|
||
await backToBracket(page);
|
||
await navigateToMatch(page, 2);
|
||
await expect(page.getByTestId("screen-allowed")).toBeVisible();
|
||
});
|
||
|
||
test("hosts a 'play all' round robin stage", async ({ page }) => {
|
||
const tournamentId = 4;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await page
|
||
.getByLabel("Count type", { exact: true })
|
||
.selectOption("PLAY_ALL");
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigateToMatch(page, 1);
|
||
await expect(page.getByText("Play all 3")).toBeVisible();
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 3 });
|
||
});
|
||
|
||
test("swiss tournament with bracket advancing/unadvancing & dropping out a team", async ({
|
||
page,
|
||
}) => {
|
||
test.slow();
|
||
|
||
const tournamentId = 5;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
// report all group A round 1 scores
|
||
for (const id of [1, 2, 3, 4]) {
|
||
await navigateToMatch(page, id);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
}
|
||
|
||
// test that we can change to view different group
|
||
await expect(page.getByTestId("start-round-button")).toBeVisible();
|
||
await page.getByTestId("group-B-button").click();
|
||
await isNotVisible(page.getByTestId("start-round-button"));
|
||
await page.getByTestId("group-A-button").click();
|
||
|
||
await submit(page, "start-round-button");
|
||
await expect(page.locator(`[data-match-id="9"]`)).toBeVisible();
|
||
|
||
await page.getByTestId("admin-tab").click();
|
||
|
||
await page.getByLabel("Action").selectOption("DROP_TEAM_OUT");
|
||
await page.getByLabel("Team", { exact: true }).selectOption("401");
|
||
await submit(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("reset-round-button").click();
|
||
await submit(page, "confirm-button");
|
||
await submit(page, "start-round-button");
|
||
await expect(page.getByTestId("bye-team")).toBeVisible();
|
||
});
|
||
|
||
test("prepares maps (including third place match linking)", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 4;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByRole("tab", { name: "Great White" }).click();
|
||
|
||
await page.getByTestId("prepare-maps-button").click();
|
||
|
||
await page.getByLabel("Expected teams").selectOption("8");
|
||
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByRole("tab", { name: "Great White" }).click();
|
||
|
||
await expect(page.getByTestId("prepared-maps-check-icon")).toBeVisible();
|
||
|
||
// we did not prepare maps for group stage
|
||
await page.getByRole("tab", { name: "Groups stage" }).click();
|
||
|
||
await isNotVisible(page.getByTestId("prepared-maps-check-icon"));
|
||
|
||
// should reuse prepared maps from Great White
|
||
await page.getByRole("tab", { name: "Hammerhead" }).click();
|
||
|
||
await expect(page.getByTestId("prepared-maps-check-icon")).toBeVisible();
|
||
|
||
// finally, test third place match linking
|
||
await page.getByRole("tab", { name: "Great White" }).click();
|
||
|
||
await page.getByTestId("prepare-maps-button").click();
|
||
|
||
await page.getByTestId("unlink-finals-3rd-place-match-button").click();
|
||
|
||
await page.getByTestId("increase-map-count-button").last().click();
|
||
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByRole("tab", { name: "Great White" }).click();
|
||
|
||
await page.getByTestId("prepare-maps-button").click();
|
||
|
||
// link button should be visible because we unlinked and made finals and third place match maps different earlier
|
||
await expect(
|
||
page.getByTestId("link-finals-3rd-place-match-button"),
|
||
).toBeVisible();
|
||
});
|
||
|
||
for (const pickBan of ["COUNTERPICK", "BAN_2"]) {
|
||
test(`ban/pick ${pickBan}`, async ({ page }) => {
|
||
const tournamentId = 4;
|
||
const matchId = 2;
|
||
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await page.getByLabel("Pick/ban").selectOption(pickBan);
|
||
|
||
await submit(page, "confirm-finalize-bracket-button");
|
||
|
||
const teamOneCaptainId = 33;
|
||
const teamTwoCaptainId = 29;
|
||
|
||
if (pickBan === "BAN_2") {
|
||
for (const id of [teamTwoCaptainId, teamOneCaptainId]) {
|
||
await impersonate(page, id);
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
await goToTab(page, "action");
|
||
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
}
|
||
|
||
// once both teams banned the ban prompt is gone and the actual map
|
||
// banner takes over.
|
||
await expect(page.getByTestId("stage-banner")).toBeVisible();
|
||
}
|
||
|
||
await impersonate(page, teamOneCaptainId);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, winner: 2, setEnds: false });
|
||
|
||
if (pickBan === "COUNTERPICK") {
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
}
|
||
|
||
await impersonate(page, teamTwoCaptainId);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, winner: 1, setEnds: false });
|
||
|
||
if (pickBan === "COUNTERPICK") {
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
await undoLastReport(page);
|
||
await expect(page.getByText("Select the winner")).toBeVisible();
|
||
await reportResult(page, {
|
||
mapsToReport: 1,
|
||
winner: 1,
|
||
setEnds: false,
|
||
});
|
||
await page.getByTestId("pick-ban-button").last().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
await expect(
|
||
page.getByText("Counterpick", { exact: true }),
|
||
).toBeVisible();
|
||
await expect(page.getByText("1-1")).toBeVisible();
|
||
}
|
||
});
|
||
}
|
||
|
||
test("can end set early when past time limit and shows timer on bracket and match page", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 2;
|
||
const matchId = 5;
|
||
|
||
await startBracket(page, tournamentId);
|
||
await navigateToMatch(page, matchId);
|
||
|
||
await page.clock.install({ time: new Date() });
|
||
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, winner: 1, setEnds: false });
|
||
|
||
await expect(page.getByTestId("match-timer")).toBeVisible();
|
||
|
||
await backToBracket(page);
|
||
|
||
const bracketMatch = page.locator('[data-match-id="5"]');
|
||
await expect(bracketMatch).toBeVisible();
|
||
|
||
// Verify timer shows on bracket page (timer is a sibling of the match link)
|
||
const matchWrapper = bracketMatch.locator("..");
|
||
await expect(matchWrapper.getByTestId("bracket-match-timer")).toBeVisible();
|
||
|
||
// Fast forward time past limit (30 minutes for Bo3 = 26min limit)
|
||
await page.clock.fastForward("30:00");
|
||
await page.reload();
|
||
|
||
await navigateToMatch(page, matchId);
|
||
|
||
await goToTab(page, "admin");
|
||
await page.getByRole("button", { name: "End set" }).click();
|
||
await page.getByRole("radio", { name: /Random/ }).check();
|
||
await submit(page, "end-set-button");
|
||
|
||
// Match is now finalized (no longer ongoing) → "Final" appears in banner
|
||
await expect(page.getByTestId("match-final")).toBeVisible();
|
||
});
|
||
|
||
test("dropping team out ends ongoing match early and auto-forfeits losers bracket match", async ({
|
||
page,
|
||
}) => {
|
||
const tournamentId = 2;
|
||
|
||
await startBracket(page, tournamentId);
|
||
|
||
// 1) Report partial score on match 5 (winners bracket)
|
||
await navigateToMatch(page, 5);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, winner: 1, setEnds: false });
|
||
await backToBracket(page);
|
||
|
||
// 2) Drop team 102 (one of the teams in match 5) via admin
|
||
await navigate({
|
||
page,
|
||
url: tournamentAdminPage(tournamentId),
|
||
});
|
||
await page.getByLabel("Action").selectOption("DROP_TEAM_OUT");
|
||
await page.getByLabel("Team", { exact: true }).selectOption("102");
|
||
await submit(page);
|
||
|
||
// 3) Verify the ongoing match ended early (no longer ongoing → "Final")
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId: 5 }),
|
||
});
|
||
await expect(page.getByTestId("match-final")).toBeVisible();
|
||
await backToBracket(page);
|
||
|
||
// 4) Complete the adjacent match (match 6) so its loser goes to losers bracket
|
||
await navigateToMatch(page, 6);
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 2 });
|
||
await backToBracket(page);
|
||
|
||
// 5) The losers bracket match (match 18) should now have teams:
|
||
// - Loser of match 5 (team 102, dropped)
|
||
// - Loser of match 6
|
||
// It should have ended early since team 102 is dropped
|
||
await navigateToMatch(page, 18);
|
||
await expect(page.getByTestId("match-final")).toBeVisible();
|
||
});
|
||
|
||
test("ban/pick CUSTOM flow", async ({ page }) => {
|
||
test.slow();
|
||
const tournamentId = 4;
|
||
const matchId = 2;
|
||
const higherSeedCaptainId = 29;
|
||
const lowerSeedCaptainId = 33;
|
||
|
||
const customFlow = {
|
||
preSet: [
|
||
{ action: "BAN", side: "HIGHER_SEED" },
|
||
{ action: "BAN", side: "HIGHER_SEED" },
|
||
{ action: "BAN", side: "LOWER_SEED" },
|
||
{ action: "BAN", side: "LOWER_SEED" },
|
||
{ action: "ROLL" },
|
||
],
|
||
postGame: [
|
||
{ action: "BAN", side: "WINNER" },
|
||
{ action: "BAN", side: "WINNER" },
|
||
{ action: "PICK", side: "LOSER" },
|
||
],
|
||
};
|
||
|
||
// 1) Start bracket with CUSTOM pick/ban flow
|
||
await seed(page);
|
||
await impersonate(page);
|
||
|
||
await navigate({
|
||
page,
|
||
url: tournamentBracketsPage({ tournamentId }),
|
||
});
|
||
|
||
await page.getByTestId("finalize-bracket-button").click();
|
||
await page.getByLabel("Pick/ban").selectOption("CUSTOM");
|
||
await expect(page.getByText("Before set")).toBeVisible();
|
||
|
||
await waitForPOSTResponse(page, async () => {
|
||
await page.evaluate((cfStr) => {
|
||
const input = document.querySelector(
|
||
'input[name="maps"]',
|
||
) as HTMLInputElement;
|
||
const maps = JSON.parse(input.value);
|
||
const cf = JSON.parse(cfStr);
|
||
for (const m of maps) {
|
||
if (m.pickBan === "CUSTOM") {
|
||
m.customFlow = cf;
|
||
}
|
||
}
|
||
input.value = JSON.stringify(maps);
|
||
|
||
const form = input.closest("form")!;
|
||
const btn = document.createElement("button");
|
||
btn.type = "submit";
|
||
btn.name = "_action";
|
||
btn.value = "START_BRACKET";
|
||
btn.style.display = "none";
|
||
form.appendChild(btn);
|
||
btn.click();
|
||
}, JSON.stringify(customFlow));
|
||
});
|
||
|
||
// 2) PreSet: Higher seed bans 2 maps
|
||
await impersonate(page, higherSeedCaptainId);
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
await goToTab(page, "action");
|
||
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
await expect(page.getByText(/Ban a map \(2\/2\)/)).toBeVisible();
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
// 3) PreSet: Lower seed bans 2 maps
|
||
await impersonate(page, lowerSeedCaptainId);
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
await goToTab(page, "action");
|
||
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
await expect(page.getByText(/Ban a map \(2\/2\)/)).toBeVisible();
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
// 4) Roll auto-executed after last ban; report game 1 score
|
||
await expect(page.getByTestId("stage-banner")).toBeVisible();
|
||
await goToTab(page, "action");
|
||
|
||
await reportResult(page, { mapsToReport: 1, winner: 1, setEnds: false });
|
||
await expectScore(page, [1, 0]);
|
||
|
||
// 5) PostGame: Winner (team 1, captain 33) bans 2 maps
|
||
await expect(page.getByText(/Ban a map/)).toBeVisible();
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
await expect(page.getByText(/Ban a map \(2\/2\)/)).toBeVisible();
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
// PostGame: Loser (team 2, captain 29) picks a map
|
||
await impersonate(page, higherSeedCaptainId);
|
||
await navigate({
|
||
page,
|
||
url: tournamentMatchPage({ tournamentId, matchId }),
|
||
});
|
||
await goToTab(page, "action");
|
||
|
||
await expect(page.getByText(/Pick a map/)).toBeVisible();
|
||
await page.getByTestId("pick-ban-button").first().click();
|
||
await submit(page, "pick-ban-submit-button");
|
||
|
||
// 6) Undo game 1 score — also deletes postGame pick/ban events
|
||
await expect(page.getByTestId("stage-banner")).toBeVisible();
|
||
await undoLastReport(page);
|
||
|
||
await expectScore(page, [0, 0]);
|
||
await expect(page.getByTestId("stage-banner")).toBeVisible();
|
||
|
||
// 7) Re-report game 1 and verify postGame cycle restarts
|
||
await goToTab(page, "action");
|
||
await reportResult(page, { mapsToReport: 1, winner: 1, setEnds: false });
|
||
await expectScore(page, [1, 0]);
|
||
|
||
await expect(page.getByText(/Ban a map/)).toBeVisible();
|
||
});
|
||
});
|