sendou.ink/e2e/tournament-bracket.spec.ts
Kalle 2b5b1b1948
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
New match page (#3032)
2026-05-04 18:15:10 +03:00

1293 lines
37 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
});
});