Tournament bracket elimination reports score and sees bracket update Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});Create teams tournament.id, members N-ZAP , ...teamSeeds (3 ) → teams
const teams = await createTeams (factories, tournament.id, [
{ members: [NZAP_TEST_ID ] },
...teamSeeds (3 ),
]);Start bracket tournament tournament.id → matches
const matches = await factories.TournamentFactory .startBracket (
tournament.id,
);nzapsMatchId = matches[0 ].id
const nzapsMatchId = matches[0 ].id;adjacentMatchId = matches[1 ].id
const adjacentMatchId = matches[1 ].id;losersMatchId = matches[3 ].id
const losersMatchId = matches[3 ].id;Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);report winner of N-ZAP's first match
Open match nzapsMatchId → match
let match = await brackets.openMatch (nzapsMatchId);Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Act
report winner of the adjacent match using admin powers
Open match adjacentMatchId → match
match = await brackets.openMatch (adjacentMatchId); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Act
report one map of the only losers side match available
Open match losersMatchId → match
match = await brackets.openMatch (losersMatchId); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 set ends false
await match.reportResult ({ mapsToReport: 1 , setEnds: false });Back to bracket
await match.backToBracket ();Tournament brackets page Act
N-ZAP's first match can't be reopened while the losers match depends on it
Open match nzapsMatchId → match
match = await brackets.openMatch (nzapsMatchId); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );Assert
reopen match button is not visible
await isNotVisible (match.locators.reopenMatchButton);Act
Back to bracket
await match.backToBracket ();Tournament brackets page Act
undo the losers match score
Open match losersMatchId → match
match = await brackets.openMatch (losersMatchId); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Undo last report
await match.undoLastReport ();Assert
score 0 , 0 is visible
await expect (match.score ([0 , 0 ])).toBeVisible ();Act
Back to bracket
await match.backToBracket ();Tournament brackets page Act
Open match nzapsMatchId → match
match = await brackets.openMatch (nzapsMatchId); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );Reopen
await match.reopen ();Assert
score 1 , 0 is visible
await expect (match.score ([1 , 0 ])).toBeVisible ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open match nzapsMatchId → match
match = await brackets.openMatch (nzapsMatchId); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Undo last report
await match.undoLastReport ();Assert
score 0 , 0 is visible
await expect (match.score ([0 , 0 ])).toBeVisible ();Act
Report result maps to report 2 winner 2
await match.reportResult ({ mapsToReport: 2 , winner: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Assert
participant in round 3 , teams[0 ].id is visible
await expect (
brackets.participantInRound (DE_LOSERS_ROUND_ID , teams[0 ].id),
).toBeVisible ();Source test ("reports score and sees bracket update" , async ({ page, factories }) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});
const teams = await createTeams (factories, tournament.id, [
{ members: [NZAP_TEST_ID ] },
...teamSeeds (3 ),
]);
const matches = await factories.TournamentFactory .startBracket (
tournament.id,
);
const nzapsMatchId = matches[0 ].id;
const adjacentMatchId = matches[1 ].id;
const losersMatchId = matches[3 ].id;
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
// report winner of N-ZAP's first match
let match = await brackets.openMatch (nzapsMatchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
// report winner of the adjacent match using admin powers
match = await brackets.openMatch (adjacentMatchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
// report one map of the only losers side match available
match = await brackets.openMatch (losersMatchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , setEnds: false });
await match.backToBracket ();
// N-ZAP's first match can't be reopened while the losers match depends on it
match = await brackets.openMatch (nzapsMatchId);
await match.openTab ("admin" );
await isNotVisible (match.locators.reopenMatchButton);
await match.backToBracket ();
// undo the losers match score
match = await brackets.openMatch (losersMatchId);
await match.openTab ("action" );
await match.undoLastReport ();
await expect (match.score ([0 , 0 ])).toBeVisible ();
await match.backToBracket ();
// now the reopen succeeds
match = await brackets.openMatch (nzapsMatchId);
await match.openTab ("admin" );
await match.reopen ();
await expect (match.score ([1 , 0 ])).toBeVisible ();
// as N-ZAP, undo every score and let the other team sweep instead
await impersonate (page, NZAP_TEST_ID );
await brackets.goto (tournament.id);
match = await brackets.openMatch (nzapsMatchId);
await match.openTab ("action" );
await match.undoLastReport ();
await expect (match.score ([0 , 0 ])).toBeVisible ();
await match.reportResult ({ mapsToReport: 2 , winner: 2 });
await match.backToBracket ();
await expect (
brackets.participantInRound (DE_LOSERS_ROUND_ID , teams[0 ].id),
).toBeVisible ();
})losers bracket team winning grand finals forces a bracket reset Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Start bracket tournament tournament.id → [wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal, grandFinals, reset]
const [wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal, grandFinals, reset] =
await factories.TournamentFactory .startBracket (tournament.id);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);every match up to grand finals is swept by whichever team is on the alpha side, leaving the winners bracket team facing the team it beat in the winners final
For each { id } of wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal
for (const { id } of [wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal]) {
const match = await brackets.openMatch (id);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}Open match id → match
const match = await brackets.openMatch (id);Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();the losers bracket team (bravo side of grand finals) takes the first set, which is only enough to even out the sets lost
Open match grandFinals.id → match
let match = await brackets.openMatch (grandFinals.id);Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2 winner 2
await match.reportResult ({ mapsToReport: 2 , winner: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Assert
round header TOURNAMENT .ROUND_NAMES .BRACKET_RESET is visible
await expect (
brackets.roundHeader (TOURNAMENT .ROUND_NAMES .BRACKET_RESET ),
).toBeVisible ();finalize tournament button is not visible
await isNotVisible (brackets.locators.finalizeTournamentButton);Act
Open match reset.id → match
match = await brackets.openMatch (reset.id); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Assert
finalize tournament button is visible
await expect (brackets.locators.finalizeTournamentButton).toBeVisible ();Source test ("losers bracket team winning grand finals forces a bracket reset" , async ({
page,
factories,
}) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
const [wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal, grandFinals, reset] =
await factories.TournamentFactory .startBracket (tournament.id);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
// every match up to grand finals is swept by whichever team is on the alpha
// side, leaving the winners bracket team facing the team it beat in the
// winners final
for (const { id } of [wbSemiOne, wbSemiTwo, wbFinal, lbSemi, lbFinal]) {
const match = await brackets.openMatch (id);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}
// the losers bracket team (bravo side of grand finals) takes the first set,
// which is only enough to even out the sets lost
let match = await brackets.openMatch (grandFinals.id);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 , winner: 2 });
await match.backToBracket ();
await expect (
brackets.roundHeader (TOURNAMENT .ROUND_NAMES .BRACKET_RESET ),
).toBeVisible ();
await isNotVisible (brackets.locators.finalizeTournamentButton);
match = await brackets.openMatch (reset.id);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
await expect (brackets.locators.finalizeTournamentButton).toBeVisible ();
})completes and finalizes a small tournament with badge assigning Renders/notifications /to/:id /to/:id/brackets /to/:id/brackets/finalize /to/:id/matches/:mid /to/:id/results /u/:identifier /u/:identifier/results
Tournament brackets page Tournament match page Tournament brackets page Finalize tournament dialog Tournament brackets page User results page Notifications page Arrange
Create 2 × badge → badges
const badges = await factories.BadgeFactory .createMany (2 );Create tournament name “In The Zone 22” author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION map pool maps TO_MAP_POOL badges badges.map ((badge) => badge.id) → tournament
const tournament = await factories.TournamentFactory .create ({
name: "In The Zone 22" ,
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
badges: badges.map ((badge) => badge.id),
});Create teams tournament.id, members Sendou , → teams
const teams = await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID ] },
{},
]);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();Open match 1 → match
const match = await brackets.openMatch (1 );Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Act
Open finalize tournament dialog → finalizeDialog
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();Finalize tournament dialog Act
Select badge receiver 0 , teams[0 ].id
await finalizeDialog.selectBadgeReceiver (0 , teams[0 ].id);Select badge receiver 1 , teams[1 ].id
await finalizeDialog.selectBadgeReceiver (1 , teams[1 ].id);Confirm
await finalizeDialog.confirm ();Tournament brackets page Act
Open results → results
const results = await brackets.nav.openResults ();Assert
seed performance rating shows up after tournament is finalized
results.locators.sprHeader is visible
await expect (results.locators.sprHeader).toBeVisible ();User results page Act
Open the user results page Sendou
await userResults.goto (ADMIN_ID );Assert
tournament name “In The Zone 22” is visible
await expect (userResults.tournamentName ("In The Zone 22" )).toBeVisible ();Notifications page Act
Open the notifications page
await notifications.goto ();Assert
first items contains “New badge”
await expect (notifications.locators.items.first ()).toContainText (
"New badge" ,
);Source test ("completes and finalizes a small tournament with badge assigning" , async ({
page,
factories,
}) => {
test.slow ();
const badges = await factories.BadgeFactory .createMany (2 );
const tournament = await factories.TournamentFactory .create ({
name: "In The Zone 22" ,
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
badges: badges.map ((badge) => badge.id),
});
const teams = await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID ] },
{},
]);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
const match = await brackets.openMatch (1 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();
await finalizeDialog.selectBadgeReceiver (0 , teams[0 ].id);
await finalizeDialog.selectBadgeReceiver (1 , teams[1 ].id);
await finalizeDialog.confirm ();
const results = await brackets.nav.openResults ();
// seed performance rating shows up after tournament is finalized
await expect (results.locators.sprHeader).toBeVisible ();
const userResults = new UserResultsPage (page);
await userResults.goto (ADMIN_ID );
await expect (userResults.tournamentName ("In The Zone 22" )).toBeVisible ();
const notifications = new NotificationsPage (page);
await notifications.goto ();
await expect (notifications.locators.items.first ()).toContainText (
"New badge" ,
);
})locks/unlocks matches & sets match as casted Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/stream /to/:id/brackets /to/:id/matches/:mid
Tournament admin page Tournament admin stream page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open stream → stream
const stream = await admin.openStream ();Tournament admin stream page Act
an empty array field already renders one placeholder input
Fill account 0 , “test”
await stream.fillAccount (0 , "test" );Save
await stream.save ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();Open match 1 → match
let match = await brackets.openMatch (1 );Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Act
match 3 is the winners' final the winner of match 1 waits in
Open match 3 → match
match = await brackets.openMatch (3 ); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );picking a chip auto-submits the cast channel
Set casted by “test”
await match.setCastedBy ("test" );Submit cast info
await match.submitCastInfo ();Back to bracket
await match.backToBracket ();Tournament brackets page Act
Open match 2 → match
match = await brackets.openMatch (2 ); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Assert
first cast badges is visible
await expect (brackets.locators.castBadges.first ()).toBeVisible ();Act
Open match 3 → match
match = await brackets.openMatch (3 ); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );Assert
the toggle reading "Unlock" is what signals the locked state
unlock button is visible
await expect (match.locators.unlockButton).toBeVisible ();a locked match still shows the pool & room pass so players can join
pool label is visible
await expect (match.locators.poolLabel).toBeVisible ();room pass is visible
await expect (match.locators.roomPass).toBeVisible ();Act
Submit cast info
await match.submitCastInfo ();Assert
stage banner is visible
await expect (match.locators.stageBanner).toBeVisible ();Act
the cast channel persists across unlock; the bracket badge flips from CAST to LIVE
Back to bracket
await match.backToBracket ();Tournament brackets page Assert
first live badges is visible
await expect (brackets.locators.liveBadges.first ()).toBeVisible ();Source test ("locks/unlocks matches & sets match as casted" , async ({
page,
factories,
}) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
await impersonate (page);
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
const stream = await admin.openStream ();
// an empty array field already renders one placeholder input
await stream.fillAccount (0 , "test" );
await stream.save ();
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
let match = await brackets.openMatch (1 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
// match 3 is the winners' final the winner of match 1 waits in
match = await brackets.openMatch (3 );
await match.openTab ("admin" );
// picking a chip auto-submits the cast channel
await match.setCastedBy ("test" );
await match.submitCastInfo ();
await match.backToBracket ();
match = await brackets.openMatch (2 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
await expect (brackets.locators.castBadges.first ()).toBeVisible ();
match = await brackets.openMatch (3 );
await match.openTab ("admin" );
// the toggle reading "Unlock" is what signals the locked state
await expect (match.locators.unlockButton).toBeVisible ();
// a locked match still shows the pool & room pass so players can join
await expect (match.locators.poolLabel).toBeVisible ();
await expect (match.locators.roomPass).toBeVisible ();
await match.submitCastInfo ();
await expect (match.locators.stageBanner).toBeVisible ();
// the cast channel persists across unlock; the bracket badge flips from CAST to LIVE
await match.backToBracket ();
await expect (brackets.locators.liveBadges.first ()).toBeVisible ();
})Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/brackets /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
});the top seed has not checked in, leaving 15 teams and a first round bye
Create teams tournament.id, is checked in false , ...teamSeeds (15 )
await createTeams (factories, tournament.id, [
{ isCheckedIn: false },
...teamSeeds (15 ),
]);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();Assert
match 1 is not visible
await isNotVisible (brackets.match (1 ));Act
Open match 2 → match
const match = await brackets.openMatch (2 );Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Open admin → admin
const admin = await match.nav.openAdmin ();Reset bracket “Main bracket”
await admin.resetBracket ("Main bracket" );Click admin.adminTab ("Teams" )
await admin.adminTab ("Teams" ).click ();Check team in 0
await admin.checkTeamIn (0 );Open brackets → bracketsAfterReset
const bracketsAfterReset = await admin.nav.openBrackets ();Finalize
await bracketsAfterReset.finalize ();Assert
bracketsAfterReset.firstRoundTeamNames (16 ) does not contain “”
expect (await bracketsAfterReset.firstRoundTeamNames (16 )).not.toContain ("" );Source test ("resets bracket" , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
mapPoolMaps: TO_MAP_POOL ,
});
// the top seed has not checked in, leaving 15 teams and a first round bye
await createTeams (factories, tournament.id, [
{ isCheckedIn: false },
...teamSeeds (15 ),
]);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
await isNotVisible (brackets.match (1 ));
const match = await brackets.openMatch (2 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
const admin = await match.nav.openAdmin ();
await admin.resetBracket ("Main bracket" );
await admin.adminTab ("Teams" ).click ();
await admin.checkTeamIn (0 );
const bracketsAfterReset = await admin.nav.openBrackets ();
await bracketsAfterReset.finalize ();
// bye is gone
expect (await bracketsAfterReset.firstRoundTeamNames (16 )).not.toContain ("" );
})dropping team out ends ongoing match early and auto-forfeits losers bracket match Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament admin page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Start bracket tournament tournament.id → matches
const matches = await factories.TournamentFactory .startBracket (
tournament.id,
);ongoingMatchId = matches[0 ].id
const ongoingMatchId = matches[0 ].id;adjacentMatchId = matches[1 ].id
const adjacentMatchId = matches[1 ].id;losersMatchId = matches[3 ].id
const losersMatchId = matches[3 ].id;Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open match ongoingMatchId → match
let match = await brackets.openMatch (ongoingMatchId);Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 1 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });Back to bracket
await match.backToBracket ();Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Drop out team 3
await admin.dropOutTeam (3 );Tournament match page Act
dropping out ended the ongoing match early
Open the tournament match page tournament id tournament.id match id ongoingMatchId
await match.goto ({ tournamentId: tournament.id, matchId: ongoingMatchId });Assert
final banner is visible
await expect (match.locators.finalBanner).toBeVisible ();Act
Back to bracket
await match.backToBracket ();Tournament brackets page Act
completing the adjacent match sends its loser to the losers bracket
Open match adjacentMatchId → match
match = await brackets.openMatch (adjacentMatchId); Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Tournament brackets page Act
the losers match, now holding the dropped team, ended early as well
Open match losersMatchId → match
match = await brackets.openMatch (losersMatchId); Tournament match page Assert
final banner is visible
await expect (match.locators.finalBanner).toBeVisible ();Source test ("dropping team out ends ongoing match early and auto-forfeits losers bracket match" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
const matches = await factories.TournamentFactory .startBracket (
tournament.id,
);
const ongoingMatchId = matches[0 ].id;
const adjacentMatchId = matches[1 ].id;
const losersMatchId = matches[3 ].id;
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
let match = await brackets.openMatch (ongoingMatchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });
await match.backToBracket ();
// the fourth team is the bravo side of the ongoing match
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.dropOutTeam (3 );
// dropping out ended the ongoing match early
await match.goto ({ tournamentId: tournament.id, matchId: ongoingMatchId });
await expect (match.locators.finalBanner).toBeVisible ();
await match.backToBracket ();
// completing the adjacent match sends its loser to the losers bracket
match = await brackets.openMatch (adjacentMatchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
// the losers match, now holding the dropped team, ended early as well
match = await brackets.openMatch (losersMatchId);
await expect (match.locators.finalBanner).toBeVisible ();
})Tournament bracket grouped elimination plays double elimination groups into a redemption and a top cut Renders/to/:id /to/:id/brackets /to/:id/brackets/finalize
Tournament brackets page Finalize tournament dialog Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression DE_GROUPS_TO_REDEMPTION_AND_TOP_CUT → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DE_GROUPS_TO_REDEMPTION_AND_TOP_CUT ,
});Create teams tournament.id, teamSeeds (16 )
await createTeams (factories, tournament.id, teamSeeds (16 ));the higher seed wins every match: Team 1 and Team 2 win their pools unbeaten
Play out tournament tournament.id 0
await factories.TournamentFactory .playOut (tournament.id, 0 );Tournament brackets page Act
Open the tournament brackets page tournament.id, 0
await brackets.goto (tournament.id, 0 );Assert
group button “A” is visible
await expect (brackets.groupButton ("A" )).toBeVisible ();group button “B” is visible
await expect (brackets.groupButton ("B" )).toBeVisible ();round header “WB Finals” is visible
await expect (brackets.roundHeader ("WB Finals" )).toBeVisible ();round header “LB Finals” is not visible
await isNotVisible (brackets.roundHeader ("LB Finals" ));round header “Grand Finals” is not visible
await isNotVisible (brackets.roundHeader ("Grand Finals" ));exit label “Top Cut [W]” is visible
await expect (brackets.exitLabel ("Top Cut [W]" )).toBeVisible ();exit label “Redemption [L]” is visible
await expect (brackets.exitLabel ("Redemption [L]" )).toBeVisible ();Act
Open group “B”
await brackets.openGroup ("B" );Assert
first bracket team name “Team 2” is visible
await expect (brackets.bracketTeamName ("Team 2" ).first ()).toBeVisible ();bracket team name “Team 1” is not visible
await isNotVisible (brackets.bracketTeamName ("Team 1" ));a pool's winners final loser is linked to the redemption group it lands in, before and after it starts
expectExitLinksToRedemptionGroups = async () => { for (const [pool, team, otherGroupTeam] of [ ["A" , "Team 4" , "T …
const expectExitLinksToRedemptionGroups = async () => {
for (const [pool, team, otherGroupTeam] of [
["A" , "Team 4" , "Team 3" ],
["B" , "Team 3" , "Team 4" ],
]) {
await brackets.goto (tournament.id, 0 );
await brackets.openGroup (pool);
await brackets.exitLabel ("Redemption [L]" ).click ();
await expect (page).toHaveURL (/idx=1 .*group=\d+/);
await expect (brackets.bracketTeamName (team).first ()).toBeVisible ();
await isNotVisible (brackets.bracketTeamName (otherGroupTeam));
}
};exit links to redemption groups
await expectExitLinksToRedemptionGroups ();Arrange
Team 3 (pool B) and Team 4 (pool A) win their redemption groups, having lost their pool's winners final
Play out tournament tournament.id 1
await factories.TournamentFactory .playOut (tournament.id, 1 );Assert
exit links to redemption groups
await expectExitLinksToRedemptionGroups ();Act
Open the tournament brackets page tournament.id, 2
await brackets.goto (tournament.id, 2 );Assert
pool winners take the top seeds and play a redemption winner from the other pool, not a rematch
first round team names 4 equals “Team 1” , “Team 3” , “Team 2” , “Team 4”
expect (await brackets.firstRoundTeamNames (4 )).toEqual ([
"Team 1" ,
"Team 3" ,
"Team 2" ,
"Team 4" ,
]);Arrange
Play out tournament tournament.id 2
await factories.TournamentFactory .playOut (tournament.id, 2 );Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize tournament dialog → finalizeDialog
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();Finalize tournament dialog Act
Confirm
await finalizeDialog.confirm ();Assert
finalize tournament button is not visible
await isNotVisible (brackets.locators.finalizeTournamentButton);Source test ("plays double elimination groups into a redemption and a top cut" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DE_GROUPS_TO_REDEMPTION_AND_TOP_CUT ,
});
await createTeams (factories, tournament.id, teamSeeds (16 ));
// the higher seed wins every match: Team 1 and Team 2 win their pools unbeaten
await factories.TournamentFactory .playOut (tournament.id, 0 );
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id, 0 );
await expect (brackets.groupButton ("A" )).toBeVisible ();
await expect (brackets.groupButton ("B" )).toBeVisible ();
await expect (brackets.roundHeader ("WB Finals" )).toBeVisible ();
await isNotVisible (brackets.roundHeader ("LB Finals" ));
await isNotVisible (brackets.roundHeader ("Grand Finals" ));
await expect (brackets.exitLabel ("Top Cut [W]" )).toBeVisible ();
await expect (brackets.exitLabel ("Redemption [L]" )).toBeVisible ();
await brackets.openGroup ("B" );
await expect (brackets.bracketTeamName ("Team 2" ).first ()).toBeVisible ();
await isNotVisible (brackets.bracketTeamName ("Team 1" ));
// a pool's winners final loser is linked to the redemption group it lands in, before and after it starts
const expectExitLinksToRedemptionGroups = async () => {
for (const [pool, team, otherGroupTeam] of [
["A" , "Team 4" , "Team 3" ],
["B" , "Team 3" , "Team 4" ],
]) {
await brackets.goto (tournament.id, 0 );
await brackets.openGroup (pool);
await brackets.exitLabel ("Redemption [L]" ).click ();
await expect (page).toHaveURL (/idx=1 .*group=\d+/);
await expect (brackets.bracketTeamName (team).first ()).toBeVisible ();
await isNotVisible (brackets.bracketTeamName (otherGroupTeam));
}
};
await expectExitLinksToRedemptionGroups ();
// Team 3 (pool B) and Team 4 (pool A) win their redemption groups, having lost their pool's winners final
await factories.TournamentFactory .playOut (tournament.id, 1 );
await expectExitLinksToRedemptionGroups ();
await brackets.goto (tournament.id, 2 );
// pool winners take the top seeds and play a redemption winner from the other pool, not a rematch
expect (await brackets.firstRoundTeamNames (4 )).toEqual ([
"Team 1" ,
"Team 3" ,
"Team 2" ,
"Team 4" ,
]);
await factories.TournamentFactory .playOut (tournament.id, 2 );
await brackets.goto (tournament.id);
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();
await finalizeDialog.confirm ();
await isNotVisible (brackets.locators.finalizeTournamentButton);
})finalizes a tournament with co-winners, each of them getting the trophy Renders/to/:id /to/:id/brackets /to/:id/brackets/finalize
Tournament brackets page Finalize tournament dialog Arrange
Create trophy name “Co-winner Trophy” → trophy
const trophy = await factories.TrophyFactory .create ({
name: "Co-winner Trophy" ,
});Create tournament author Sendou start times startedTournamentTimes () bracket progression SE_WITHOUT_FINALS trophy id trophy.id → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SE_WITHOUT_FINALS ,
trophyId: trophy.id,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Play out tournament tournament.id 0
await factories.TournamentFactory .playOut (tournament.id, 0 );Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Assert
round header “Finals” is not visible
await isNotVisible (brackets.roundHeader ("Finals" ));Act
Open finalize tournament dialog → finalizeDialog
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();Finalize tournament dialog Assert
one trophy receiver picker per 1st place team, full rosters picked by default
trophy team heading “Team 1” is visible
await expect (finalizeDialog.trophyTeamHeading ("Team 1" )).toBeVisible ();trophy team heading “Team 2” is visible
await expect (finalizeDialog.trophyTeamHeading ("Team 2" )).toBeVisible ();Act
Confirm
await finalizeDialog.confirm ();Assert
finalize tournament button is not visible
await isNotVisible (brackets.locators.finalizeTournamentButton);Source test ("finalizes a tournament with co-winners, each of them getting the trophy" , async ({
page,
factories,
}) => {
const trophy = await factories.TrophyFactory .create ({
name: "Co-winner Trophy" ,
});
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SE_WITHOUT_FINALS ,
trophyId: trophy.id,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
await factories.TournamentFactory .playOut (tournament.id, 0 );
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await isNotVisible (brackets.roundHeader ("Finals" ));
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();
// one trophy receiver picker per 1st place team, full rosters picked by default
await expect (finalizeDialog.trophyTeamHeading ("Team 1" )).toBeVisible ();
await expect (finalizeDialog.trophyTeamHeading ("Team 2" )).toBeVisible ();
await finalizeDialog.confirm ();
await isNotVisible (brackets.locators.finalizeTournamentButton);
})builds a grouped double elimination with rounds left unplayed Renders/calendar/new /to/:id /to/:id/info
Calendar new event page Arrange
Create user null roles “TOURNAMENT_ORGANIZER” → organizer
const organizer = await factories.UserFactory .create (null , {
roles: ["TOURNAMENT_ORGANIZER" ],
});Calendar new event page Act
Go to new tournament
await newTournament.gotoNewTournament ();Fill Name with “Grouped Tournament”
await newTournament.form.fill ("name" , "Grouped Tournament" );Set first date new Date (2027 , 0 , 15 , 17 , 0 )
await newTournament.setFirstDate (new Date (2027 , 0 , 15 , 17 , 0 ));Go to step “maps”
await newTournament.goToStep ("maps" );Check items map picking style “TO”
await newTournament.form.checkItems ("mapPickingStyle" , ["TO" ]);Pick map pool stage “Scorch Gorge” mode “Splat Zones” , stage “Eeltail Alley” mode “Tower Control”
await newTournament.pickMapPool ([
{ stage: "Scorch Gorge" , mode: "Splat Zones" },
{ stage: "Eeltail Alley" , mode: "Tower Control" },
]);Select bracket 0
await newTournament.selectBracket (0 );Select “4” in group count select
await newTournament.locators.groupCountSelect.selectOption ("4" );roundPlayed = (round: string ) => newTournament.roundPlayedCheckbox (round)
const roundPlayed = (round: string ) =>
newTournament.roundPlayedCheckbox (round);a round can't be played without the rounds feeding teams to it
Uncheck roundPlayed ("LB_SEMIS" )
await roundPlayed ("LB_SEMIS" ).uncheck ();Assert
For each round of “LB_FINALS” , “GRAND_FINALS” , “BRACKET_RESET”
for (const round of ["LB_FINALS" , "GRAND_FINALS" , "BRACKET_RESET" ]) {
await expect (roundPlayed (round)).not.toBeChecked ();
}roundPlayed (round) is not checked
await expect (roundPlayed (round)).not.toBeChecked ();roundPlayed ("WB_FINALS" ) is checked
await expect (roundPlayed ("WB_FINALS" )).toBeChecked ();round not played hint “Losers bracket semifinals” is visible
await expect (
newTournament.roundNotPlayedHint ("Losers bracket semifinals" ),
).toBeVisible ();Act
playing a round again plays what it depends on too
Check roundPlayed ("GRAND_FINALS" )
await roundPlayed ("GRAND_FINALS" ).check ();Assert
For each round of “LB_SEMIS” , “LB_FINALS” , “GRAND_FINALS”
for (const round of ["LB_SEMIS" , "LB_FINALS" , "GRAND_FINALS" ]) {
await expect (roundPlayed (round)).toBeChecked ();
}roundPlayed (round) is checked
await expect (roundPlayed (round)).toBeChecked ();roundPlayed ("BRACKET_RESET" ) is not checked
await expect (roundPlayed ("BRACKET_RESET" )).not.toBeChecked ();Act
Save
await newTournament.save ();Assert
the page URL is /\/to\/\d+/
await expect (page).toHaveURL (/\/to\/\d+/);Source test ("builds a grouped double elimination with rounds left unplayed" , async ({
page,
factories,
}) => {
const organizer = await factories.UserFactory .create (null , {
roles: ["TOURNAMENT_ORGANIZER" ],
});
await impersonate (page, organizer.id);
const newTournament = new CalendarNewEventPage (page);
await newTournament.gotoNewTournament ();
await newTournament.form.fill ("name" , "Grouped Tournament" );
await newTournament.setFirstDate (new Date (2027 , 0 , 15 , 17 , 0 ));
await newTournament.goToStep ("maps" );
await newTournament.form.checkItems ("mapPickingStyle" , ["TO" ]);
await newTournament.pickMapPool ([
{ stage: "Scorch Gorge" , mode: "Splat Zones" },
{ stage: "Eeltail Alley" , mode: "Tower Control" },
]);
await newTournament.selectBracket (0 );
await newTournament.locators.groupCountSelect.selectOption ("4" );
const roundPlayed = (round: string ) =>
newTournament.roundPlayedCheckbox (round);
// a round can't be played without the rounds feeding teams to it
await roundPlayed ("LB_SEMIS" ).uncheck ();
for (const round of ["LB_FINALS" , "GRAND_FINALS" , "BRACKET_RESET" ]) {
await expect (roundPlayed (round)).not.toBeChecked ();
}
await expect (roundPlayed ("WB_FINALS" )).toBeChecked ();
await expect (
newTournament.roundNotPlayedHint ("Losers bracket semifinals" ),
).toBeVisible ();
// playing a round again plays what it depends on too
await roundPlayed ("GRAND_FINALS" ).check ();
for (const round of ["LB_SEMIS" , "LB_FINALS" , "GRAND_FINALS" ]) {
await expect (roundPlayed (round)).toBeChecked ();
}
await expect (roundPlayed ("BRACKET_RESET" )).not.toBeChecked ();
await newTournament.save ();
await expect (page).toHaveURL (/\/to\/\d+/);
})Tournament bracket multi stage completes and finalizes a small tournament (RR->SE w/ underground bracket) Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/brackets /to/:id/brackets/finalize /to/:id/matches/:mid
Tournament brackets page Tournament admin page Tournament brackets page Finalize tournament dialog Tournament brackets page Tournament match page Arrange
Create 2 × badge → badges
const badges = await factories.BadgeFactory .createMany (2 );Create tournament author Sendou start times startedTournamentTimes () bracket progression RR_TO_SE_WITH_UNDERGROUND badges badges.map ((badge) => badge.id) → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE_WITH_UNDERGROUND ,
badges: badges.map ((badge) => badge.id),
});groups of 3 and 4; the factory plays every match with the higher seed winning, dropping teams 5, 6 and 7 into the underground bracket's placements
Create teams tournament.id, teamSeeds (7 ) → teams
const teams = await createTeams (factories, tournament.id, teamSeeds (7 ));Play out tournament tournament.id
await factories.TournamentFactory .playOut (tournament.id);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Select bracket “Underground”
await brackets.selectBracket ("Underground" );Check in bracket
await brackets.checkInBracket ();Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Check team in to bracket teams[6 ].id, “Underground bracket”
await admin.checkTeamInToBracket (teams[6 ].id, "Underground bracket" );Arrange
the checked in teams meet in the underground bracket; score reporting and starting brackets from the UI are covered by other tests
Play out tournament tournament.id 2 , 1
await factories.TournamentFactory .playOut (tournament.id, [2 , 1 ]);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize tournament dialog → finalizeDialog
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();Finalize tournament dialog Act
Assign badges later
await finalizeDialog.assignBadgesLater ();Confirm
await finalizeDialog.confirm ();Tournament brackets page Act
after finalizing the tournament, the admin tab disappears so the reopen action is no longer reachable
Open match 11 → match
const match = await brackets.openMatch (11 );Assert
admin tab is not visible
await isNotVisible (match.locators.adminTab);reopen match button is not visible
await isNotVisible (match.locators.reopenMatchButton);Tournament match page Act
Back to bracket
await match.backToBracket ();Source test ("completes and finalizes a small tournament (RR->SE w/ underground bracket)" , async ({
page,
factories,
}) => {
const badges = await factories.BadgeFactory .createMany (2 );
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE_WITH_UNDERGROUND ,
badges: badges.map ((badge) => badge.id),
});
// groups of 3 and 4; the factory plays every match with the higher seed
// winning, dropping teams 5, 6 and 7 into the underground bracket's placements
const teams = await createTeams (factories, tournament.id, teamSeeds (7 ));
await factories.TournamentFactory .playOut (tournament.id);
// captain of one of the underground bracket teams
await impersonate (page, teams[4 ].ownerUserId);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.selectBracket ("Underground" );
await brackets.checkInBracket ();
await impersonate (page);
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.checkTeamInToBracket (teams[6 ].id, "Underground bracket" );
// the checked in teams meet in the underground bracket; score reporting and
// starting brackets from the UI are covered by other tests
await factories.TournamentFactory .playOut (tournament.id, [2 , 1 ]);
await brackets.goto (tournament.id);
const finalizeDialog = await brackets.openFinalizeTournamentDialog ();
await finalizeDialog.assignBadgesLater ();
await finalizeDialog.confirm ();
// after finalizing the tournament, the admin tab disappears so the
// reopen action is no longer reachable
const match = await brackets.openMatch (11 );
await isNotVisible (match.locators.adminTab);
await isNotVisible (match.locators.reopenMatchButton);
await match.backToBracket ();
})seeds the follow-up single elimination so group rivals rematch as late as possible Renders/to/:id /to/:id/brackets
Tournament brackets page Arrange
4 groups of 4, every team advancing -> 16 team single elimination
Create tournament author Sendou start times startedTournamentTimes () bracket progression RR_TOP_4_TO_SE → topFourTournament
const topFourTournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TOP_4_TO_SE ,
});Create teams topFourTournament.id, teamSeeds (16 )
await createTeams (factories, topFourTournament.id, teamSeeds (16 ));Play out tournament topFourTournament.id
await factories.TournamentFactory .playOut (topFourTournament.id);Tournament brackets page Act
Open the tournament brackets page topFourTournament.id
await brackets.goto (topFourTournament.id);Select bracket “Groups stage”
await brackets.selectBracket ("Groups stage" );Group standings team names 4 → topFourGroups
const topFourGroups = await brackets.groupStandingsTeamNames (4 );Select bracket “Final stage”
await brackets.selectBracket ("Final stage" );First round team names 16 → topFourLineup
const topFourLineup = await brackets.firstRoundTeamNames (16 );Assert
topFourLineup groups spread across bracket topFourGroups
expectGroupsSpreadAcrossBracket (topFourLineup, topFourGroups);topFourLineup seed order respected topFourGroups
expectSeedOrderRespected (topFourLineup, topFourGroups);Arrange
4 groups of 4, top 2 advancing -> 8 team single elimination
Create tournament author Sendou start times startedTournamentTimes () bracket progression RR_TO_SE → topTwoTournament
const topTwoTournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
});Create teams topTwoTournament.id, teamSeeds (16 )
await createTeams (factories, topTwoTournament.id, teamSeeds (16 ));Play out tournament topTwoTournament.id
await factories.TournamentFactory .playOut (topTwoTournament.id);Act
Open the tournament brackets page topTwoTournament.id
await brackets.goto (topTwoTournament.id);Select bracket “Groups stage”
await brackets.selectBracket ("Groups stage" );Group standings team names 4 → topTwoGroups
const topTwoGroups = await brackets.groupStandingsTeamNames (4 );Select bracket “Final stage”
await brackets.selectBracket ("Final stage" );First round team names 8 → topTwoLineup
const topTwoLineup = await brackets.firstRoundTeamNames (8 );Assert
topTwoLineup groups spread across bracket topTwoGroups.map ((group) => group.slice (0 , 2 ))
expectGroupsSpreadAcrossBracket (
topTwoLineup,
topTwoGroups.map ((group) => group.slice (0 , 2 )),
);topTwoLineup seed order respected topTwoGroups.map ((group) => group.slice (0 , 2 ))
expectSeedOrderRespected (
topTwoLineup,
topTwoGroups.map ((group) => group.slice (0 , 2 )),
);Source test ("seeds the follow-up single elimination so group rivals rematch as late as possible" , async ({
page,
factories,
}) => {
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
// 4 groups of 4, every team advancing -> 16 team single elimination
const topFourTournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TOP_4_TO_SE ,
});
await createTeams (factories, topFourTournament.id, teamSeeds (16 ));
await factories.TournamentFactory .playOut (topFourTournament.id);
await brackets.goto (topFourTournament.id);
await brackets.selectBracket ("Groups stage" );
const topFourGroups = await brackets.groupStandingsTeamNames (4 );
await brackets.selectBracket ("Final stage" );
const topFourLineup = await brackets.firstRoundTeamNames (16 );
expectGroupsSpreadAcrossBracket (topFourLineup, topFourGroups);
expectSeedOrderRespected (topFourLineup, topFourGroups);
// 4 groups of 4, top 2 advancing -> 8 team single elimination
const topTwoTournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
});
await createTeams (factories, topTwoTournament.id, teamSeeds (16 ));
await factories.TournamentFactory .playOut (topTwoTournament.id);
await brackets.goto (topTwoTournament.id);
await brackets.selectBracket ("Groups stage" );
const topTwoGroups = await brackets.groupStandingsTeamNames (4 );
await brackets.selectBracket ("Final stage" );
const topTwoLineup = await brackets.firstRoundTeamNames (8 );
expectGroupsSpreadAcrossBracket (
topTwoLineup,
topTwoGroups.map ((group) => group.slice (0 , 2 )),
);
expectSeedOrderRespected (
topTwoLineup,
topTwoGroups.map ((group) => group.slice (0 , 2 )),
);
})shows tournament results on user profile after finalized tournament Renders/to/:id /to/:id/brackets /to/:id/results /to/:id/teams/:tid /u/:identifier /u/:identifier /u/:identifier/results /u/:identifier/seasons /u/:identifier/seasons
Tournament brackets page Arrange
progression editing, score reporting and the finalize dialog are covered by other tests; only the result pages themselves are under test here
Create ROSTER_SIZE * 2 × user → players
const players = await factories.UserFactory .createMany (ROSTER_SIZE * 2 );teamRosters = [ players.slice (0 , ROSTER_SIZE ), players.slice (ROSTER_SIZE ), ].map ((roster) =…
const teamRosters = [
players.slice (0 , ROSTER_SIZE ),
players.slice (ROSTER_SIZE ),
].map ((roster) => roster.map ((player) => player.id));Create played tournament name “Swim or Sink 101” author Sendou start times startedTournamentTimes () bracket progression RR_TO_SE teamRosters played out “all” → tournament
const tournament = await factories.TournamentFactory .createPlayed (
{
name: "Swim or Sink 101" ,
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
},
{ teamRosters, playedOut: "all" },
);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open results → results
const results = await brackets.nav.openResults ();Expand team 0
await results.expandTeam (0 );Open member 0 → userPage
const userPage = await results.openMember (0 );Open seasons
await userPage.openSeasons ();Assert
userPage.locators.seasonsTournamentResult is visible
await expect (userPage.locators.seasonsTournamentResult).toBeVisible ();Act
Back to profile
await userPage.backToProfile ();Open results → userResults
const userResults = await userPage.openResults ();Assert
userResults.locators.tournamentNameCells.first () contains “Swim or Sink 101”
await expect (
userResults.locators.tournamentNameCells.first (),
).toContainText ("Swim or Sink 101" );Act
Open mates 0
await userResults.openMates (0 );Assert
userResults.matesListItems (0 ) count is 3
await expect (userResults.matesListItems (0 )).toHaveCount (3 );Source test ("shows tournament results on user profile after finalized tournament" , async ({
page,
factories,
}) => {
// progression editing, score reporting and the finalize dialog are covered
// by other tests; only the result pages themselves are under test here
const players = await factories.UserFactory .createMany (ROSTER_SIZE * 2 );
const teamRosters = [
players.slice (0 , ROSTER_SIZE ),
players.slice (ROSTER_SIZE ),
].map ((roster) => roster.map ((player) => player.id));
const tournament = await factories.TournamentFactory .createPlayed (
{
name: "Swim or Sink 101" ,
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
},
{ teamRosters, playedOut: "all" },
);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const results = await brackets.nav.openResults ();
await results.expandTeam (0 );
const userPage = await results.openMember (0 );
await userPage.openSeasons ();
await expect (userPage.locators.seasonsTournamentResult).toBeVisible ();
await userPage.backToProfile ();
const userResults = await userPage.openResults ();
await expect (
userResults.locators.tournamentNameCells.first (),
).toContainText ("Swim or Sink 101" );
await userResults.openMates (0 );
await expect (userResults.matesListItems (0 )).toHaveCount (3 );
})Renders/calendar/new /to/:id /to/:id/admin /to/:id/admin /to/:id/admin/registration/:tid? /to/:id/brackets /to/:id/info /to/:id/matches/:mid /to/:id/teams
Tournament admin page Calendar new event page Tournament brackets page Top right buttons Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament admin registration page Tournament teams page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression SOS_BRACKETS map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});the to-be double registrant plays in Sendou's team; the admin add flow requires a friend code, which the admin user itself lacks
Create user discord name “Duplicate Dave” → doubleRegistrant
const doubleRegistrant = await factories.UserFactory .create ({
discordName: "Duplicate Dave" ,
});Create teams tournament.id, members Sendou , doubleRegistrant.id , ...teamSeeds (3 ) → teams
const teams = await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID , doubleRegistrant.id] },
...teamSeeds (3 ),
]);Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Edit event info → eventEdit
const eventEdit = await admin.editEventInfo ();Calendar new event page Act
Delete last bracket
await eventEdit.deleteLastBracket ();Pick last placements 3 , 4
await eventEdit.pickLastPlacements ([3 , 4 ]);Save
await eventEdit.save ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();every match of the group but one that Sendou's team is not part of
For each matchId of 1 , 2 , 3 , 4 , 6
for (const matchId of [1 , 2 , 3 , 4 , 6 ]) {
const match = await brackets.openMatch (matchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}Open match matchId → match
const match = await brackets.openMatch (matchId);Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Top right buttons Assert
Sendou's team has finished its group's matches, the header says so while the last one plays out
global status “Waiting for match” is visible
await expect (
new TopRightButtons (page).globalStatus ("Waiting for match" ),
).toBeVisible ();Tournament brackets page Act
Open match 5 → lastGroupsMatch
const lastGroupsMatch = await brackets.openMatch (5 );Tournament match page Act
Open tab “action”
await lastGroupsMatch.openTab ("action" );Report result maps to report 2
await lastGroupsMatch.reportResult ({ mapsToReport: 2 });Back to bracket
await lastGroupsMatch.backToBracket ();Tournament brackets page Act
Select bracket “Hammerhead”
await brackets.selectBracket ("Hammerhead" );Assert
brackets viewer is not visible
await isNotVisible (brackets.locators.bracketsViewer);Act
Select bracket “Mako”
await brackets.selectBracket ("Mako" );Assert
brackets viewer is visible
await expect (brackets.locators.bracketsViewer).toBeVisible ();Act
Finalize
await brackets.finalize ();Open match 7 → makoMatch
const makoMatch = await brackets.openMatch (7 );Tournament match page Assert
back to bracket button is visible
await expect (makoMatch.locators.backToBracketButton).toBeVisible ();Tournament admin registration page Act
Go to edit tournament.id, teams[2 ].id
await registration.gotoEdit (tournament.id, teams[2 ].id);Add member “Duplicate Dave”
await registration.addMember ("Duplicate Dave" );Save
await registration.save ();Tournament teams page Act
Open the tournament teams page tournament.id
await teamsPage.goto (tournament.id);Assert
member named “Duplicate Dave” count is 2
await expect (teamsPage.memberNamed ("Duplicate Dave" )).toHaveCount (2 );Source test ("changes SOS format and progresses with it & adds a member to another team" , async ({
page,
factories,
}) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});
// the to-be double registrant plays in Sendou's team; the admin add
// flow requires a friend code, which the admin user itself lacks
const doubleRegistrant = await factories.UserFactory .create ({
discordName: "Duplicate Dave" ,
});
const teams = await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID , doubleRegistrant.id] },
...teamSeeds (3 ),
]);
await impersonate (page);
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
const eventEdit = await admin.editEventInfo ();
await eventEdit.deleteLastBracket ();
await eventEdit.pickLastPlacements ([3 , 4 ]);
await eventEdit.save ();
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
// every match of the group but one that Sendou's team is not part of
for (const matchId of [1 , 2 , 3 , 4 , 6 ]) {
const match = await brackets.openMatch (matchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}
// Sendou's team has finished its group's matches, the header says so while the last one plays out
await expect (
new TopRightButtons (page).globalStatus ("Waiting for match" ),
).toBeVisible ();
const lastGroupsMatch = await brackets.openMatch (5 );
await lastGroupsMatch.openTab ("action" );
await lastGroupsMatch.reportResult ({ mapsToReport: 2 });
await lastGroupsMatch.backToBracket ();
await brackets.selectBracket ("Hammerhead" );
await isNotVisible (brackets.locators.bracketsViewer);
await brackets.selectBracket ("Mako" );
await expect (brackets.locators.bracketsViewer).toBeVisible ();
await brackets.finalize ();
const makoMatch = await brackets.openMatch (7 );
await expect (makoMatch.locators.backToBracketButton).toBeVisible ();
// add a player of the first team also to the third team (a team in the Mako bracket)
const registration = new TournamentAdminRegistrationPage (page);
await registration.gotoEdit (tournament.id, teams[2 ].id);
await registration.addMember ("Duplicate Dave" );
await registration.save ();
const teamsPage = new TournamentTeamsPage (page);
await teamsPage.goto (tournament.id);
await expect (teamsPage.memberNamed ("Duplicate Dave" )).toHaveCount (2 );
})conducts a tournament with many starting brackets Renders/calendar/new /to/:id /to/:id/admin /to/:id/admin /to/:id/admin/seeds /to/:id/brackets /to/:id/info
Tournament admin page Calendar new event page Tournament seeds page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression SOS_BRACKETS map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (16 )
await createTeams (factories, tournament.id, teamSeeds (16 ));Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Edit event info → eventEdit
const eventEdit = await admin.editEventInfo ();Calendar new event page Act
Delete last bracket
await eventEdit.deleteLastBracket ();Make all brackets starting brackets
await eventEdit.makeAllBracketsStartingBrackets ();Set bracket format 0 , “Single elimination”
await eventEdit.setBracketFormat (0 , "Single elimination" );Set bracket format 1 , “Single elimination”
await eventEdit.setBracketFormat (1 , "Single elimination" );Set bracket format 2 , “Swiss”
await eventEdit.setBracketFormat (2 , "Swiss" );Set bracket format 3 , “Swiss”
await eventEdit.setBracketFormat (3 , "Swiss" );Save
await eventEdit.save ();Tournament seeds page Act
Open the tournament seeds page tournament.id
await seeds.goto (tournament.id);Open starting brackets dialog
await seeds.openStartingBracketsDialog ();Repeat for (let i = 0 ; i < 16 ; i++)
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 seeds.setStartingBracket (i, bracketName);
}let bracketName: string ;
let bracketName: string ;If i < 4
if (i < 4 ) {
bracketName = "Groups stage" ;
} else if (i < 8 ) {
bracketName = "Great White" ;
} else if (i < 12 ) {
bracketName = "Hammerhead" ;
} else {
bracketName = "Mako" ;
}bracketName = "Groups stage"
bracketName = "Groups stage" ; Set starting bracket i, bracketName
await seeds.setStartingBracket (i, bracketName);Save starting brackets
await seeds.saveStartingBrackets ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);For each bracketName of “Groups stage” , “Great White” , “Hammerhead” , “Mako”
for (const bracketName of [
"Groups stage" ,
"Great White" ,
"Hammerhead" ,
"Mako" ,
]) {
await brackets.selectBracket (bracketName);
await brackets.finalize ();
}Select bracket bracketName
await brackets.selectBracket (bracketName);Finalize
await brackets.finalize ();Assert
match 11 is visible
await expect (brackets.match (11 )).toBeVisible ();Source test ("conducts a tournament with many starting brackets" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (16 ));
await impersonate (page);
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
const eventEdit = await admin.editEventInfo ();
await eventEdit.deleteLastBracket ();
await eventEdit.makeAllBracketsStartingBrackets ();
await eventEdit.setBracketFormat (0 , "Single elimination" );
await eventEdit.setBracketFormat (1 , "Single elimination" );
await eventEdit.setBracketFormat (2 , "Swiss" );
await eventEdit.setBracketFormat (3 , "Swiss" );
await eventEdit.save ();
const seeds = new TournamentSeedsPage (page);
await seeds.goto (tournament.id);
await seeds.openStartingBracketsDialog ();
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 seeds.setStartingBracket (i, bracketName);
}
await seeds.saveStartingBrackets ();
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
for (const bracketName of [
"Groups stage" ,
"Great White" ,
"Hammerhead" ,
"Mako" ,
]) {
await brackets.selectBracket (bracketName);
await brackets.finalize ();
}
await expect (brackets.match (11 )).toBeVisible ();
})offers check out until the team's starting bracket starts and drop out after Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/brackets
Tournament brackets page Tournament admin page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression TWO_STARTING_BRACKETS map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: TWO_STARTING_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, [0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({ startingBracketIdx, })) → teams
const teams = await createTeams (
factories,
tournament.id,
[0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({
startingBracketIdx,
})),
);Start bracket tournament tournament.id bracket idx 0
await factories.TournamentFactory .startBracket (tournament.id, {
bracketIdx: 0 ,
});Tournament brackets page Act
Open the tournament brackets page tournament.id, 1
await brackets.goto (tournament.id, 1 );Assert
bracket team name “Team 5” is visible
await expect (brackets.bracketTeamName ("Team 5" )).toBeVisible ();Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open team actions teams[0 ].id
await admin.openTeamActions (teams[0 ].id);Assert
team actions menu item “Drop out” is visible
await expect (admin.teamActionsMenuItem ("Drop out" )).toBeVisible ();team actions menu item /^Check out/ count is 0
await expect (admin.teamActionsMenuItem (/^Check out/)).toHaveCount (0 );Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open team actions teams[4 ].id
await admin.openTeamActions (teams[4 ].id);Assert
team actions menu item /^Check out/ is visible
await expect (admin.teamActionsMenuItem (/^Check out/)).toBeVisible ();team actions menu item “Drop out” count is 0
await expect (admin.teamActionsMenuItem ("Drop out" )).toHaveCount (0 );Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Check started team out teams[4 ].id
await admin.checkStartedTeamOut (teams[4 ].id);Assert
team row teams[4 ].id count is 0
await expect (admin.teamRow (teams[4 ].id)).toHaveCount (0 );Tournament brackets page Act
Open the tournament brackets page tournament.id, 1
await brackets.goto (tournament.id, 1 );Assert
bracket team name “Team 6” is visible
await expect (brackets.bracketTeamName ("Team 6" )).toBeVisible ();bracket team name “Team 5” count is 0
await expect (brackets.bracketTeamName ("Team 5" )).toHaveCount (0 );Source test ("offers check out until the team's starting bracket starts and drop out after" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: TWO_STARTING_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});
const teams = await createTeams (
factories,
tournament.id,
[0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({
startingBracketIdx,
})),
);
await factories.TournamentFactory .startBracket (tournament.id, {
bracketIdx: 0 ,
});
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id, 1 );
await expect (brackets.bracketTeamName ("Team 5" )).toBeVisible ();
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.openTeamActions (teams[0 ].id);
await expect (admin.teamActionsMenuItem ("Drop out" )).toBeVisible ();
await expect (admin.teamActionsMenuItem (/^Check out/)).toHaveCount (0 );
await admin.goto (tournament.id);
await admin.openTeamActions (teams[4 ].id);
await expect (admin.teamActionsMenuItem (/^Check out/)).toBeVisible ();
await expect (admin.teamActionsMenuItem ("Drop out" )).toHaveCount (0 );
await admin.goto (tournament.id);
await admin.checkStartedTeamOut (teams[4 ].id);
await expect (admin.teamRow (teams[4 ].id)).toHaveCount (0 );
await brackets.goto (tournament.id, 1 );
await expect (brackets.bracketTeamName ("Team 6" )).toBeVisible ();
await expect (brackets.bracketTeamName ("Team 5" )).toHaveCount (0 );
})starts a starting bracket with the maps prepared for another starting bracket Renders/to/:id /to/:id/brackets
Tournament brackets page Bracket map list dialog Tournament brackets page Bracket map list dialog Tournament brackets page Arrange
[division1, division2] = TWO_STARTING_BRACKETS
const [division1, division2] = TWO_STARTING_BRACKETS ;Create tournament author Sendou start times startedTournamentTimes () bracket progression division1, ...division2 start time dateToDatabaseTimestamp (addHours (new Date (), 1 )) map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
// maps can only be prepared for a bracket that can't be started yet
bracketProgression: [
division1,
{
...division2,
startTime: dateToDatabaseTimestamp (addHours (new Date (), 1 )),
},
],
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, [0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({ startingBracketIdx, }))
await createTeams (
factories,
tournament.id,
[0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({
startingBracketIdx,
})),
);Tournament brackets page Act
Open the tournament brackets page tournament.id, 1
await brackets.goto (tournament.id, 1 );Open prepare maps dialog → prepareDialog
const prepareDialog = await brackets.openPrepareMapsDialog ();Bracket map list dialog Act
Confirm
await prepareDialog.confirm ();Tournament brackets page Act
Open the tournament brackets page tournament.id, 1
await brackets.goto (tournament.id, 1 );Assert
prepared maps check icon is visible
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();Act
Open the tournament brackets page tournament.id, 0
await brackets.goto (tournament.id, 0 );Open finalize dialog → startDialog
const startDialog = await brackets.openFinalizeDialog ();Bracket map list dialog Assert
prepared by text is visible
await expect (startDialog.locators.preparedByText).toBeVisible ();Act
Confirm
await startDialog.confirm ();Tournament brackets page Assert
first matches is visible
await expect (brackets.locators.matches.first ()).toBeVisible ();Source test ("starts a starting bracket with the maps prepared for another starting bracket" , async ({
page,
factories,
}) => {
const [division1, division2] = TWO_STARTING_BRACKETS ;
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
// maps can only be prepared for a bracket that can't be started yet
bracketProgression: [
division1,
{
...division2,
startTime: dateToDatabaseTimestamp (addHours (new Date (), 1 )),
},
],
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (
factories,
tournament.id,
[0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ].map ((startingBracketIdx) => ({
startingBracketIdx,
})),
);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id, 1 );
const prepareDialog = await brackets.openPrepareMapsDialog ();
await prepareDialog.confirm ();
await brackets.goto (tournament.id, 1 );
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();
await brackets.goto (tournament.id, 0 );
const startDialog = await brackets.openFinalizeDialog ();
await expect (startDialog.locators.preparedByText).toBeVisible ();
await startDialog.confirm ();
await expect (brackets.locators.matches.first ()).toBeVisible ();
})Renders/calendar/new /to/:id /to/:id/brackets /to/:id/info /to/:id/matches/:mid
Calendar new event page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create user null roles “TOURNAMENT_ORGANIZER” → organizer
const organizer = await factories.UserFactory .create (null , {
roles: ["TOURNAMENT_ORGANIZER" ],
});Calendar new event page Act
Go to new tournament
await newTournament.gotoNewTournament ();Fill Name with “Redemption Arc”
await newTournament.form.fill ("name" , "Redemption Arc" );start time in the past so the brackets can be started right away
Set first date subMinutes (new Date (), 30 )
await newTournament.setFirstDate (subMinutes (new Date (), 30 ));Go to step “maps”
await newTournament.goToStep ("maps" );Check items map picking style “TO”
await newTournament.form.checkItems ("mapPickingStyle" , ["TO" ]);Paste map pool MapPool .SZ
await newTournament.pasteMapPool (MapPool .SZ );groups of 4: top 2 advance to the finals directly, 3rd placers get another shot at the last finals spot through the redemption bracket
Rename bracket 0 , “Groups”
await newTournament.renameBracket (0 , "Groups" );Set bracket format 0 , “Round robin”
await newTournament.setBracketFormat (0 , "Round robin" );Add follow up bracket name “Redemption” format “Single elimination” placements 3
await newTournament.addFollowUpBracket ({
name: "Redemption" ,
format: "Single elimination" ,
placements: [3 ],
});Add follow up bracket name “Finals” format “Single elimination” placements 1 , 2
await newTournament.addFollowUpBracket ({
name: "Finals" ,
format: "Single elimination" ,
placements: [1 , 2 ],
});Connect 1 , 2 , 1
await newTournament.connect (1 , 2 , [1 ]);Save
await newTournament.save ();Assert
the page URL is /\/to\/\d+/
await expect (page).toHaveURL (/\/to\/\d+/);Arrange
tournamentId = Number (page.url ().match (/\/to\/(\d+)/)![1 ])
const tournamentId = Number (page.url ().match (/\/to\/(\d+)/)![1 ]);Create teams tournamentId, teamSeeds (8 )
await createTeams (factories, tournamentId, teamSeeds (8 ));Play out tournament tournamentId 0
await factories.TournamentFactory .playOut (tournamentId, 0 );Tournament brackets page Act
Open the tournament brackets page tournamentId
await brackets.goto (tournamentId);Select bracket “Groups”
await brackets.selectBracket ("Groups" );Group standings team names 2 → groups
const groups = await brackets.groupStandingsTeamNames (2 );redemptionTeamNames = groups.map ((group) => group[2 ])
const redemptionTeamNames = groups.map ((group) => group[2 ]);the finals can not be started before the redemption bracket has been played out
Select bracket “Finals”
await brackets.selectBracket ("Finals" );Assert
teams pending from sources text is visible
await expect (brackets.locators.teamsPendingFromSourcesText).toBeVisible ();finalize bracket button is not visible
await isNotVisible (brackets.locators.finalizeBracketButton);Act
Select bracket “Redemption”
await brackets.selectBracket ("Redemption" );Finalize
await brackets.finalize ();redemptionMatchId = Number ( await brackets.locators.matches.first ().getAttribute ("data-match-id" ), )
const redemptionMatchId = Number (
await brackets.locators.matches.first ().getAttribute ("data-match-id" ),
);Open match redemptionMatchId → redemptionMatch
const redemptionMatch = await brackets.openMatch (redemptionMatchId);Tournament match page Act
Open tab “action”
await redemptionMatch.openTab ("action" );Report result for team team name redemptionTeamNames[0 ] maps to report 3
await redemptionMatch.reportResultForTeam ({
teamName: redemptionTeamNames[0 ],
mapsToReport: 3 ,
});Back to bracket
await redemptionMatch.backToBracket ();Tournament brackets page Act
Select bracket “Finals”
await brackets.selectBracket ("Finals" );Assert
teams pending from sources text is not visible
await isNotVisible (brackets.locators.teamsPendingFromSourcesText);Act
Finalize
await brackets.finalize ();Assert
the redemption bracket's winner took the last spot in the finals
first brackets viewer › text redemptionTeamNames[0 ] is visible
await expect (
brackets.locators.bracketsViewer
.getByText (redemptionTeamNames[0 ])
.first (),
).toBeVisible ();Source test ("plays out a redemption bracket set up in the tournament creation form" , async ({
page,
factories,
}) => {
test.slow ();
const organizer = await factories.UserFactory .create (null , {
roles: ["TOURNAMENT_ORGANIZER" ],
});
await impersonate (page, organizer.id);
const newTournament = new CalendarNewEventPage (page);
await newTournament.gotoNewTournament ();
await newTournament.form.fill ("name" , "Redemption Arc" );
// start time in the past so the brackets can be started right away
await newTournament.setFirstDate (subMinutes (new Date (), 30 ));
await newTournament.goToStep ("maps" );
await newTournament.form.checkItems ("mapPickingStyle" , ["TO" ]);
await newTournament.pasteMapPool (MapPool .SZ );
// groups of 4: top 2 advance to the finals directly, 3rd placers get
// another shot at the last finals spot through the redemption bracket
await newTournament.renameBracket (0 , "Groups" );
await newTournament.setBracketFormat (0 , "Round robin" );
await newTournament.addFollowUpBracket ({
name: "Redemption" ,
format: "Single elimination" ,
placements: [3 ],
});
await newTournament.addFollowUpBracket ({
name: "Finals" ,
format: "Single elimination" ,
placements: [1 , 2 ],
});
await newTournament.connect (1 , 2 , [1 ]);
await newTournament.save ();
await expect (page).toHaveURL (/\/to\/\d+/);
const tournamentId = Number (page.url ().match (/\/to\/(\d+)/)![1 ]);
await createTeams (factories, tournamentId, teamSeeds (8 ));
await factories.TournamentFactory .playOut (tournamentId, 0 );
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournamentId);
await brackets.selectBracket ("Groups" );
const groups = await brackets.groupStandingsTeamNames (2 );
const redemptionTeamNames = groups.map ((group) => group[2 ]);
// the finals can not be started before the redemption bracket has been played out
await brackets.selectBracket ("Finals" );
await expect (brackets.locators.teamsPendingFromSourcesText).toBeVisible ();
await isNotVisible (brackets.locators.finalizeBracketButton);
await brackets.selectBracket ("Redemption" );
await brackets.finalize ();
const redemptionMatchId = Number (
await brackets.locators.matches.first ().getAttribute ("data-match-id" ),
);
const redemptionMatch = await brackets.openMatch (redemptionMatchId);
await redemptionMatch.openTab ("action" );
await redemptionMatch.reportResultForTeam ({
teamName: redemptionTeamNames[0 ],
mapsToReport: 3 ,
});
await redemptionMatch.backToBracket ();
await brackets.selectBracket ("Finals" );
await isNotVisible (brackets.locators.teamsPendingFromSourcesText);
await brackets.finalize ();
// the redemption bracket's winner took the last spot in the finals
await expect (
brackets.locators.bracketsViewer
.getByText (redemptionTeamNames[0 ])
.first (),
).toBeVisible ();
})prepares maps (including third place match linking) Renders/to/:id /to/:id/brackets
Tournament brackets page Bracket map list dialog Tournament brackets page Bracket map list dialog Tournament brackets page Bracket map list dialog Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression SOS_BRACKETS map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Select bracket “Great White”
await brackets.selectBracket ("Great White" );Open prepare maps dialog → prepareDialog
const prepareDialog = await brackets.openPrepareMapsDialog ();Bracket map list dialog Act
Set expected teams 8
await prepareDialog.setExpectedTeams (8 );Confirm
await prepareDialog.confirm ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Select bracket “Great White”
await brackets.selectBracket ("Great White" );Assert
prepared maps check icon is visible
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();Act
we did not prepare maps for group stage
Select bracket “Groups stage”
await brackets.selectBracket ("Groups stage" );Assert
prepared maps check icon is not visible
await isNotVisible (brackets.locators.preparedMapsCheckIcon);Act
reuses the maps prepared for Great White
Select bracket “Hammerhead”
await brackets.selectBracket ("Hammerhead" );Assert
prepared maps check icon is visible
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();Act
Select bracket “Great White”
await brackets.selectBracket ("Great White" );Open prepare maps dialog → unlinkDialog
const unlinkDialog = await brackets.openPrepareMapsDialog ();Bracket map list dialog Act
Unlink finals third place match
await unlinkDialog.unlinkFinalsThirdPlaceMatch ();Increase map count “last”
await unlinkDialog.increaseMapCount ("last" );Confirm
await unlinkDialog.confirm ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Select bracket “Great White”
await brackets.selectBracket ("Great White" );Open prepare maps dialog → relinkDialog
const relinkDialog = await brackets.openPrepareMapsDialog ();Bracket map list dialog Assert
linking is offered again since the finals and third place match maps now differ
link finals button is visible
await expect (relinkDialog.locators.linkFinalsButton).toBeVisible ();Source test ("prepares maps (including third place match linking)" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SOS_BRACKETS ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.selectBracket ("Great White" );
const prepareDialog = await brackets.openPrepareMapsDialog ();
await prepareDialog.setExpectedTeams (8 );
await prepareDialog.confirm ();
await brackets.goto (tournament.id);
await brackets.selectBracket ("Great White" );
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();
// we did not prepare maps for group stage
await brackets.selectBracket ("Groups stage" );
await isNotVisible (brackets.locators.preparedMapsCheckIcon);
// reuses the maps prepared for Great White
await brackets.selectBracket ("Hammerhead" );
await expect (brackets.locators.preparedMapsCheckIcon).toBeVisible ();
await brackets.selectBracket ("Great White" );
const unlinkDialog = await brackets.openPrepareMapsDialog ();
await unlinkDialog.unlinkFinalsThirdPlaceMatch ();
await unlinkDialog.increaseMapCount ("last" );
await unlinkDialog.confirm ();
await brackets.goto (tournament.id);
await brackets.selectBracket ("Great White" );
const relinkDialog = await brackets.openPrepareMapsDialog ();
// linking is offered again since the finals and third place match maps now differ
await expect (relinkDialog.locators.linkFinalsButton).toBeVisible ();
})Tournament bracket pick/ban Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Bracket map list dialog Tournament match page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (4 ) → teams
const teams = await createTeams (factories, tournament.id, teamSeeds (4 ));match 2 of the group has the third team as alpha and the second as bravo
matchId = 2
const matchId = 2 ;teamOneCaptainId = teams[2 ].ownerUserId
const teamOneCaptainId = teams[2 ].ownerUserId;teamTwoCaptainId = teams[1 ].ownerUserId
const teamTwoCaptainId = teams[1 ].ownerUserId;Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize dialog → mapListDialog
const mapListDialog = await brackets.openFinalizeDialog ();Bracket map list dialog Act
Toggle pick ban
await mapListDialog.togglePickBan ();Set pick ban pickBan
await mapListDialog.setPickBan (pickBan);Confirm
await mapListDialog.confirm ();If !isCounterpick
if (!isCounterpick) {
for (const captainId of [teamTwoCaptainId, teamOneCaptainId]) {
await impersonate (page, captainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.pickBan ();
}
// once both teams banned the ban prompt is gone and the actual map
// banner takes over.
await expect (match.locators.stageBanner).toBeVisible ();
}For each captainId of teamTwoCaptainId, teamOneCaptainId
for (const captainId of [teamTwoCaptainId, teamOneCaptainId]) {
await impersonate (page, captainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.pickBan ();
}
await impersonate (page, captainId);Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Pick ban
await match.pickBan ();once both teams banned the ban prompt is gone and the actual map banner takes over.
stage banner is visible
await expect (match.locators.stageBanner).toBeVisible ();Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 2 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 2 , setEnds: false });If isCounterpick
if (isCounterpick) {
await match.pickBan ();
}Pick ban
await match.pickBan ();Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 1 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });If isCounterpick
if (isCounterpick) {
// team two won the first map, so its own counterpick after losing the
// second one is the one the mode repeat rule bites on
await expect (match.locators.pickBanModeGroups).toHaveCount (
modeGroupsAfterWinning,
);
await match.pickBan ();
await match.undoLastReport ();
await expect (match.locators.selectWinnerText).toBeVisible ();
await match.reportResult ({
mapsToReport: 1 ,
winner: 1 ,
setEnds: false ,
});
await expect (match.locators.counterpickText).toBeVisible ();
await match.pickBan ("last" );
await expect (match.locators.selectWinnerText).toBeVisible ();
await expect (match.score ([1 , 1 ])).toBeVisible ();
}team two won the first map, so its own counterpick after losing the second one is the one the mode repeat rule bites on
pick ban mode groups count is modeGroupsAfterWinning
await expect (match.locators.pickBanModeGroups).toHaveCount (
modeGroupsAfterWinning,
);Pick ban
await match.pickBan ();Undo last report
await match.undoLastReport ();select winner text is visible
await expect (match.locators.selectWinnerText).toBeVisible ();Report result maps to report 1 winner 1 set ends false
await match.reportResult ({
mapsToReport: 1 ,
winner: 1 ,
setEnds: false ,
});counterpick text is visible
await expect (match.locators.counterpickText).toBeVisible ();Pick ban “last”
await match.pickBan ("last" );select winner text is visible
await expect (match.locators.selectWinnerText).toBeVisible ();score 1 , 1 is visible
await expect (match.score ([1 , 1 ])).toBeVisible ();Source test (`ban/pick ${pickBan}` , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});
const teams = await createTeams (factories, tournament.id, teamSeeds (4 ));
// match 2 of the group has the third team as alpha and the second as bravo
const matchId = 2 ;
const teamOneCaptainId = teams[2 ].ownerUserId;
const teamTwoCaptainId = teams[1 ].ownerUserId;
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const mapListDialog = await brackets.openFinalizeDialog ();
await mapListDialog.togglePickBan ();
await mapListDialog.setPickBan (pickBan);
await mapListDialog.confirm ();
const match = new TournamentMatchPage (page);
if (!isCounterpick) {
for (const captainId of [teamTwoCaptainId, teamOneCaptainId]) {
await impersonate (page, captainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.pickBan ();
}
// once both teams banned the ban prompt is gone and the actual map
// banner takes over.
await expect (match.locators.stageBanner).toBeVisible ();
}
await impersonate (page, teamOneCaptainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 2 , setEnds: false });
if (isCounterpick) {
await match.pickBan ();
}
await impersonate (page, teamTwoCaptainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });
if (isCounterpick) {
// team two won the first map, so its own counterpick after losing the
// second one is the one the mode repeat rule bites on
await expect (match.locators.pickBanModeGroups).toHaveCount (
modeGroupsAfterWinning,
);
await match.pickBan ();
await match.undoLastReport ();
await expect (match.locators.selectWinnerText).toBeVisible ();
await match.reportResult ({
mapsToReport: 1 ,
winner: 1 ,
setEnds: false ,
});
await expect (match.locators.counterpickText).toBeVisible ();
await match.pickBan ("last" );
await expect (match.locators.selectWinnerText).toBeVisible ();
await expect (match.score ([1 , 1 ])).toBeVisible ();
}
})Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Bracket map list dialog Tournament match page Tournament match page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (4 ) → teams
const teams = await createTeams (factories, tournament.id, teamSeeds (4 ));match 2 of the group has the third team (lower seed) as alpha and the second team (higher seed) as bravo
matchId = 2
const matchId = 2 ;higherSeedCaptainId = teams[1 ].ownerUserId
const higherSeedCaptainId = teams[1 ].ownerUserId;lowerSeedCaptainId = teams[2 ].ownerUserId
const lowerSeedCaptainId = teams[2 ].ownerUserId;customFlow = { preSet: [ { action: "BAN" , side: "HIGHER_SEED" }, { action: "BAN" , side: "H …
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" },
],
};Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize dialog → mapListDialog
const mapListDialog = await brackets.openFinalizeDialog ();Bracket map list dialog Act
Set pick ban “CUSTOM”
await mapListDialog.setPickBan ("CUSTOM" );Assert
before set text is visible
await expect (mapListDialog.locators.beforeSetText).toBeVisible ();Act
Confirm with custom flow customFlow
await mapListDialog.confirmWithCustomFlow (customFlow);Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Pick ban
await match.pickBan ();Assert
last ban text is visible
await expect (match.locators.lastBanText).toBeVisible ();Act
Pick ban
await match.pickBan ();Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Pick ban
await match.pickBan ();Assert
last ban text is visible
await expect (match.locators.lastBanText).toBeVisible ();Act
Pick ban
await match.pickBan ();Assert
the roll auto-executes after the last ban
stage banner is visible
await expect (match.locators.stageBanner).toBeVisible ();Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 1 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });Assert
score 1 , 0 is visible
await expect (match.score ([1 , 0 ])).toBeVisible ();PostGame: the winner (alpha, whose captain is still impersonated) bans 2 maps
ban A map text is visible
await expect (match.locators.banAMapText).toBeVisible ();Act
Pick ban
await match.pickBan ();Assert
last ban text is visible
await expect (match.locators.lastBanText).toBeVisible ();Act
Pick ban
await match.pickBan ();Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await match.openTab ("action" );Assert
pick A map text is visible
await expect (match.locators.pickAMapText).toBeVisible ();Act
Pick ban
await match.pickBan ();Assert
undoing game 1 also deletes the postGame pick/ban events
stage banner is visible
await expect (match.locators.stageBanner).toBeVisible ();Act
Undo last report
await match.undoLastReport ();Assert
score 0 , 0 is visible
await expect (match.score ([0 , 0 ])).toBeVisible ();stage banner is visible
await expect (match.locators.stageBanner).toBeVisible ();Act
the postGame cycle restarts
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 1 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });Assert
score 1 , 0 is visible
await expect (match.score ([1 , 0 ])).toBeVisible ();ban A map text is visible
await expect (match.locators.banAMapText).toBeVisible ();Source test ("ban/pick CUSTOM flow" , async ({ page, factories }) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});
const teams = await createTeams (factories, tournament.id, teamSeeds (4 ));
// match 2 of the group has the third team (lower seed) as alpha and the
// second team (higher seed) as bravo
const matchId = 2 ;
const higherSeedCaptainId = teams[1 ].ownerUserId;
const lowerSeedCaptainId = teams[2 ].ownerUserId;
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" },
],
};
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const mapListDialog = await brackets.openFinalizeDialog ();
await mapListDialog.setPickBan ("CUSTOM" );
await expect (mapListDialog.locators.beforeSetText).toBeVisible ();
await mapListDialog.confirmWithCustomFlow (customFlow);
const match = new TournamentMatchPage (page);
// PreSet: higher seed bans 2 maps
await impersonate (page, higherSeedCaptainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.pickBan ();
await expect (match.locators.lastBanText).toBeVisible ();
await match.pickBan ();
// PreSet: lower seed bans 2 maps
await impersonate (page, lowerSeedCaptainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await match.pickBan ();
await expect (match.locators.lastBanText).toBeVisible ();
await match.pickBan ();
// the roll auto-executes after the last ban
await expect (match.locators.stageBanner).toBeVisible ();
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });
await expect (match.score ([1 , 0 ])).toBeVisible ();
// PostGame: the winner (alpha, whose captain is still impersonated) bans 2 maps
await expect (match.locators.banAMapText).toBeVisible ();
await match.pickBan ();
await expect (match.locators.lastBanText).toBeVisible ();
await match.pickBan ();
// PostGame: the loser (bravo) picks a map
await impersonate (page, higherSeedCaptainId);
await match.goto ({ tournamentId: tournament.id, matchId });
await match.openTab ("action" );
await expect (match.locators.pickAMapText).toBeVisible ();
await match.pickBan ();
// undoing game 1 also deletes the postGame pick/ban events
await expect (match.locators.stageBanner).toBeVisible ();
await match.undoLastReport ();
await expect (match.score ([0 , 0 ])).toBeVisible ();
await expect (match.locators.stageBanner).toBeVisible ();
// the postGame cycle restarts
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });
await expect (match.score ([1 , 0 ])).toBeVisible ();
await expect (match.locators.banAMapText).toBeVisible ();
})Tournament bracket round robin starts a round robin bracket with unevenly sized groups (5 teams) Renders/to/:id /to/:id/brackets
Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression RR_TO_SE_WITH_UNDERGROUND map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE_WITH_UNDERGROUND ,
mapPoolMaps: TO_MAP_POOL ,
});5 checked in teams -> groups of 3 and 2 with different round counts
Create teams tournament.id, teamSeeds (5 )
await createTeams (factories, tournament.id, teamSeeds (5 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();Assert
bracket starts without an "Invalid map count" error -> matches are rendered
first matches is visible
await expect (brackets.locators.matches.first ()).toBeVisible ();Source test ("starts a round robin bracket with unevenly sized groups (5 teams)" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE_WITH_UNDERGROUND ,
mapPoolMaps: TO_MAP_POOL ,
});
// 5 checked in teams -> groups of 3 and 2 with different round counts
await createTeams (factories, tournament.id, teamSeeds (5 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
// bracket starts without an "Invalid map count" error -> matches are rendered
await expect (brackets.locators.matches.first ()).toBeVisible ();
})organizer edits a match after it is done Renders/to/:id /to/:id/brackets /to/:id/info /to/:id/matches/:mid
Tournament page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, roster size 5 , roster size 5
await createTeams (factories, tournament.id, [
{ rosterSize: 5 },
{ rosterSize: 5 },
]);Tournament page Act
Open the tournament page tournament.id
await tournamentPage.goto (tournament.id);Open brackets → brackets
const brackets = await tournamentPage.nav.openBrackets ();Finalize
await brackets.finalize ();Open match 1 → match
const match = await brackets.openMatch (1 );Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Open tab “admin”
await match.openTab ("admin" );Click match.editResultButton (0 )
await match.editResultButton (0 ).click ();Click match.editResultPlayerCheckbox ("alpha" , 3 )
await match.editResultPlayerCheckbox ("alpha" , 3 ).click ();Click match.editResultPlayerCheckbox ("alpha" , 4 )
await match.editResultPlayerCheckbox ("alpha" , 4 ).click ();RR collects KO, which shows the edit went through
Check match.locators.koCheckbox
await match.locators.koCheckbox.check ();Save result 0
await match.saveResult (0 );Assert
match.editResultButton (0 ) is visible
await expect (match.editResultButton (0 )).toBeVisible ();match.locators.koResultText is visible
await expect (match.locators.koResultText).toBeVisible ();Source test ("organizer edits a match after it is done" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, [
{ rosterSize: 5 },
{ rosterSize: 5 },
]);
await impersonate (page);
const tournamentPage = new TournamentPage (page);
await tournamentPage.goto (tournament.id);
const brackets = await tournamentPage.nav.openBrackets ();
await brackets.finalize ();
const match = await brackets.openMatch (1 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.openTab ("admin" );
await match.editResultButton (0 ).click ();
await match.editResultPlayerCheckbox ("alpha" , 3 ).click ();
await match.editResultPlayerCheckbox ("alpha" , 4 ).click ();
// RR collects KO, which shows the edit went through
await match.locators.koCheckbox.check ();
await match.saveResult (0 );
await expect (match.editResultButton (0 )).toBeVisible ();
await expect (match.locators.koResultText).toBeVisible ();
})changes to picked map pool & best of Renders/calendar/new /to/:id /to/:id/admin /to/:id/admin /to/:id/brackets /to/:id/info /to/:id/matches/:mid
Tournament admin page Calendar new event page Tournament brackets page Bracket map list dialog Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (2 )
await createTeams (factories, tournament.id, teamSeeds (2 ));Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Edit event info → eventEdit
const eventEdit = await admin.editEventInfo ();Calendar new event page Act
Go to step “maps”
await eventEdit.goToStep ("maps" );Clear map pool
await eventEdit.clearMapPool ();Paste map pool MapPool .CB
await eventEdit.pasteMapPool (MapPool .CB );Save
await eventEdit.save ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize dialog → mapListDialog
const mapListDialog = await brackets.openFinalizeDialog ();Bracket map list dialog Act
Increase map count “first”
await mapListDialog.increaseMapCount ("first" );Confirm
await mapListDialog.confirm ();Tournament brackets page Act
Open match 1 → match
const match = await brackets.openMatch (1 );Tournament match page Assert
Bo5 of clam blitz: one mode icon + ×5 count text
mode progress “CB” is visible
await expect (match.modeProgress ("CB" )).toBeVisible ();map count text 5 is visible
await expect (match.mapCountText (5 )).toBeVisible ();Source test ("changes to picked map pool & best of" , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (2 ));
await impersonate (page);
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
const eventEdit = await admin.editEventInfo ();
await eventEdit.goToStep ("maps" );
await eventEdit.clearMapPool ();
await eventEdit.pasteMapPool (MapPool .CB );
await eventEdit.save ();
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const mapListDialog = await brackets.openFinalizeDialog ();
await mapListDialog.increaseMapCount ("first" );
await mapListDialog.confirm ();
const match = await brackets.openMatch (1 );
// Bo5 of clam blitz: one mode icon + ×5 count text
await expect (match.modeProgress ("CB" )).toBeVisible ();
await expect (match.mapCountText (5 )).toBeVisible ();
})reopens round robin match and changes score Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));the two passes play rounds 1 (matches 1 & 2) and 2 (matches 3 & 4), so the winner of a match whose participants already played on gets changed
Start bracket tournament tournament.id
await factories.TournamentFactory .startBracket (tournament.id);Play matches tournament tournament.id
await factories.TournamentFactory .playMatches (tournament.id);Play matches tournament tournament.id
await factories.TournamentFactory .playMatches (tournament.id);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open match 2 → match
const match = await brackets.openMatch (2 );Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );Reopen
await match.reopen ();Assert
switching tabs is a defaultShouldRevalidate: false navigation that would abort the in-flight post-reopen revalidation, leaving the match "over"
reopen match button is not visible
await isNotVisible (match.locators.reopenMatchButton);Act
Open tab “action”
await match.openTab ("action" );Undo last report
await match.undoLastReport ();Report result maps to report 2 winner 2 set ends true
await match.reportResult ({
mapsToReport: 2 ,
winner: 2 ,
setEnds: true ,
});Source test ("reopens round robin match and changes score" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
// the two passes play rounds 1 (matches 1 & 2) and 2 (matches 3 & 4), so the
// winner of a match whose participants already played on gets changed
await factories.TournamentFactory .startBracket (tournament.id);
await factories.TournamentFactory .playMatches (tournament.id);
await factories.TournamentFactory .playMatches (tournament.id);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const match = await brackets.openMatch (2 );
await match.openTab ("admin" );
await match.reopen ();
// switching tabs is a `defaultShouldRevalidate: false` navigation that would
// abort the in-flight post-reopen revalidation, leaving the match "over"
await isNotVisible (match.locators.reopenMatchButton);
await match.openTab ("action" );
await match.undoLastReport ();
await match.reportResult ({
mapsToReport: 2 ,
winner: 2 ,
setEnds: true ,
});
})reopening round robin match does not lock already-unlocked matches (issue #2690) Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));playing R1 (matches 1 and 2) unlocks R2
Start bracket tournament tournament.id
await factories.TournamentFactory .startBracket (tournament.id);Play matches tournament tournament.id
await factories.TournamentFactory .playMatches (tournament.id);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);match 3 is R2: start it without completing it
Open match 3 → match
let match = await brackets.openMatch (3 );Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 set ends false
await match.reportResult ({ mapsToReport: 1 , setEnds: false });Back to bracket
await match.backToBracket ();Tournament brackets page Act
reopening the R1 match simulates a score misreport correction
Open match 1 → match
match = await brackets.openMatch (1 ); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );Reopen
await match.reopen ();Back to bracket
await match.backToBracket ();Tournament brackets page Act
the R2 match already in progress used to become locked and unplayable
Open match 3 → match
match = await brackets.openMatch (3 ); Tournament match page Assert
score 1 , 0 is visible
await expect (match.score ([1 , 0 ])).toBeVisible ();Act
Open tab “action”
await match.openTab ("action" );Assert
winner radio 1 is visible
await expect (match.winnerRadio (1 )).toBeVisible ();Source test ("reopening round robin match does not lock already-unlocked matches (issue #2690)" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
// playing R1 (matches 1 and 2) unlocks R2
await factories.TournamentFactory .startBracket (tournament.id);
await factories.TournamentFactory .playMatches (tournament.id);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
// match 3 is R2: start it without completing it
let match = await brackets.openMatch (3 );
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , setEnds: false });
await match.backToBracket ();
// reopening the R1 match simulates a score misreport correction
match = await brackets.openMatch (1 );
await match.openTab ("admin" );
await match.reopen ();
await match.backToBracket ();
// the R2 match already in progress used to become locked and unplayable
match = await brackets.openMatch (3 );
await expect (match.score ([1 , 0 ])).toBeVisible ();
await match.openTab ("action" );
await expect (match.winnerRadio (1 )).toBeVisible ();
})user no screen setting affects tournament match Renders/settings /to/:id /to/:id/brackets /to/:id/matches/:mid
Match profile page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL enable no screen toggle true → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
enableNoScreenToggle: true ,
});Create teams tournament.id, members Sendou , ...teamSeeds (3 )
await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID ] },
...teamSeeds (3 ),
]);Match profile page Act
Open the match profile page
await matchProfile.goto ();Check [Accessibility] Avoid Splattercolor Screen
await matchProfile.form.check ("noScreen" );Save
await matchProfile.save ();Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();match 1 has Sendou's team in it, match 2 does not
Open match 1 → ownMatch
const ownMatch = await brackets.openMatch (1 );Tournament match page Assert
screen banned is visible
await expect (ownMatch.locators.screenBanned).toBeVisible ();Act
Back to bracket
await ownMatch.backToBracket ();Tournament brackets page Act
Open match 2 → otherMatch
const otherMatch = await brackets.openMatch (2 );Tournament match page Assert
screen allowed is visible
await expect (otherMatch.locators.screenAllowed).toBeVisible ();Source test ("user no screen setting affects tournament match" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
enableNoScreenToggle: true ,
});
await createTeams (factories, tournament.id, [
{ members: [ADMIN_ID ] },
...teamSeeds (3 ),
]);
await impersonate (page);
const matchProfile = new MatchProfilePage (page);
await matchProfile.goto ();
await matchProfile.form.check ("noScreen" );
await matchProfile.save ();
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
// match 1 has Sendou's team in it, match 2 does not
const ownMatch = await brackets.openMatch (1 );
await expect (ownMatch.locators.screenBanned).toBeVisible ();
await ownMatch.backToBracket ();
const otherMatch = await brackets.openMatch (2 );
await expect (otherMatch.locators.screenAllowed).toBeVisible ();
})hosts a 'play all' round robin stage Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Bracket map list dialog Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression ROUND_ROBIN map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (2 )
await createTeams (factories, tournament.id, teamSeeds (2 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open finalize dialog → mapListDialog
const mapListDialog = await brackets.openFinalizeDialog ();Bracket map list dialog Act
Set count type “PLAY_ALL”
await mapListDialog.setCountType ("PLAY_ALL" );Confirm
await mapListDialog.confirm ();Tournament brackets page Act
Open match 1 → match
const match = await brackets.openMatch (1 );Tournament match page Assert
play all text 3 is visible
await expect (match.playAllText (3 )).toBeVisible ();Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 3
await match.reportResult ({ mapsToReport: 3 });Source test ("hosts a 'play all' round robin stage" , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: ROUND_ROBIN ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (2 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
const mapListDialog = await brackets.openFinalizeDialog ();
await mapListDialog.setCountType ("PLAY_ALL" );
await mapListDialog.confirm ();
const match = await brackets.openMatch (1 );
await expect (match.playAllText (3 )).toBeVisible ();
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 3 });
})Tournament brackets page Chat sidebar Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracketProgression map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression,
mapPoolMaps: TO_MAP_POOL ,
});enough teams for the bracket to be scrolled past the first screen
Create teams tournament.id, teamSeeds (32 )
await createTeams (factories, tournament.id, teamSeeds (32 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();the elimination bracket's height does not follow the finalize controls above it going away
Reload
await brackets.reload ();Chat sidebar Act
match pages open the chat, which narrows the bracket: open it from the start for one layout throughout
Open
await new ChatSidebar (page).open ();If selectGroup
if (selectGroup) {
const otherGroupMatchId = await brackets.lastMatchId ();
await brackets.openGroup (selectGroup);
await isNotVisible (brackets.match (otherGroupMatchId));
}Last match id → otherGroupMatchId
const otherGroupMatchId = await brackets.lastMatchId ();Open group selectGroup
await brackets.openGroup (selectGroup);match otherGroupMatchId is not visible
await isNotVisible (brackets.match (otherGroupMatchId));Tournament brackets page Act
Last match id → matchId
const matchId = await brackets.lastMatchId ();Scroll match near top left matchId
await brackets.scrollMatchNearTopLeft (matchId);Match viewport position matchId → leftAt
const leftAt = await brackets.matchViewportPosition (matchId);expectAtLeftSpot = () => expect .poll (() => brackets.matchViewportPosition (matchId)) .toEqual (le…
const expectAtLeftSpot = () =>
expect
.poll (() => brackets.matchViewportPosition (matchId))
.toEqual (leftAt);Open match matchId → match
const match = await brackets.openMatch (matchId);Tournament match page Act
Back to bracket
await match.backToBracket ();Assert
at left spot
await expectAtLeftSpot ();Tournament brackets page Act
Open match matchId
await brackets.openMatch (matchId);Tournament match page Act
Browser back
await match.browserBack ();Assert
at left spot
await expectAtLeftSpot ();Tournament brackets page Act
opened from elsewhere, the spot the bracket was left at does not show the match
Scroll to start
await brackets.scrollToStart ();Tournament match page Act
Open the tournament match page tournament id tournament.id matchId
await match.goto ({ tournamentId: tournament.id, matchId });Back to bracket
await match.backToBracket ();Tournament brackets page Assert
match matchId be in viewport
await expect (brackets.match (matchId)).toBeInViewport ();Source test (`returns to the spot a ${name} bracket was left at` , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression,
mapPoolMaps: TO_MAP_POOL ,
});
// enough teams for the bracket to be scrolled past the first screen
await createTeams (factories, tournament.id, teamSeeds (32 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
// the elimination bracket's height does not follow the finalize controls above it going away
await brackets.reload ();
// match pages open the chat, which narrows the bracket: open it from the start for one layout throughout
await new ChatSidebar (page).open ();
if (selectGroup) {
const otherGroupMatchId = await brackets.lastMatchId ();
await brackets.openGroup (selectGroup);
await isNotVisible (brackets.match (otherGroupMatchId));
}
const matchId = await brackets.lastMatchId ();
await brackets.scrollMatchNearTopLeft (matchId);
const leftAt = await brackets.matchViewportPosition (matchId);
const expectAtLeftSpot = () =>
expect
.poll (() => brackets.matchViewportPosition (matchId))
.toEqual (leftAt);
const match = await brackets.openMatch (matchId);
await match.backToBracket ();
await expectAtLeftSpot ();
await brackets.openMatch (matchId);
await match.browserBack ();
await expectAtLeftSpot ();
// opened from elsewhere, the spot the bracket was left at does not show the match
await brackets.scrollToStart ();
await match.goto ({ tournamentId: tournament.id, matchId });
await match.backToBracket ();
await expect (brackets.match (matchId)).toBeInViewport ();
})Tournament bracket swiss swiss tournament with bracket advancing/unadvancing & dropping out a team Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression SWISS_TO_TOP_CUT map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SWISS_TO_TOP_CUT ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (16 )
await createTeams (factories, tournament.id, teamSeeds (16 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();For each matchId of 1 , 2 , 3 , 4
for (const matchId of [1 , 2 , 3 , 4 ]) {
const match = await brackets.openMatch (matchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}Open match matchId → match
const match = await brackets.openMatch (matchId);Open tab “action”
await match.openTab ("action" );Report result maps to report 2
await match.reportResult ({ mapsToReport: 2 });Back to bracket
await match.backToBracket ();Assert
start round button is visible
await expect (brackets.locators.startRoundButton).toBeVisible ();Act
Open group “B”
await brackets.openGroup ("B" );Assert
start round button is not visible
await isNotVisible (brackets.locators.startRoundButton);Act
Open group “A”
await brackets.openGroup ("A" );Start round
await brackets.startRound ();Assert
match 9 is visible
await expect (brackets.match (9 )).toBeVisible ();Act
Open admin → admin
const admin = await brackets.nav.openAdmin ();drop out the top seed, playing in group A
Drop out team 0
await admin.dropOutTeam (0 );Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Reset round
await brackets.resetRound ();Start round
await brackets.startRound ();Assert
bye team is visible
await expect (brackets.locators.byeTeam).toBeVisible ();Source test ("swiss tournament with bracket advancing/unadvancing & dropping out a team" , async ({
page,
factories,
}) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SWISS_TO_TOP_CUT ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (16 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
for (const matchId of [1 , 2 , 3 , 4 ]) {
const match = await brackets.openMatch (matchId);
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 2 });
await match.backToBracket ();
}
await expect (brackets.locators.startRoundButton).toBeVisible ();
await brackets.openGroup ("B" );
await isNotVisible (brackets.locators.startRoundButton);
await brackets.openGroup ("A" );
await brackets.startRound ();
await expect (brackets.match (9 )).toBeVisible ();
const admin = await brackets.nav.openAdmin ();
// drop out the top seed, playing in group A
await admin.dropOutTeam (0 );
await brackets.goto (tournament.id);
await brackets.resetRound ();
await brackets.startRound ();
await expect (brackets.locators.byeTeam).toBeVisible ();
})swiss tournament with early advance replaying & giving a bye to a lone team Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Tournament match page Tournament brackets page Arrange
Create tournament author Sendou start times startedTournamentTimes () bracket progression SWISS_EARLY_ADVANCE_TO_TOP_CUT map pool maps TO_MAP_POOL → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SWISS_EARLY_ADVANCE_TO_TOP_CUT ,
mapPoolMaps: TO_MAP_POOL ,
});Create teams tournament.id, teamSeeds (3 )
await createTeams (factories, tournament.id, teamSeeds (3 ));Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Finalize
await brackets.finalize ();round 1: Team 1 vs. Team 2, Team 3 gets a bye
Open match 1 → roundOneMatch
const roundOneMatch = await brackets.openMatch (1 );Tournament match page Act
Open tab “action”
await roundOneMatch.openTab ("action" );Report result for team team name “Team 1” maps to report 2
await roundOneMatch.reportResultForTeam ({
teamName: "Team 1" ,
mapsToReport: 2 ,
});Back to bracket
await roundOneMatch.backToBracket ();Tournament brackets page Act
round 2: Team 1 vs. Team 3, Team 2 gets a bye
Start round
await brackets.startRound ();Open match 3 → roundTwoMatch
const roundTwoMatch = await brackets.openMatch (3 );Tournament match page Act
Open tab “action”
await roundTwoMatch.openTab ("action" );Team 3 reaches the advance threshold and is done with the bracket
Report result for team team name “Team 3” maps to report 2
await roundTwoMatch.reportResultForTeam ({
teamName: "Team 3" ,
mapsToReport: 2 ,
});Back to bracket
await roundTwoMatch.backToBracket ();Tournament brackets page Act
round 3: Team 1 & Team 2 are the only ones left so they replay their round 1 match
Start round
await brackets.startRound ();Assert
match 5 contains “Team 1”
await expect (brackets.match (5 )).toContainText ("Team 1" );match 5 contains “Team 2”
await expect (brackets.match (5 )).toContainText ("Team 2" );Act
Open match 5 → roundThreeMatch
const roundThreeMatch = await brackets.openMatch (5 );Tournament match page Act
Open tab “action”
await roundThreeMatch.openTab ("action" );Report result for team team name “Team 1” maps to report 2
await roundThreeMatch.reportResultForTeam ({
teamName: "Team 1" ,
mapsToReport: 2 ,
});Back to bracket
await roundThreeMatch.backToBracket ();Tournament brackets page Act
round 4: Team 2 is alone in the round so they get a bye
Start round
await brackets.startRound ();Assert
bye team reads “BYE: Team 3” , “BYE: Team 2” , “BYE: Team 2”
await expect (brackets.locators.byeTeam).toHaveText ([
"BYE: Team 3" ,
"BYE: Team 2" ,
"BYE: Team 2" ,
]);every team has advanced so the last round is never played
round label 5 is not visible
await isNotVisible (brackets.roundLabel (5 ));start round button is not visible
await isNotVisible (brackets.locators.startRoundButton);Act
Select bracket “Top Cut”
await brackets.selectBracket ("Top Cut" );Finalize
await brackets.finalize ();Assert
match 8 is visible
await expect (brackets.match (8 )).toBeVisible ();Source test ("swiss tournament with early advance replaying & giving a bye to a lone team" , async ({
page,
factories,
}) => {
test.slow ();
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: SWISS_EARLY_ADVANCE_TO_TOP_CUT ,
mapPoolMaps: TO_MAP_POOL ,
});
await createTeams (factories, tournament.id, teamSeeds (3 ));
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await brackets.finalize ();
// round 1: Team 1 vs. Team 2, Team 3 gets a bye
const roundOneMatch = await brackets.openMatch (1 );
await roundOneMatch.openTab ("action" );
await roundOneMatch.reportResultForTeam ({
teamName: "Team 1" ,
mapsToReport: 2 ,
});
await roundOneMatch.backToBracket ();
// round 2: Team 1 vs. Team 3, Team 2 gets a bye
await brackets.startRound ();
const roundTwoMatch = await brackets.openMatch (3 );
await roundTwoMatch.openTab ("action" );
// Team 3 reaches the advance threshold and is done with the bracket
await roundTwoMatch.reportResultForTeam ({
teamName: "Team 3" ,
mapsToReport: 2 ,
});
await roundTwoMatch.backToBracket ();
// round 3: Team 1 & Team 2 are the only ones left so they replay their round 1 match
await brackets.startRound ();
await expect (brackets.match (5 )).toContainText ("Team 1" );
await expect (brackets.match (5 )).toContainText ("Team 2" );
const roundThreeMatch = await brackets.openMatch (5 );
await roundThreeMatch.openTab ("action" );
await roundThreeMatch.reportResultForTeam ({
teamName: "Team 1" ,
mapsToReport: 2 ,
});
await roundThreeMatch.backToBracket ();
// round 4: Team 2 is alone in the round so they get a bye
await brackets.startRound ();
await expect (brackets.locators.byeTeam).toHaveText ([
"BYE: Team 3" ,
"BYE: Team 2" ,
"BYE: Team 2" ,
]);
// every team has advanced so the last round is never played
await isNotVisible (brackets.roundLabel (5 ));
await isNotVisible (brackets.locators.startRoundButton);
await brackets.selectBracket ("Top Cut" );
await brackets.finalize ();
await expect (brackets.match (8 )).toBeVisible ();
})Tournament bracket sets active roster as regular member Renders/to/:id /to/:id/matches/:mid
Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});the team with subs is created second, making it the bravo side of the match
Create teams tournament.id, , roster size 5 → [, teamWithSub]
const [, teamWithSub] = await createTeams (factories, tournament.id, [
{},
{ rosterSize: 5 },
]);Start bracket tournament tournament.id → [match]
const [match] = await factories.TournamentFactory .startBracket (
tournament.id,
);Tournament match page Act
Open the tournament match page tournament id tournament.id match id match.id
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });Assert
active roster needed text is visible
await expect (matchPage.locators.activeRosterNeededText).toBeVisible ();Act
the roster tab opens in editing mode while the active roster is missing
Open tab “rosters”
await matchPage.openTab ("rosters" );Click player checkbox “bravo” , 0
await matchPage.playerCheckbox ("bravo" , 0 ).click ();Click player checkbox “bravo” , 1
await matchPage.playerCheckbox ("bravo" , 1 ).click ();Click player checkbox “bravo” , 2
await matchPage.playerCheckbox ("bravo" , 2 ).click ();Click player checkbox “bravo” , 3
await matchPage.playerCheckbox ("bravo" , 3 ).click ();Save active roster “bravo”
await matchPage.saveActiveRoster ("bravo" );Open the tournament match page tournament id tournament.id match id match.id
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });Assert
active roster needed text is not visible
await isNotVisible (matchPage.locators.activeRosterNeededText);Act
Open tab “rosters”
await matchPage.openTab ("rosters" );Click edit active roster button “bravo”
await matchPage.editActiveRosterButton ("bravo" ).click ();Click player checkbox “bravo” , 3
await matchPage.playerCheckbox ("bravo" , 3 ).click ();Click player checkbox “bravo” , 4
await matchPage.playerCheckbox ("bravo" , 4 ).click ();Save active roster “bravo”
await matchPage.saveActiveRoster ("bravo" );Assert
edit active roster button “bravo” is visible
await expect (matchPage.editActiveRosterButton ("bravo" )).toBeVisible ();Source test ("sets active roster as regular member" , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});
// the team with subs is created second, making it the bravo side of the match
const [, teamWithSub] = await createTeams (factories, tournament.id, [
{},
{ rosterSize: 5 },
]);
const [match] = await factories.TournamentFactory .startBracket (
tournament.id,
);
await impersonate (page, teamWithSub.ownerUserId);
const matchPage = new TournamentMatchPage (page);
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });
await expect (matchPage.locators.activeRosterNeededText).toBeVisible ();
// the roster tab opens in editing mode while the active roster is missing
await matchPage.openTab ("rosters" );
await matchPage.playerCheckbox ("bravo" , 0 ).click ();
await matchPage.playerCheckbox ("bravo" , 1 ).click ();
await matchPage.playerCheckbox ("bravo" , 2 ).click ();
await matchPage.playerCheckbox ("bravo" , 3 ).click ();
await matchPage.saveActiveRoster ("bravo" );
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });
await isNotVisible (matchPage.locators.activeRosterNeededText);
await matchPage.openTab ("rosters" );
await matchPage.editActiveRosterButton ("bravo" ).click ();
await matchPage.playerCheckbox ("bravo" , 3 ).click ();
await matchPage.playerCheckbox ("bravo" , 4 ).click ();
await matchPage.saveActiveRoster ("bravo" );
await expect (matchPage.editActiveRosterButton ("bravo" )).toBeVisible ();
})only asks for weapons of the maps played Renders/to/:id /to/:id/matches/:mid
Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});the team with subs is created second, making it the bravo side of the match
Create teams tournament.id, , roster size 5 → [, teamWithSub]
const [, teamWithSub] = await createTeams (factories, tournament.id, [
{},
{ rosterSize: 5 },
]);Start bracket tournament tournament.id → [match]
const [match] = await factories.TournamentFactory .startBracket (
tournament.id,
);ROSTER_WITH_VIEWER = [0 , 1 , 2 , 4 ]
const ROSTER_WITH_VIEWER = [0 , 1 , 2 , 4 ];ROSTER_WITHOUT_VIEWER = [0 , 1 , 2 , 3 ]
const ROSTER_WITHOUT_VIEWER = [0 , 1 , 2 , 3 ];viewerUserId = teamWithSub.memberUserIds[4 ]
const viewerUserId = teamWithSub.memberUserIds[4 ];Tournament match page Act
Open the tournament match page tournament id tournament.id match id match.id
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });Map 1: subbed in, reports their weapon like any other player
Open tab “rosters”
await matchPage.openTab ("rosters" );Set active roster “bravo” , ROSTER_WITH_VIEWER
await matchPage.setActiveRoster ("bravo" , ROSTER_WITH_VIEWER );Open tab “action”
await matchPage.openTab ("action" );Report weapon “Splattershot”
await matchPage.reportWeapon ("Splattershot" );Assert
undo weapon button is visible
await expect (matchPage.locators.undoWeaponButton).toBeVisible ();Act
Report result maps to report 1 winner 1 set ends false
await matchPage.reportResult ({
mapsToReport: 1 ,
winner: 1 ,
setEnds: false ,
});Map 2: subbed out, so there is no weapon of theirs to report
Open tab “rosters”
await matchPage.openTab ("rosters" );Set active roster “bravo” , ROSTER_WITHOUT_VIEWER
await matchPage.setActiveRoster ("bravo" , ROSTER_WITHOUT_VIEWER );Open tab “action”
await matchPage.openTab ("action" );Expand weapon reporter
await matchPage.expandWeaponReporter ();Assert
weapon prompt 2 is not visible
await isNotVisible (matchPage.weaponPrompt (2 ));Act
Report result maps to report 1 winner 2 set ends false
await matchPage.reportResult ({
mapsToReport: 1 ,
winner: 2 ,
setEnds: false ,
});Map 3: subbed back in, asked for the map they are about to play
Open tab “rosters”
await matchPage.openTab ("rosters" );Set active roster “bravo” , ROSTER_WITH_VIEWER
await matchPage.setActiveRoster ("bravo" , ROSTER_WITH_VIEWER );Open tab “action”
await matchPage.openTab ("action" );Expand weapon reporter
await matchPage.expandWeaponReporter ();Assert
weapon prompt 3 is visible
await expect (matchPage.weaponPrompt (3 )).toBeVisible ();Act
Undoing map 2 puts it back up for grabs, now with them on the roster
Undo last report
await matchPage.undoLastReport ();Expand weapon reporter
await matchPage.expandWeaponReporter ();Assert
weapon prompt 2 is visible
await expect (matchPage.weaponPrompt (2 )).toBeVisible ();Source test ("only asks for weapons of the maps played" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});
// the team with subs is created second, making it the bravo side of the match
const [, teamWithSub] = await createTeams (factories, tournament.id, [
{},
{ rosterSize: 5 },
]);
const [match] = await factories.TournamentFactory .startBracket (
tournament.id,
);
const ROSTER_WITH_VIEWER = [0 , 1 , 2 , 4 ];
const ROSTER_WITHOUT_VIEWER = [0 , 1 , 2 , 3 ];
const viewerUserId = teamWithSub.memberUserIds[4 ];
await impersonate (page, viewerUserId);
const matchPage = new TournamentMatchPage (page);
await matchPage.goto ({ tournamentId: tournament.id, matchId: match.id });
// Map 1: subbed in, reports their weapon like any other player
await matchPage.openTab ("rosters" );
await matchPage.setActiveRoster ("bravo" , ROSTER_WITH_VIEWER );
await matchPage.openTab ("action" );
await matchPage.reportWeapon ("Splattershot" );
await expect (matchPage.locators.undoWeaponButton).toBeVisible ();
await matchPage.reportResult ({
mapsToReport: 1 ,
winner: 1 ,
setEnds: false ,
});
// Map 2: subbed out, so there is no weapon of theirs to report
await matchPage.openTab ("rosters" );
await matchPage.setActiveRoster ("bravo" , ROSTER_WITHOUT_VIEWER );
await matchPage.openTab ("action" );
await matchPage.expandWeaponReporter ();
await isNotVisible (matchPage.weaponPrompt (2 ));
await matchPage.reportResult ({
mapsToReport: 1 ,
winner: 2 ,
setEnds: false ,
});
// Map 3: subbed back in, asked for the map they are about to play
await matchPage.openTab ("rosters" );
await matchPage.setActiveRoster ("bravo" , ROSTER_WITH_VIEWER );
await matchPage.openTab ("action" );
await matchPage.expandWeaponReporter ();
await expect (matchPage.weaponPrompt (3 )).toBeVisible ();
// Undoing map 2 puts it back up for grabs, now with them on the roster
await matchPage.undoLastReport ();
await matchPage.expandWeaponReporter ();
await expect (matchPage.weaponPrompt (2 )).toBeVisible ();
})adds a sub mid tournament Renders/to/:id /to/:id/brackets /to/:id/join /to/:id/teams/:tid
Tournament team page Tournament join page Arrange
Create tournament author Sendou start times startedTournamentTimes () → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});Create teams tournament.id, teamSeeds (3 ) → teams
const teams = await createTeams (factories, tournament.id, teamSeeds (3 ));Start bracket tournament tournament.id
await factories.TournamentFactory .startBracket (tournament.id);Create user → sub
const sub = await factories.UserFactory .create ();Tournament team page Act
Open the tournament team page tournament.id, teams[2 ].id
await teamPage.goto (tournament.id, teams[2 ].id);Copy sub invite link → inviteLink
const inviteLink = await teamPage.copySubInviteLink ();Tournament join page Act
Go to via invite link inviteLink
await join.gotoViaInviteLink (inviteLink);Join
await join.join ();Assert
the page URL is /brackets/
await expect (page).toHaveURL (/brackets/);Source test ("adds a sub mid tournament" , async ({ page, factories }) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});
const teams = await createTeams (factories, tournament.id, teamSeeds (3 ));
await factories.TournamentFactory .startBracket (tournament.id);
const sub = await factories.UserFactory .create ();
// captain of the last seeded team
await impersonate (page, teams[2 ].ownerUserId);
const teamPage = new TournamentTeamPage (page);
await teamPage.goto (tournament.id, teams[2 ].id);
const inviteLink = await teamPage.copySubInviteLink ();
await impersonate (page, sub.id);
const join = new TournamentJoinPage (page);
await join.gotoViaInviteLink (inviteLink);
await join.join ();
await expect (page).toHaveURL (/brackets/);
})can end set early when past time limit and shows timer on bracket and match page Renders/to/:id /to/:id/brackets /to/:id/matches/:mid
Tournament brackets page Tournament match page Tournament brackets page Tournament match page Arrange
Create tournament author Sendou start times startedTournamentTimes () → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});Create teams tournament.id, teamSeeds (2 )
await createTeams (factories, tournament.id, teamSeeds (2 ));Start bracket tournament tournament.id → [{ id: matchId }]
const [{ id: matchId }] = await factories.TournamentFactory .startBracket (
tournament.id,
);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Open match matchId → match
let match = await brackets.openMatch (matchId);Install time new Date ()
await page.clock.install ({ time: new Date () });Tournament match page Act
Open tab “action”
await match.openTab ("action" );Report result maps to report 1 winner 1 set ends false
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });Assert
match timer is visible
await expect (match.locators.matchTimer).toBeVisible ();Act
Back to bracket
await match.backToBracket ();Tournament brackets page Assert
match matchId is visible
await expect (brackets.match (matchId)).toBeVisible ();match timer matchId is visible
await expect (brackets.matchTimer (matchId)).toBeVisible ();Act
past the 26min limit of a Bo3
Fast forward “30:00”
await page.clock.fastForward ("30:00" );Reload
await brackets.reload ();Open match matchId → match
match = await brackets.openMatch (matchId); Tournament match page Act
Open tab “admin”
await match.openTab ("admin" );End set with random winner
await match.endSetWithRandomWinner ();Assert
final banner is visible
await expect (match.locators.finalBanner).toBeVisible ();Source test ("can end set early when past time limit and shows timer on bracket and match page" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: ADMIN_ID ,
startTimes: startedTournamentTimes (),
});
await createTeams (factories, tournament.id, teamSeeds (2 ));
const [{ id: matchId }] = await factories.TournamentFactory .startBracket (
tournament.id,
);
await impersonate (page);
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
let match = await brackets.openMatch (matchId);
await page.clock.install ({ time: new Date () });
await match.openTab ("action" );
await match.reportResult ({ mapsToReport: 1 , winner: 1 , setEnds: false });
await expect (match.locators.matchTimer).toBeVisible ();
await match.backToBracket ();
await expect (brackets.match (matchId)).toBeVisible ();
await expect (brackets.matchTimer (matchId)).toBeVisible ();
// past the 26min limit of a Bo3
await page.clock.fastForward ("30:00" );
await brackets.reload ();
match = await brackets.openMatch (matchId);
await match.openTab ("admin" );
await match.endSetWithRandomWinner ();
await expect (match.locators.finalBanner).toBeVisible ();
})shows a result reported while the app was suspended Renders/to/:id /to/:id/brackets
Tournament brackets page Arrange
Create in progress match name “Backgrounded Cup” friend N-ZAP → { tournament, matchId }
const { tournament, matchId } = await createInProgressMatch (factories, {
name: "Backgrounded Cup" ,
friendId: NZAP_TEST_ID ,
});Act
the event stream a suspended app comes back to: still connected as far as the page knows, but no longer subscribed to anything, so the broadcast of the result below never reaches it. Answered rather than aborted: a failed subscription reopens the stream, and that reconnect alone catches the page up
Route /\/sse\/[^/]+\/topics$/, (route) => route.fulfill ({ status: 200 })
await page.route (/\/sse\/[^/]+\/topics$/, (route) =>
route.fulfill ({ status: 200 }),
);Tournament brackets page Act
Open the tournament brackets page tournament.id
await brackets.goto (tournament.id);Assert
match scores matchId reads “0” , “0”
await expect (brackets.matchScores (matchId)).toHaveText (["0" , "0" ]);Act
Open second user browser, workerBaseURL → organizer
const organizer = await openSecondUser (browser, workerBaseURL);Try
try {
const matchPage = new TournamentMatchPage (organizer.page);
await matchPage.goto ({ tournamentId: tournament.id, matchId });
await matchPage.openTab ("action" );
await matchPage.reportResult ({ mapsToReport: 1 , setEnds: false });
// the page missed it, the same way a phone with its screen off does
await expect (brackets.matchScores (matchId)).toHaveText (["0" , "0" ]);
await deviceAsleep (page, "10:00" );
await expect (brackets.matchScores (matchId)).toHaveText (["1" , "0" ], {
timeout: 15_000 ,
});
} finally {
await organizer.close ();
}Open the tournament match page tournament id tournament.id matchId
await matchPage.goto ({ tournamentId: tournament.id, matchId });Open tab “action”
await matchPage.openTab ("action" );Report result maps to report 1 set ends false
await matchPage.reportResult ({ mapsToReport: 1 , setEnds: false });the page missed it, the same way a phone with its screen off does
match scores matchId reads “0” , “0”
await expect (brackets.matchScores (matchId)).toHaveText (["0" , "0" ]);Device asleep “10:00”
await deviceAsleep (page, "10:00" );match scores matchId reads “1” , “0”
await expect (brackets.matchScores (matchId)).toHaveText (["1" , "0" ], {
timeout: 15_000 ,
});Source test ("shows a result reported while the app was suspended" , async ({
page,
browser,
workerBaseURL,
factories,
}) => {
test.slow ();
const { tournament, matchId } = await createInProgressMatch (factories, {
name: "Backgrounded Cup" ,
friendId: NZAP_TEST_ID ,
});
// the event stream a suspended app comes back to: still connected as far as
// the page knows, but no longer subscribed to anything, so the broadcast of
// the result below never reaches it. Answered rather than aborted: a failed
// subscription reopens the stream, and that reconnect alone catches the page up
await page.route (/\/sse\/[^/]+\/topics$/, (route) =>
route.fulfill ({ status: 200 }),
);
await impersonate (page, NZAP_TEST_ID );
const brackets = new TournamentBracketsPage (page);
await brackets.goto (tournament.id);
await expect (brackets.matchScores (matchId)).toHaveText (["0" , "0" ]);
const organizer = await openSecondUser (browser, workerBaseURL);
try {
const matchPage = new TournamentMatchPage (organizer.page);
await matchPage.goto ({ tournamentId: tournament.id, matchId });
await matchPage.openTab ("action" );
await matchPage.reportResult ({ mapsToReport: 1 , setEnds: false });
// the page missed it, the same way a phone with its screen off does
await expect (brackets.matchScores (matchId)).toHaveText (["0" , "0" ]);
await deviceAsleep (page, "10:00" );
await expect (brackets.matchScores (matchId)).toHaveText (["1" , "0" ], {
timeout: 15_000 ,
});
} finally {
await organizer.close ();
}
})createTeamse2e/helpers/tournament.ts:232
export async function createTeams (
factories: Factories ,
tournamentId: number ,
seeds: TeamSeed [],
) {
const teams: Awaited <
ReturnType <typeof factories.TournamentTeamFactory .create>
>[] = [];
for (const [i, seed] of seeds.entries ()) {
const presetMembers = seed.members ?? [];
const rosterSize = seed.rosterSize ?? ROSTER_SIZE ;
const fillerUsers = await factories.UserFactory .createMany (
rosterSize - presetMembers.length,
);
// … TournamentBracketsPage.gotoe2e/pages/tournament/tournament-brackets-page.ts:45
No JSDoc
async goto (tournamentId: number , bracketIdx?: number ) {
await navigate ({
page: this .page,
url: tournamentBracketsPage ({ tournamentId, bracketIdx }),
});
}TournamentBracketsPage.openMatche2e/pages/tournament/tournament-brackets-page.ts:207
No JSDoc
async openMatch (matchId: number ) {
await expect (async () => {
await this .match (matchId).click ();
await expect (
this .page.getByTestId ("back-to-bracket-button" ),
).toBeVisible ();
}).toPass ();
// a click that beat hydration loads the match page as a new document
await expectIsHydrated (this .page);
return new TournamentMatchPage (this .page);
}TournamentMatchPage.openTabe2e/pages/tournament/tournament-match-page.ts:121
No JSDoc
async openTab (tab: Tab ) {
// with more members than the minimum, the action tab is hidden until each
// team's active roster is locked in via the rosters tab
if (tab === "action" ) {
await this .ensureActiveRostersSet ();
}
await this .page.getByRole ("tab" , { name: TAB_LABELS [tab] }).click ();
}TournamentMatchPage.reportResulte2e/pages/tournament/tournament-match-page.ts:154
async reportResult ({
mapsToReport,
winner = 1 ,
setEnds = true ,
}: {
mapsToReport: number ;
winner?: Side ;
setEnds?: boolean ;
}) {
for (let i = 0 ; i < mapsToReport; i++) {
const isFinal = setEnds && i === mapsToReport - 1 ;
// the loader revalidation swapping in the next map runs after the POST, so a
// previous winner can still be selected; clicking the about-to-unmount label
// loses the selection on remount
// … TournamentMatchPage.backToBrackete2e/pages/tournament/tournament-match-page.ts:316
No JSDoc
async backToBracket () {
await this .locators.backToBracketButton.click ();
await expect (this .page.getByTestId ("brackets-viewer" )).toBeVisible ();
return new TournamentBracketsPage (this .page);
}TournamentMatchPage.locators.reopenMatchButtone2e/pages/tournament/tournament-match-page.ts:40
No JSDoc
page.getByTestId ("reopen-match-button" ) isNotVisiblee2e/helpers/playwright.ts:745
No JSDoc
export function isNotVisible (locator: Locator ) {
return expect (locator).toHaveCount (0 );
}TournamentMatchPage.undoLastReporte2e/pages/tournament/tournament-match-page.ts:210
No JSDoc
undoLastReport () {
return waitForPOSTResponse (this .page, async () => {
await this .page.getByTestId ("undo-score-button" ).click ();
});
}TournamentMatchPage.scoree2e/pages/tournament/tournament-match-page.ts:130
No JSDoc
score (score: [number , number ]) {
return this .page.getByText (score.join ("-" )).first ();
}TournamentMatchPage.reopene2e/pages/tournament/tournament-match-page.ts:281
No JSDoc
reopen () {
return submit (this .page, "reopen-match-button" );
}TournamentBracketsPage.participantInRounde2e/pages/tournament/tournament-brackets-page.ts:177
No JSDoc
participantInRound (roundId: number , tournamentTeamId: number ) {
return this .page.locator (
`[data-round-id="${roundId}"] [data-participant-id="${tournamentTeamId}"]` ,
);
}TournamentBracketsPagee2e/pages/tournament/tournament-brackets-page.ts:14
No JSDoc
TournamentMatchPagee2e/pages/tournament/tournament-match-page.ts:28
TournamentBracketsPage.roundHeadere2e/pages/tournament/tournament-brackets-page.ts:83
roundHeader (name: string ) {
return this .locators.bracketsViewer.getByText (name, { exact: true });
}TournamentBracketsPage.locators.finalizeTournamentButtone2e/pages/tournament/tournament-brackets-page.ts:33
No JSDoc
page.getByTestId ("finalize-tournament-button" ) TournamentBracketsPage.finalizee2e/pages/tournament/tournament-brackets-page.ts:184
async finalize () {
await this .page.getByTestId ("finalize-bracket-button" ).click ();
await submit (this .page, "confirm-finalize-bracket-button" );
}TournamentBracketsPage.openFinalizeTournamentDialoge2e/pages/tournament/tournament-brackets-page.ts:202
No JSDoc
async openFinalizeTournamentDialog () {
await this .locators.finalizeTournamentButton.click ();
return new FinalizeTournamentDialog (this .page);
}FinalizeTournamentDialog.selectBadgeReceivere2e/pages/tournament/tournament-brackets-page.ts:349
No JSDoc
async selectBadgeReceiver (nth: number , tournamentTeamId: number ) {
await this .page
.getByLabel ("Receiving team" )
.nth (nth)
.selectOption (String (tournamentTeamId));
}FinalizeTournamentDialog.confirme2e/pages/tournament/tournament-brackets-page.ts:360
No JSDoc
confirm () {
return modalClickConfirmButton (this .page);
}UserResultsPage.gotoe2e/pages/user/user-results-page.ts:34
No JSDoc
async goto (userId: number ) {
await navigate ({ page: this .page, url: userResultsPage ({ id: userId }) });
}UserResultsPage.tournamentNamee2e/pages/user/user-results-page.ts:38
No JSDoc
tournamentName (name: string ) {
return this .locators.tournamentNameCells.getByText (name);
}NotificationsPage.gotoe2e/pages/notifications/notifications-page.ts:18
No JSDoc
async goto () {
await navigate ({ page: this .page, url: NOTIFICATIONS_URL });
}NotificationsPage.locators.itemse2e/pages/notifications/notifications-page.ts:14
No JSDoc
this .page.getByTestId ("notification-item" )FinalizeTournamentDialoge2e/pages/tournament/tournament-brackets-page.ts:333
UserResultsPagee2e/pages/user/user-results-page.ts:7
NotificationsPagee2e/pages/notifications/notifications-page.ts:6
TournamentAdminPage.gotoe2e/pages/tournament/tournament-admin-page.ts:50
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentAdminPage (tournamentId) });
}TournamentAdminPage.openStreame2e/pages/tournament/tournament-admin-page.ts:68
No JSDoc
async openStream () {
await this .adminTab ("Stream" ).click ();
return new TournamentAdminStreamPage (this .page);
}TournamentAdminStreamPage.fillAccounte2e/pages/tournament/tournament-admin-stream-page.ts:30
async fillAccount (nth: number , account: string ) {
await this .accountInput (nth).fill (account);
}TournamentAdminStreamPage.savee2e/pages/tournament/tournament-admin-stream-page.ts:38
No JSDoc
save () {
return submit (this .page, "save-cast-accounts-button" );
}TournamentMatchPage.setCastedBye2e/pages/tournament/tournament-match-page.ts:299
setCastedBy (twitchAccount: string ) {
return waitForPOSTResponse (this .page, async () => {
await this .page.locator (`label[for$="-TWITCH:${twitchAccount}"]` ).click ();
});
}TournamentMatchPage.submitCastInfoe2e/pages/tournament/tournament-match-page.ts:306
submitCastInfo () {
return submit (this .page, "cast-info-submit-button" );
}TournamentBracketsPage.locators.castBadgese2e/pages/tournament/tournament-brackets-page.ts:30
No JSDoc
page.getByTestId ("brackets-viewer" ).getByText ("CAST" ) TournamentMatchPage.locators.unlockButtone2e/pages/tournament/tournament-match-page.ts:47
No JSDoc
page.getByRole ("button" , { name: "Unlock" }) TournamentMatchPage.locators.poolLabele2e/pages/tournament/tournament-match-page.ts:48
No JSDoc
page.getByText ("Pool" , { exact: true }) TournamentMatchPage.locators.roomPasse2e/pages/tournament/tournament-match-page.ts:37
No JSDoc
page.getByTestId ("room-pass" ) TournamentMatchPage.locators.stageBannere2e/pages/tournament/tournament-match-page.ts:42
No JSDoc
page.getByTestId ("stage-banner" ) TournamentBracketsPage.locators.liveBadgese2e/pages/tournament/tournament-brackets-page.ts:29
No JSDoc
page.getByTestId ("brackets-viewer" ).getByText ("LIVE" ) TournamentAdminPagee2e/pages/tournament/tournament-admin-page.ts:17
TournamentAdminStreamPagee2e/pages/tournament/tournament-admin-stream-page.ts:6
TournamentBracketsPage.matche2e/pages/tournament/tournament-brackets-page.ts:87
No JSDoc
match (matchId: number ) {
return this .page.locator (`[data-match-id="${matchId}"]` );
}TournamentAdminPage.dropOutTeame2e/pages/tournament/tournament-admin-page.ts:92
No JSDoc
async dropOutTeam (nth: number ) {
await this .teamRowActions (nth).click ();
await this .page.getByRole ("menuitem" , { name: "Drop out" }).click ();
await modalClickConfirmButton (this .page);
}TournamentMatchPage.gotoe2e/pages/tournament/tournament-match-page.ts:108
No JSDoc
async goto ({
tournamentId,
matchId,
}: {
tournamentId: number ;
matchId: number ;
}) {
await navigate ({
page: this .page,
url: tournamentMatchPage ({ tournamentId, matchId }),
});
}TournamentMatchPage.locators.finalBannere2e/pages/tournament/tournament-match-page.ts:43
No JSDoc
page.getByTestId ("match-final" ) TournamentBracketsPage.groupButtone2e/pages/tournament/tournament-brackets-page.ts:224
No JSDoc
groupButton (letter: string ) {
return this .page.getByTestId (`group-${letter}-button` );
}TournamentBracketsPage.exitLabele2e/pages/tournament/tournament-brackets-page.ts:229
exitLabel (text: string ) {
return this .locators.bracketsViewer
.getByTestId ("match-exits" )
.getByText (text, { exact: true })
.first ();
}TournamentBracketsPage.openGroupe2e/pages/tournament/tournament-brackets-page.ts:236
No JSDoc
async openGroup (letter: string ) {
await this .page.getByTestId (`group-${letter}-button` ).click ();
}TournamentBracketsPage.bracketTeamNamee2e/pages/tournament/tournament-brackets-page.ts:62
bracketTeamName (name: string ) {
return this .locators.bracketsViewer.getByText (name, { exact: true });
}TournamentBracketsPage.firstRoundTeamNamese2e/pages/tournament/tournament-brackets-page.ts:146
async firstRoundTeamNames (teamCount: number ): Promise <string []> {
const firstRound = this .locators.bracketsViewer
.locator ("[data-round-id]" )
.first ();
await expect (firstRound.getByTestId ("match-team-name" )).toHaveCount (
teamCount,
);
// a BYE renders no match rows at all, so its two slots are filled in here to
// keep the returned names aligned with the bracket's slots
return firstRound
.locator ('[data-testid="match-bye"], [data-testid="match-team-name"]' )
.evaluateAll ((elements) =>
elements.flatMap ((element) =>
// … FinalizeTournamentDialog.trophyTeamHeadinge2e/pages/tournament/tournament-brackets-page.ts:345
trophyTeamHeading (teamName: string ) {
return this .page.getByRole ("heading" , { name: teamName });
}CalendarNewEventPage.gotoNewTournamente2e/pages/calendar/calendar-new-event-page.ts:70
No JSDoc
async gotoNewTournament () {
await navigate ({ page: this .page, url: TOURNAMENT_NEW_PAGE });
}calendarNewBaseSchema.nameapp/features/calendar/calendar-new-schemas.ts:101
name: textField ({
label: "labels.name" ,
minLength: CALENDAR_EVENT .NAME_MIN_LENGTH ,
maxLength: CALENDAR_EVENT .NAME_MAX_LENGTH ,
}) CalendarNewEventPage.setFirstDatee2e/pages/calendar/calendar-new-event-page.ts:100
No JSDoc
async setFirstDate (date: Date ) {
await this .page
.getByLabel (/^Date *\*?$/)
.first ()
.fill (datetimeLocalValue (date));
}CalendarNewEventPage.goToStepe2e/pages/calendar/calendar-new-event-page.ts:147
async goToStep (step: "basics" | "teams" | "maps" | "format" | "prizes" ) {
await this .stepButton (step).click ();
}calendarNewBaseSchema.mapPickingStyleapp/features/calendar/calendar-new-schemas.ts:169
No JSDoc
mapPickingStyle: mapPickingStyleField CalendarNewEventPage.pickMapPoole2e/pages/calendar/calendar-new-event-page.ts:136
No JSDoc
async pickMapPool (maps: Array <{ stage: string ; mode: string }>) {
for (const { stage, mode } of maps) {
await this .page.getByRole ("tab" , { name: mode }).click ();
await this .page
.getByRole ("tabpanel" )
.getByRole ("checkbox" , { name: stage, exact: true })
.click ();
}
}CalendarNewEventPage.selectBrackete2e/pages/calendar/calendar-new-event-page.ts:157
async selectBracket (nth: number ) {
await this .goToStep ("format" );
await this .locators.bracketCards
.nth (nth)
.getByRole ("button" )
.first ()
.click ();
}CalendarNewEventPage.locators.groupCountSelecte2e/pages/calendar/calendar-new-event-page.ts:55
No JSDoc
page.getByLabel (/^Group count/) CalendarNewEventPage.roundNotPlayedHinte2e/pages/calendar/calendar-new-event-page.ts:255
roundNotPlayedHint (prerequisiteName: string ) {
return this .page
.getByText (`Can't be played unless ${prerequisiteName} is played` )
.first ();
}CalendarNewEventPage.savee2e/pages/calendar/calendar-new-event-page.ts:275
async save () {
if (await this .locators.lastStepButton.isVisible ()) {
await this .locators.lastStepButton.click ();
}
return submit (this .page);
}CalendarNewEventPagee2e/pages/calendar/calendar-new-event-page.ts:24
TournamentBracketsPage.selectBrackete2e/pages/tournament/tournament-brackets-page.ts:72
async selectBracket (name: string ) {
const tab = this .bracketTab (name);
await tab.click ();
await expect (tab).toHaveAttribute ("aria-selected" , "true" );
}TournamentBracketsPage.checkInBrackete2e/pages/tournament/tournament-brackets-page.ts:220
checkInBracket () {
return submit (this .page, "check-in-bracket-button" );
}TournamentAdminPage.checkTeamInToBrackete2e/pages/tournament/tournament-admin-page.ts:106
async checkTeamInToBracket (tournamentTeamId: number , bracketName: string ) {
await this .openTeamActions (tournamentTeamId);
await waitForPOSTResponse (this .page, async () => {
await this .teamActionsMenuItem (`Check in (${bracketName})` ).click ();
});
}FinalizeTournamentDialog.assignBadgesLatere2e/pages/tournament/tournament-brackets-page.ts:356
No JSDoc
async assignBadgesLater () {
await this .locators.assignBadgesLaterSwitch.click ();
}TournamentMatchPage.locators.adminTabe2e/pages/tournament/tournament-match-page.ts:41
No JSDoc
page.getByRole ("tab" , { name: TAB_LABELS .admin }) TournamentBracketsPage.groupStandingsTeamNamese2e/pages/tournament/tournament-brackets-page.ts:168
async groupStandingsTeamNames (groupCount: number ): Promise <string [][]> {
await expect (this .locators.rrStandingsTables).toHaveCount (groupCount);
const result: string [][] = [];
for (const table of await this .locators.rrStandingsTables.all ()) {
result.push (await table.locator ("td:first-child a" ).allTextContents ());
}
return result;
}expectGroupsSpreadAcrossBrackete2e/tournament-bracket-multi-stage.spec.ts:549
No JSDoc
function expectGroupsSpreadAcrossBracket (lineup: string [], groups: string [][]) {
for (let i = 0 ; i < lineup.length; i += groups.length) {
const section = lineup.slice (i, i + groups.length);
const sectionGroups = section.map ((teamName) =>
groupIndexOf (groups, teamName),
);
expect (new Set (sectionGroups).size).toBe (groups.length);
}
}expectSeedOrderRespectede2e/tournament-bracket-multi-stage.spec.ts:562
No JSDoc
function expectSeedOrderRespected (lineup: string [], groups: string [][]) {
const tiersPerGroup = lineup.length / groups.length;
const tierOf = (teamName: string ) =>
groups[groupIndexOf (groups, teamName)].indexOf (teamName) + 1 ;
for (let i = 0 ; i < lineup.length; i += 2 ) {
expect (tierOf (lineup[i]) + tierOf (lineup[i + 1 ])).toBe (tiersPerGroup + 1 );
}
// the factory plays higher seeds to a win, so Teams 1 & 2 top their groups
// with the best records overall
expect (lineup.slice (0 , lineup.length / 2 )).toContain ("Team 1" );
expect (lineup.slice (lineup.length / 2 )).toContain ("Team 2" );
}TournamentAdminPage.editEventInfoe2e/pages/tournament/tournament-admin-page.ts:54
No JSDoc
async editEventInfo () {
await this .locators.editEventInfoButton.click ();
return new CalendarNewEventPage (this .page);
}CalendarNewEventPage.deleteLastBrackete2e/pages/calendar/calendar-new-event-page.ts:217
No JSDoc
async deleteLastBracket () {
await this .selectBracket ((await this .locators.bracketCards.count ()) - 1 );
await this .locators.deleteBracketButton.click ();
}CalendarNewEventPage.pickLastPlacementse2e/pages/calendar/calendar-new-event-page.ts:223
async pickLastPlacements (placements: number []) {
await this .goToStep ("format" );
await this .locators.connectionPills.last ().click ();
await this .pickPlacements (placements);
}TopRightButtons.globalStatuse2e/pages/layout/top-right-buttons.ts:23
globalStatus (text: string ) {
return this .page.getByRole ("banner" ).getByRole ("link" , { name: text });
}TournamentBracketsPage.locators.bracketsViewere2e/pages/tournament/tournament-brackets-page.ts:23
No JSDoc
page.getByTestId ("brackets-viewer" ) TournamentMatchPage.locators.backToBracketButtone2e/pages/tournament/tournament-match-page.ts:38
No JSDoc
page.getByTestId ("back-to-bracket-button" ) TournamentAdminRegistrationPage.gotoEdite2e/pages/tournament/tournament-admin-registration-page.ts:47
No JSDoc
async gotoEdit (tournamentId: number , tournamentTeamId: number ) {
await navigate ({
page: this .page,
url: tournamentAdminRegistrationEditPage (tournamentId, tournamentTeamId),
});
}TournamentAdminRegistrationPage.addMembere2e/pages/tournament/tournament-admin-registration-page.ts:60
async addMember (userName: string ) {
await this .page.getByRole ("button" , { name: "Add" , exact: true }).click ();
await this .page.getByLabel ("Player" ).last ().click ();
await this .page.getByTestId ("user-search-input" ).fill (userName);
// clicking rather than pressing Enter: a result is in the DOM one commit
// before the effect that registers it and focuses it for Enter has run
await this .page
.getByRole ("listbox" )
.getByTestId ("user-search-item" )
.first ()
.click ();
}TournamentAdminRegistrationPage.savee2e/pages/tournament/tournament-admin-registration-page.ts:84
No JSDoc
save () {
return submit (this .page);
}TournamentTeamsPage.gotoe2e/pages/tournament/tournament-teams-page.ts:17
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentTeamsPage (tournamentId) });
}TournamentTeamsPage.memberNamede2e/pages/tournament/tournament-teams-page.ts:21
No JSDoc
memberNamed (name: string ) {
return this .locators.teamMemberNames.getByText (name);
}TopRightButtonse2e/pages/layout/top-right-buttons.ts:4
TournamentAdminRegistrationPagee2e/pages/tournament/tournament-admin-registration-page.ts:22
TournamentTeamsPagee2e/pages/tournament/tournament-teams-page.ts:5
No JSDoc
CalendarNewEventPage.makeAllBracketsStartingBracketse2e/pages/calendar/calendar-new-event-page.ts:235
async makeAllBracketsStartingBrackets () {
await this .goToStep ("format" );
const count = await this .locators.bracketCards.count ();
// last one first, each dropped right below the first card so the order is kept
// and the pointer stays clear of the viewport bottom where dnd-kit auto scrolls
for (let nth = count - 1 ; nth >= 1 ; nth--) {
await dragAndDrop (this .page, {
from : this .locators.bracketCards.nth (nth),
to: this .locators.bracketCards.first (),
at: ({ width, height }) => ({ x: width / 2 , y: height + 8 }),
});
}
}CalendarNewEventPage.setBracketFormate2e/pages/calendar/calendar-new-event-page.ts:229
No JSDoc
async setBracketFormat (nth: number , formatLabel: string ) {
await this .selectBracket (nth);
await this .locators.bracketFormatSelect.selectOption (formatLabel);
}TournamentSeedsPage.gotoe2e/pages/tournament/tournament-seeds-page.ts:23
No JSDoc
async goto (tournamentId: number ) {
await navigate ({
page: this .page,
url: `${tournamentAdminPage(tournamentId)}/seeds` ,
});
}TournamentSeedsPage.openStartingBracketsDialoge2e/pages/tournament/tournament-seeds-page.ts:52
No JSDoc
async openStartingBracketsDialog () {
await this .page.getByTestId ("set-starting-brackets" ).click ();
}TournamentSeedsPage.setStartingBrackete2e/pages/tournament/tournament-seeds-page.ts:56
No JSDoc
async setStartingBracket (nth: number , bracketName: string ) {
await this .page
.getByTestId ("starting-bracket-select" )
.nth (nth)
.selectOption (bracketName);
}TournamentSeedsPage.saveStartingBracketse2e/pages/tournament/tournament-seeds-page.ts:63
No JSDoc
saveStartingBrackets () {
return submit (this .page, "set-starting-brackets-submit-button" );
}TournamentSeedsPagee2e/pages/tournament/tournament-seeds-page.ts:11
No JSDoc
TournamentAdminPage.openTeamActionse2e/pages/tournament/tournament-admin-page.ts:142
No JSDoc
async openTeamActions (tournamentTeamId: number ) {
await this .teamRow (tournamentTeamId).getByLabel ("Actions" ).click ();
}TournamentAdminPage.teamActionsMenuIteme2e/pages/tournament/tournament-admin-page.ts:146
No JSDoc
teamActionsMenuItem (name: string | RegExp ) {
return this .page.getByRole ("menuitem" , { name });
}TournamentAdminPage.checkStartedTeamOute2e/pages/tournament/tournament-admin-page.ts:151
async checkStartedTeamOut (tournamentTeamId: number ) {
await this .openTeamActions (tournamentTeamId);
await this .teamActionsMenuItem (/^Check out/).click ();
await modalClickConfirmButton (this .page);
}TournamentAdminPage.teamRowe2e/pages/tournament/tournament-admin-page.ts:99
teamRow (tournamentTeamId: number ) {
return this .page.locator (
`[data-testid="team-row"][data-team-id="${tournamentTeamId}"]` ,
);
}TournamentBracketsPage.openPrepareMapsDialoge2e/pages/tournament/tournament-brackets-page.ts:197
async openPrepareMapsDialog () {
await this .locators.prepareMapsButton.click ();
return new BracketMapListDialog (this .page);
}BracketMapListDialog.confirme2e/pages/tournament/tournament-brackets-page.ts:298
No JSDoc
confirm () {
return submit (this .page, "confirm-finalize-bracket-button" );
}TournamentBracketsPage.locators.preparedMapsCheckIcone2e/pages/tournament/tournament-brackets-page.ts:41
No JSDoc
page.getByTestId ("prepared-maps-check-icon" ) TournamentBracketsPage.openFinalizeDialoge2e/pages/tournament/tournament-brackets-page.ts:191
async openFinalizeDialog () {
await this .page.getByTestId ("finalize-bracket-button" ).click ();
return new BracketMapListDialog (this .page);
}BracketMapListDialog.locators.preparedByTexte2e/pages/tournament/tournament-brackets-page.ts:267
No JSDoc
page.getByText (/^Prepared by/) TournamentBracketsPage.locators.matchese2e/pages/tournament/tournament-brackets-page.ts:27
No JSDoc
page.locator ("[data-match-id]" ) BracketMapListDialoge2e/pages/tournament/tournament-brackets-page.ts:251
CalendarNewEventPage.pasteMapPoole2e/pages/calendar/calendar-new-event-page.ts:267
async pasteMapPool (mapPool: MapPool ) {
await this .locators.pasteMapPoolLinkButton.click ();
await this .locators.mapPoolLinkInput.fill (
`https://sendou.ink${mapsPageWithMapPool(mapPool)}` ,
);
}CalendarNewEventPage.renameBrackete2e/pages/calendar/calendar-new-event-page.ts:298
No JSDoc
async renameBracket (nth: number , name: string ) {
await this .selectBracket (nth);
await this .locators.bracketNameInput.fill (name);
}CalendarNewEventPage.addFollowUpBrackete2e/pages/calendar/calendar-new-event-page.ts:284
async addFollowUpBracket ({
name,
format,
placements,
}: {
name: string ;
format: string ;
placements: number [];
}) {
await this .addBracket ({ name, format });
const lastNth = (await this .locators.bracketCards.count ()) - 1 ;
await this .connect (0 , lastNth, placements);
}CalendarNewEventPage.connecte2e/pages/calendar/calendar-new-event-page.ts:174
async connect (fromNth: number , toNth: number , placements?: number []) {
await this .goToStep ("format" );
await this .locators.bracketCards
.nth (fromNth)
.getByRole ("button" , { name: /^Send teams from / })
.click ();
await this .locators.bracketCards
.nth (toNth)
.getByRole ("button" )
.first ()
.click ();
if (placements !== undefined ) {
await this .pickPlacements (placements);
// … TournamentBracketsPage.locators.teamsPendingFromSourcesTexte2e/pages/tournament/tournament-brackets-page.ts:35
No JSDoc
page.getByText (
"Teams pending from the source brackets" ,
) TournamentBracketsPage.locators.finalizeBracketButtone2e/pages/tournament/tournament-brackets-page.ts:34
No JSDoc
page.getByTestId ("finalize-bracket-button" ) TournamentMatchPage.reportResultForTeame2e/pages/tournament/tournament-match-page.ts:184
async reportResultForTeam ({
teamName,
mapsToReport,
}: {
teamName: string ;
mapsToReport: number ;
}) {
await expect (this .winnerRadio (1 )).toBeVisible ();
const sideOneIsTeam =
(await this .winnerRadio (1 ).getByText (teamName, { exact: true }).count ()) >
0 ;
return this .reportResult ({ mapsToReport, winner: sideOneIsTeam ? 1 : 2 });
}BracketMapListDialog.setExpectedTeamse2e/pages/tournament/tournament-brackets-page.ts:286
No JSDoc
async setExpectedTeams (count: number ) {
await this .locators.expectedTeamsSelect.selectOption (String (count));
}BracketMapListDialog.unlinkFinalsThirdPlaceMatche2e/pages/tournament/tournament-brackets-page.ts:294
No JSDoc
async unlinkFinalsThirdPlaceMatch () {
await this .locators.unlinkFinalsButton.click ();
}BracketMapListDialog.increaseMapCounte2e/pages/tournament/tournament-brackets-page.ts:290
No JSDoc
async increaseMapCount (position: "first" | "last" ) {
await this .locators.increaseMapCountButtons[position]().click ();
}BracketMapListDialog.locators.linkFinalsButtone2e/pages/tournament/tournament-brackets-page.ts:265
No JSDoc
page.getByTestId ("link-finals-3rd-place-match-button" ) BracketMapListDialog.togglePickBane2e/pages/tournament/tournament-brackets-page.ts:278
async togglePickBan () {
await this .locators.pickBanToggles.first ().click ();
}BracketMapListDialog.setPickBane2e/pages/tournament/tournament-brackets-page.ts:272
No JSDoc
async setPickBan (value: string ) {
await this .locators.pickBanSelect.selectOption (value);
}TournamentMatchPage.pickBane2e/pages/tournament/tournament-match-page.ts:203
pickBan (option: "first" | "last" = "first" ) {
return waitForPOSTResponse (this .page, async () => {
await this .page.getByTestId ("pick-ban-button" )[option]().click ();
await this .page.getByTestId ("pick-ban-submit-button" ).click ();
});
}TournamentMatchPage.locators.pickBanModeGroupse2e/pages/tournament/tournament-match-page.ts:57
No JSDoc
page.getByTestId ("pick-ban-mode-group" ) TournamentMatchPage.locators.selectWinnerTexte2e/pages/tournament/tournament-match-page.ts:51
No JSDoc
page.getByText ("Select the winner" ) TournamentMatchPage.locators.counterpickTexte2e/pages/tournament/tournament-match-page.ts:52
No JSDoc
page.getByText ("Counterpick" , { exact: true }) BracketMapListDialog.locators.beforeSetTexte2e/pages/tournament/tournament-brackets-page.ts:266
No JSDoc
page.getByText ("Before set" ) BracketMapListDialog.confirmWithCustomFlowe2e/pages/tournament/tournament-brackets-page.ts:304
confirmWithCustomFlow (customFlow: unknown ) {
return waitForPOSTResponse (this .page, async () => {
await this .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);
// … TournamentMatchPage.locators.lastBanTexte2e/pages/tournament/tournament-match-page.ts:54
No JSDoc
page.getByText (/Ban a map \(2 \/2 \)/) TournamentMatchPage.locators.banAMapTexte2e/pages/tournament/tournament-match-page.ts:53
No JSDoc
page.getByText (/Ban a map/) TournamentMatchPage.locators.pickAMapTexte2e/pages/tournament/tournament-match-page.ts:55
No JSDoc
page.getByText (/Pick a map/) TournamentPage.gotoe2e/pages/tournament/tournament-page.ts:21
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentPage (tournamentId) });
}TournamentPagee2e/pages/tournament/tournament-page.ts:7
No JSDoc
CalendarNewEventPage.clearMapPoole2e/pages/calendar/calendar-new-event-page.ts:261
No JSDoc
async clearMapPool () {
await this .locators.clearMapPoolButton.click ();
await this .page.getByTestId ("confirm-button" ).click ();
}TournamentMatchPage.modeProgresse2e/pages/tournament/tournament-match-page.ts:138
No JSDoc
modeProgress (mode: string ) {
return this .page.getByTestId (`mode-progress-${mode}` );
}TournamentMatchPage.mapCountTexte2e/pages/tournament/tournament-match-page.ts:142
No JSDoc
mapCountText (count: number ) {
return this .page.getByText (`×${count}` );
}TournamentMatchPage.winnerRadioe2e/pages/tournament/tournament-match-page.ts:134
No JSDoc
winnerRadio (side: Side ) {
return this .page.getByTestId (`winner-radio-${side}` );
}MatchProfilePage.gotoe2e/pages/settings/match-profile-page.ts:25
No JSDoc
async goto () {
await navigate ({ page: this .page, url: MATCH_PROFILE_PAGE });
}updateMatchProfileSchema.noScreenapp/features/settings/match-profile-schemas.ts:70
noScreen: toggle ({
label: "labels.noScreen" ,
bottomText: "bottomTexts.noScreen" ,
}) MatchProfilePage.savee2e/pages/settings/match-profile-page.ts:29
No JSDoc
async save () {
await submit (this .page);
}TournamentMatchPage.locators.screenBannede2e/pages/tournament/tournament-match-page.ts:45
No JSDoc
page.getByTestId ("screen-banned" ) TournamentMatchPage.locators.screenAllowede2e/pages/tournament/tournament-match-page.ts:46
No JSDoc
page.getByTestId ("screen-allowed" ) MatchProfilePagee2e/pages/settings/match-profile-page.ts:8
No JSDoc
BracketMapListDialog.setCountTypee2e/pages/tournament/tournament-brackets-page.ts:282
No JSDoc
async setCountType (value: string ) {
await this .locators.countTypeSelect.selectOption (value);
}TournamentMatchPage.playAllTexte2e/pages/tournament/tournament-match-page.ts:146
No JSDoc
playAllText (count: number ) {
return this .page.getByText (`Play all ${count}` );
}TournamentBracketsPage.reloade2e/pages/tournament/tournament-brackets-page.ts:52
No JSDoc
async reload () {
await this .page.reload ();
await expectIsHydrated (this .page);
}ChatSidebar.opene2e/pages/layout/chat-sidebar.ts:40
No JSDoc
async open () {
await this .locators.toggleButton.click ();
}TournamentBracketsPage.lastMatchIde2e/pages/tournament/tournament-brackets-page.ts:127
async lastMatchId () {
return Number (
await this .locators.matches.last ().getAttribute ("data-match-id" ),
);
}TournamentBracketsPage.scrollMatchNearTopLefte2e/pages/tournament/tournament-brackets-page.ts:95
async scrollMatchNearTopLeft (matchId: number ) {
await this .match (matchId).evaluate ((element) => {
const rect = element.getBoundingClientRect ();
const scroller = element.closest ("[data-bracket-scroller]" );
if (!scroller) {
window.scrollTo (0 , rect.top + window.scrollY - 150 );
return ;
}
const scrollerRect = scroller.getBoundingClientRect ();
scroller.scrollTop += rect.top - scrollerRect.top - 50 ;
scroller.scrollLeft += rect.left - scrollerRect.left - 50 ;
});
}TournamentBracketsPage.matchViewportPositione2e/pages/tournament/tournament-brackets-page.ts:119
matchViewportPosition (matchId: number ) {
return this .match (matchId).evaluate ((element) => {
const { top, left } = element.getBoundingClientRect ();
return { top, left };
});
}TournamentMatchPage.browserBacke2e/pages/tournament/tournament-match-page.ts:323
async browserBack () {
await this .page.goBack ();
await expect (this .page.getByTestId ("brackets-viewer" )).toBeVisible ();
return new TournamentBracketsPage (this .page);
}TournamentBracketsPage.scrollToStarte2e/pages/tournament/tournament-brackets-page.ts:111
async scrollToStart () {
await this .page.evaluate (() => {
window.scrollTo (0 , 0 );
document.querySelector ("[data-bracket-scroller]" )?.scrollTo (0 , 0 );
});
}ChatSidebare2e/pages/layout/chat-sidebar.ts:8
TournamentBracketsPage.locators.startRoundButtone2e/pages/tournament/tournament-brackets-page.ts:38
No JSDoc
page.getByTestId ("start-round-button" ) TournamentBracketsPage.startRounde2e/pages/tournament/tournament-brackets-page.ts:240
No JSDoc
startRound () {
return submit (this .page, "start-round-button" );
}TournamentBracketsPage.resetRounde2e/pages/tournament/tournament-brackets-page.ts:244
No JSDoc
async resetRound () {
await this .page.getByTestId ("reset-round-button" ).click ();
await modalClickConfirmButton (this .page);
}TournamentBracketsPage.locators.byeTeame2e/pages/tournament/tournament-brackets-page.ts:39
No JSDoc
page.getByTestId ("bye-team" ) TournamentBracketsPage.roundLabele2e/pages/tournament/tournament-brackets-page.ts:78
No JSDoc
roundLabel (roundNumber: number ) {
return this .page.getByText (`Round ${roundNumber}` , { exact: true });
}TournamentMatchPage.locators.activeRosterNeededTexte2e/pages/tournament/tournament-match-page.ts:39
No JSDoc
page.getByTestId ("active-roster-needed-text" ) TournamentMatchPage.playerCheckboxe2e/pages/tournament/tournament-match-page.ts:241
No JSDoc
playerCheckbox (side: RosterSide , nth: number ) {
return this .page.getByTestId (`player-checkbox-${side}-${nth}` );
}TournamentMatchPage.saveActiveRostere2e/pages/tournament/tournament-match-page.ts:245
No JSDoc
saveActiveRoster (side: RosterSide ) {
return submit (this .page, `save-active-roster-button-${side}` );
}TournamentMatchPage.editActiveRosterButtone2e/pages/tournament/tournament-match-page.ts:249
No JSDoc
editActiveRosterButton (side: RosterSide ) {
return this .page.getByTestId (`edit-active-roster-button-${side}` );
}TournamentMatchPage.setActiveRostere2e/pages/tournament/tournament-match-page.ts:255
async setActiveRoster (side: RosterSide , memberIndexes: number []) {
await expect (this .locators.rostersPanel).toBeVisible ();
const editButton = this .editActiveRosterButton (side);
const checkboxes = this .page.locator (
`[data-testid^="player-checkbox-${side}-"]` ,
);
// A roster that was never set renders in editing mode already, one that was
// has to be put back into it.
await expect (editButton.or (checkboxes.first ())).toBeVisible ();
if (await editButton.isVisible ()) {
await editButton.click ();
await expect (checkboxes.first ()).toBeVisible ();
}
// … TournamentMatchPage.reportWeapone2e/pages/tournament/tournament-match-page.ts:233
No JSDoc
async reportWeapon (name: string ) {
await this .expandWeaponReporter ();
await selectWeapon ({ page: this .page, name });
await waitForPOSTResponse (this .page, async () => {
await this .locators.submitWeaponButton.click ();
});
}TournamentMatchPage.locators.undoWeaponButtone2e/pages/tournament/tournament-match-page.ts:65
No JSDoc
page.getByRole ("button" , { name: "Undo weapon" }) TournamentMatchPage.expandWeaponReportere2e/pages/tournament/tournament-match-page.ts:224
async expandWeaponReporter () {
await expect (this .locators.actionPanel).toBeVisible ();
if (await this .locators.reportWeaponsButton.isVisible ()) {
await waitForPOSTResponse (this .page, async () => {
await this .locators.reportWeaponsButton.click ();
});
}
}TournamentMatchPage.weaponPrompte2e/pages/tournament/tournament-match-page.ts:217
weaponPrompt (mapNumber: number ) {
return this .page.getByText (`Your weapon #${mapNumber}` );
}TournamentTeamPage.gotoe2e/pages/tournament/tournament-team-page.ts:17
No JSDoc
async goto (tournamentId: number , tournamentTeamId: number ) {
await navigate ({
page: this .page,
url: tournamentTeamPage ({ tournamentId, tournamentTeamId }),
});
}TournamentTeamPage.copySubInviteLinke2e/pages/tournament/tournament-team-page.ts:25
async copySubInviteLink (): Promise <string > {
await this .page.getByTestId ("add-sub-button" ).click ();
await this .page.getByTestId ("copy-invite-link-button" ).click ();
return this .page.evaluate ("navigator.clipboard.readText()" );
}TournamentJoinPage.gotoViaInviteLinke2e/pages/tournament/tournament-join-page.ts:14
async gotoViaInviteLink (inviteLink: string ) {
const url = new URL (inviteLink);
await navigate ({ page: this .page, url: url.pathname + url.search });
}TournamentJoinPage.joine2e/pages/tournament/tournament-join-page.ts:19
No JSDoc
async join () {
await submit (this .page);
return new TournamentBracketsPage (this .page);
}TournamentTeamPagee2e/pages/tournament/tournament-team-page.ts:6
No JSDoc
TournamentJoinPagee2e/pages/tournament/tournament-join-page.ts:6
TournamentMatchPage.locators.matchTimere2e/pages/tournament/tournament-match-page.ts:44
No JSDoc
page.getByTestId ("match-timer" ) TournamentBracketsPage.matchTimere2e/pages/tournament/tournament-brackets-page.ts:139
matchTimer (matchId: number ) {
return this .match (matchId).locator (".." ).getByTestId ("bracket-match-timer" );
}TournamentMatchPage.endSetWithRandomWinnere2e/pages/tournament/tournament-match-page.ts:310
No JSDoc
async endSetWithRandomWinner () {
await this .page.getByRole ("button" , { name: "End set" }).click ();
await this .page.getByRole ("radio" , { name: /Random / }).check ();
await submit (this .page, "end-set-button" );
}createInProgressMatche2e/helpers/tournament.ts:279
export async function createInProgressMatch (
factories: Factories ,
{
name,
friendId,
teammateId,
opponentId,
tier,
}: {
name: string ;
friendId: number ;
teammateId?: number ;
opponentId?: number ;
tier?: TournamentTierNumber ;
// … TournamentBracketsPage.matchScorese2e/pages/tournament/tournament-brackets-page.ts:134
matchScores (matchId: number ) {
return this .match (matchId).getByTestId ("match-score" );
}openSecondUsere2e/helpers/chat.ts:7
export async function openSecondUser (
browser: Browser ,
baseURL: string ,
userId = ADMIN_ID ,
) {
const context = await browser.newContext ({ baseURL });
// the `context` fixture blocks these for the test's own context only
await context.route (
/^https:\/\/fonts\.(googleapis|gstatic)\.com\//,
(route) => route.abort (),
);
const page = await context.newPage ();
await impersonate (page, userId);
// … deviceAsleepe2e/tournament-bracket.spec.ts:248
async function deviceAsleep (page: Page , duration: string ) {
await page.clock.install ();
await page.clock.fastForward (duration);
await page.clock.resume ();
}