Tournament admin team management edits a registration, checks a team in and out, unregisters it and records it in the audit log Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/audit /to/:id/admin/registration/:tid? /to/:id/teams/:tid
Tournament admin registration page Tournament admin page Tournament team page Tournament admin page Tournament admin audit page Arrange
an established organization's tournament, so its captain's tournament name can be set as part of the edit
Create tournament established organization true → tournament
const tournament = await createTournament (factories, {
establishedOrganization: true ,
});Create 4 × user → roster
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );Create tournament team tournament id tournament.id team pickUpTeam ("Original Team" ) member user ids roster.map ((user) => user.id) → team
const team = await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Original Team" ),
memberUserIds: roster.map ((user) => user.id),
});Tournament admin registration page Act
Go to edit tournament.id, team.id
await registration.gotoEdit (tournament.id, team.id);Assert
edit heading is visible
await expect (registration.locators.editHeading).toBeVisible ();Act
Fill Team name with “Renamed Team”
await registration.form.fill ("pickUpName" , "Renamed Team" );Set tournament name 0 , “Riko”
await registration.setTournamentName (0 , "Riko" );Save
await registration.save ();Tournament admin page Assert
search input is visible
await expect (admin.locators.searchInput).toBeVisible ();team name “Renamed Team” is visible
await expect (admin.teamName ("Renamed Team" )).toBeVisible ();Tournament team page Act
Open the tournament team page tournament.id, team.id
await teamPage.goto (tournament.id, team.id);Assert
first member names reads “Riko”
await expect (teamPage.locators.memberNames.first ()).toHaveText ("Riko" );Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Check team in 0
await admin.checkTeamIn (0 );Check team out 0
await admin.checkTeamOut (0 );Unregister team 0
await admin.unregisterTeam (0 );Assert
unregister dialog heading is visible
await expect (admin.locators.unregisterDialogHeading).toBeVisible ();Act
Confirm unregister
await admin.confirmUnregister ();Tournament admin audit page Act
Open the tournament admin audit page tournament.id
await audit.goto (tournament.id);Assert
event cell “Team checked in” is visible
await expect (audit.eventCell ("Team checked in" )).toBeVisible ();event cell “Team checked out” is visible
await expect (audit.eventCell ("Team checked out" )).toBeVisible ();event cell “Team unregistered” is visible
await expect (audit.eventCell ("Team unregistered" )).toBeVisible ();event cell “Tournament name changed” is visible
await expect (audit.eventCell ("Tournament name changed" )).toBeVisible ();Source test ("edits a registration, checks a team in and out, unregisters it and records it in the audit log" , async ({
page,
factories,
}) => {
// an established organization's tournament, so its captain's tournament name
// can be set as part of the edit
const tournament = await createTournament (factories, {
establishedOrganization: true ,
});
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );
const team = await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Original Team" ),
memberUserIds: roster.map ((user) => user.id),
});
await impersonate (page, NZAP_TEST_ID );
const registration = new TournamentAdminRegistrationPage (page);
await registration.gotoEdit (tournament.id, team.id);
await expect (registration.locators.editHeading).toBeVisible ();
await registration.form.fill ("pickUpName" , "Renamed Team" );
await registration.setTournamentName (0 , "Riko" );
await registration.save ();
// back on the team list, the rename is reflected
const admin = new TournamentAdminPage (page);
await expect (admin.locators.searchInput).toBeVisible ();
await expect (admin.teamName ("Renamed Team" )).toBeVisible ();
// the captain is shown under the name the organizer gave them
const teamPage = new TournamentTeamPage (page);
await teamPage.goto (tournament.id, team.id);
await expect (teamPage.locators.memberNames.first ()).toHaveText ("Riko" );
await admin.goto (tournament.id);
await admin.checkTeamIn (0 );
await admin.checkTeamOut (0 );
await admin.unregisterTeam (0 );
await expect (admin.locators.unregisterDialogHeading).toBeVisible ();
await admin.confirmUnregister ();
const audit = new TournamentAdminAuditPage (page);
await audit.goto (tournament.id);
await expect (audit.eventCell ("Team checked in" )).toBeVisible ();
await expect (audit.eventCell ("Team checked out" )).toBeVisible ();
await expect (audit.eventCell ("Team unregistered" )).toBeVisible ();
await expect (audit.eventCell ("Tournament name changed" )).toBeVisible ();
})adds a new team and records it in the audit log Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/audit /to/:id/admin/registration/:tid?
Tournament admin registration page Tournament admin page Tournament admin audit page Arrange
Create tournament → tournament
const tournament = await createTournament (factories);Create user discord name “Rostered Riko” → player
const player = await factories.UserFactory .create ({
discordName: "Rostered Riko" ,
});Tournament admin registration page Act
Go to new tournament.id
await registration.gotoNew (tournament.id);Assert
add heading is visible
await expect (registration.locators.addHeading).toBeVisible ();Act
Fill Team name with “Panda Squad”
await registration.form.fill ("pickUpName" , "Panda Squad" );Select player “Rostered Riko”
await registration.selectPlayer ("Rostered Riko" );Select captain player.id
await registration.selectCaptain (player.id);Save
await registration.save ();Tournament admin page Assert
team name “Panda Squad” is visible
await expect (admin.teamName ("Panda Squad" )).toBeVisible ();Tournament admin audit page Act
Open the tournament admin audit page tournament.id
await audit.goto (tournament.id);Assert
event cell “Team registered” is visible
await expect (audit.eventCell ("Team registered" )).toBeVisible ();Source test ("adds a new team and records it in the audit log" , async ({
page,
factories,
}) => {
const tournament = await createTournament (factories);
const player = await factories.UserFactory .create ({
discordName: "Rostered Riko" ,
});
await impersonate (page, NZAP_TEST_ID );
const registration = new TournamentAdminRegistrationPage (page);
await registration.gotoNew (tournament.id);
await expect (registration.locators.addHeading).toBeVisible ();
await registration.form.fill ("pickUpName" , "Panda Squad" );
await registration.selectPlayer ("Rostered Riko" );
await registration.selectCaptain (player.id);
await registration.save ();
const admin = new TournamentAdminPage (page);
await expect (admin.teamName ("Panda Squad" )).toBeVisible ();
const audit = new TournamentAdminAuditPage (page);
await audit.goto (tournament.id);
await expect (audit.eventCell ("Team registered" )).toBeVisible ();
})imports a roster from another tournament and registers it Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/registration/:tid? /to/:id/teams
Tournament admin registration page Tournament admin page Tournament teams page Arrange
Create tournament → tournament
const tournament = await createTournament (factories);the tournament search of the import dialog only finds past tournaments
Create tournament name “Paddling Pool 253” author N-ZAP start times dateToDatabaseTimestamp (subDays (new Date (), 2 )) → pastTournament
const pastTournament = await factories.TournamentFactory .create ({
name: "Paddling Pool 253" ,
authorId: NZAP_TEST_ID ,
startTimes: [dateToDatabaseTimestamp (subDays (new Date (), 2 ))],
});importedRosterNames = Array .from ( { length: ROSTER_SIZE }, (_, i) => `Imported Player ${i + 1}` , )
const importedRosterNames = Array .from (
{ length: ROSTER_SIZE },
(_, i) => `Imported Player ${i + 1}` ,
);Create 4 × user (i) => ({ discordName: importedRosterNames[i] }) → importedRoster
const importedRoster = await factories.UserFactory .createMany (
ROSTER_SIZE ,
(i) => ({ discordName: importedRosterNames[i] }),
);Create tournament team tournament id pastTournament.id team pickUpTeam ("Imported Legends" ) member user ids importedRoster.map ((user) => user.id)
await factories.TournamentTeamFactory .create ({
tournamentId: pastTournament.id,
team: pickUpTeam ("Imported Legends" ),
memberUserIds: importedRoster.map ((user) => user.id),
});Tournament admin registration page Act
Go to new tournament.id
await registration.gotoNew (tournament.id);Assert
add heading is visible
await expect (registration.locators.addHeading).toBeVisible ();Act
Open import dialog
await registration.openImportDialog ();Assert
import dialog heading is visible
await expect (registration.locators.importDialogHeading).toBeVisible ();Act
Import first team from “Paddling Pool”
await registration.importFirstTeamFrom ("Paddling Pool" );Assert
the dialog closes and the imported roster prefills the form
import dialog heading count is 0
await expect (registration.locators.importDialogHeading).toHaveCount (0 );team name input has value “Imported Legends”
await expect (registration.locators.teamNameInput).toHaveValue (
"Imported Legends" ,
);For each name of importedRosterNames
for (const name of importedRosterNames) {
await expect (registration.memberWithName (name)).toBeVisible ();
}member with name name is visible
await expect (registration.memberWithName (name)).toBeVisible ();Act
Save
await registration.save ();Tournament admin page Assert
team name “Imported Legends” is visible
await expect (admin.teamName ("Imported Legends" )).toBeVisible ();Tournament teams page Act
Open the tournament teams page tournament.id
await teamsPage.goto (tournament.id);Assert
team named “Imported Legends” is visible
await expect (teamsPage.teamNamed ("Imported Legends" )).toBeVisible ();For each name of importedRosterNames
for (const name of importedRosterNames) {
await expect (teamsPage.memberNamed (name)).toBeVisible ();
}member named name is visible
await expect (teamsPage.memberNamed (name)).toBeVisible ();Source test ("imports a roster from another tournament and registers it" , async ({
page,
factories,
}) => {
const tournament = await createTournament (factories);
// the tournament search of the import dialog only finds past tournaments
const pastTournament = await factories.TournamentFactory .create ({
name: "Paddling Pool 253" ,
authorId: NZAP_TEST_ID ,
startTimes: [dateToDatabaseTimestamp (subDays (new Date (), 2 ))],
});
const importedRosterNames = Array .from (
{ length: ROSTER_SIZE },
(_, i) => `Imported Player ${i + 1}` ,
);
const importedRoster = await factories.UserFactory .createMany (
ROSTER_SIZE ,
(i) => ({ discordName: importedRosterNames[i] }),
);
await factories.TournamentTeamFactory .create ({
tournamentId: pastTournament.id,
team: pickUpTeam ("Imported Legends" ),
memberUserIds: importedRoster.map ((user) => user.id),
});
await impersonate (page, NZAP_TEST_ID );
const registration = new TournamentAdminRegistrationPage (page);
await registration.gotoNew (tournament.id);
await expect (registration.locators.addHeading).toBeVisible ();
await registration.openImportDialog ();
await expect (registration.locators.importDialogHeading).toBeVisible ();
await registration.importFirstTeamFrom ("Paddling Pool" );
// the dialog closes and the imported roster prefills the form
await expect (registration.locators.importDialogHeading).toHaveCount (0 );
await expect (registration.locators.teamNameInput).toHaveValue (
"Imported Legends" ,
);
for (const name of importedRosterNames) {
await expect (registration.memberWithName (name)).toBeVisible ();
}
await registration.save ();
const admin = new TournamentAdminPage (page);
await expect (admin.teamName ("Imported Legends" )).toBeVisible ();
// the imported team registered with its full roster
const teamsPage = new TournamentTeamsPage (page);
await teamsPage.goto (tournament.id);
await expect (teamsPage.teamNamed ("Imported Legends" )).toBeVisible ();
for (const name of importedRosterNames) {
await expect (teamsPage.memberNamed (name)).toBeVisible ();
}
})sets the counterpick map pool of a team that has none, rejects an incomplete edit to it and then edits it Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/registration/:tid? /to/:id/teams/:tid
Tournament admin registration page Tournament admin page Tournament team page Tournament admin registration page Tournament team page Tournament admin registration page Tournament admin page Tournament team page Arrange
teams pre-pick their maps, so the registration form has a map pool to edit
Create tournament map picking style “AUTO” → tournament
const tournament = await createTournament (factories, {
mapPickingStyle: "AUTO" ,
});Create 4 × user → roster
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );Create tournament team tournament id tournament.id team pickUpTeam ("Poolless Pandas" ) member user ids roster.map ((user) => user.id) → team
const team = await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Poolless Pandas" ),
memberUserIds: roster.map ((user) => user.id),
});Tournament admin registration page Act
Go to edit tournament.id, team.id
await registration.gotoEdit (tournament.id, team.id);Assert
edit heading is visible
await expect (registration.locators.editHeading).toBeVisible ();Act
Pick counterpick maps → picked
const picked = await registration.pickCounterpickMaps ();Save
await registration.save ();Tournament admin page Assert
search input is visible
await expect (admin.locators.searchInput).toBeVisible ();Tournament team page Act
Open the tournament team page tournament.id, team.id
await teamPage.goto (tournament.id, team.id);Assert
For each { mode, stageId } of picked
for (const { mode, stageId } of picked) {
await expect (teamPage.mapPoolStage (mode, stageId)).toBeVisible ();
}map pool stage mode, stageId is visible
await expect (teamPage.mapPoolStage (mode, stageId)).toBeVisible ();Act
a pool left incomplete is rejected instead of overwriting the saved one
[replaced] = picked
const [replaced] = picked;Tournament admin registration page Act
Go to edit tournament.id, team.id
await registration.gotoEdit (tournament.id, team.id);Assert
picked counterpick map replaced.mode, replaced.stageId is visible
await expect (
registration.pickedCounterpickMap (replaced.mode, replaced.stageId),
).toBeVisible ();Act
Unpick counterpick map replaced.mode, replaced.stageId
await registration.unpickCounterpickMap (replaced.mode, replaced.stageId);Save
await registration.save ();Assert
invalid map pool error is visible
await expect (registration.locators.invalidMapPoolError).toBeVisible ();Tournament team page Act
Open the tournament team page tournament.id, team.id
await teamPage.goto (tournament.id, team.id);Assert
map pool stage replaced.mode, replaced.stageId is visible
await expect (
teamPage.mapPoolStage (replaced.mode, replaced.stageId),
).toBeVisible ();Tournament admin registration page Act
swapping one of the picked maps for another is reflected on the team page
Go to edit tournament.id, team.id
await registration.gotoEdit (tournament.id, team.id);Unpick counterpick map replaced.mode, replaced.stageId
await registration.unpickCounterpickMap (replaced.mode, replaced.stageId);Pick counterpick map replaced.mode, 17
await registration.pickCounterpickMap (replaced.mode, REPLACEMENT_STAGE_ID );Save
await registration.save ();Tournament admin page Assert
search input is visible
await expect (admin.locators.searchInput).toBeVisible ();Tournament team page Act
Open the tournament team page tournament.id, team.id
await teamPage.goto (tournament.id, team.id);Assert
map pool stage replaced.mode, 17 is visible
await expect (
teamPage.mapPoolStage (replaced.mode, REPLACEMENT_STAGE_ID ),
).toBeVisible ();map pool stage replaced.mode, replaced.stageId count is 0
await expect (
teamPage.mapPoolStage (replaced.mode, replaced.stageId),
).toHaveCount (0 );Source test ("sets the counterpick map pool of a team that has none, rejects an incomplete edit to it and then edits it" , async ({
page,
factories,
}) => {
// teams pre-pick their maps, so the registration form has a map pool to edit
const tournament = await createTournament (factories, {
mapPickingStyle: "AUTO" ,
});
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );
const team = await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Poolless Pandas" ),
memberUserIds: roster.map ((user) => user.id),
});
await impersonate (page, NZAP_TEST_ID );
const registration = new TournamentAdminRegistrationPage (page);
await registration.gotoEdit (tournament.id, team.id);
await expect (registration.locators.editHeading).toBeVisible ();
const picked = await registration.pickCounterpickMaps ();
await registration.save ();
const admin = new TournamentAdminPage (page);
await expect (admin.locators.searchInput).toBeVisible ();
const teamPage = new TournamentTeamPage (page);
await teamPage.goto (tournament.id, team.id);
for (const { mode, stageId } of picked) {
await expect (teamPage.mapPoolStage (mode, stageId)).toBeVisible ();
}
// a pool left incomplete is rejected instead of overwriting the saved one
const [replaced] = picked;
await registration.gotoEdit (tournament.id, team.id);
await expect (
registration.pickedCounterpickMap (replaced.mode, replaced.stageId),
).toBeVisible ();
await registration.unpickCounterpickMap (replaced.mode, replaced.stageId);
await registration.save ();
await expect (registration.locators.invalidMapPoolError).toBeVisible ();
await teamPage.goto (tournament.id, team.id);
await expect (
teamPage.mapPoolStage (replaced.mode, replaced.stageId),
).toBeVisible ();
// swapping one of the picked maps for another is reflected on the team page
await registration.gotoEdit (tournament.id, team.id);
await registration.unpickCounterpickMap (replaced.mode, replaced.stageId);
await registration.pickCounterpickMap (replaced.mode, REPLACEMENT_STAGE_ID );
await registration.save ();
await expect (admin.locators.searchInput).toBeVisible ();
await teamPage.goto (tournament.id, team.id);
await expect (
teamPage.mapPoolStage (replaced.mode, REPLACEMENT_STAGE_ID ),
).toBeVisible ();
await expect (
teamPage.mapPoolStage (replaced.mode, replaced.stageId),
).toHaveCount (0 );
})Renders/to/:id /to/:id/admin /to/:id/admin
Tournament admin page Arrange
Create tournament → tournament
const tournament = await createTournament (factories);Create 4 × user → roster
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );Create tournament team tournament id tournament.id team pickUpTeam ("Alpha Squad" ) member user ids roster.map ((user) => user.id)
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Alpha Squad" ),
memberUserIds: roster.map ((user) => user.id),
});Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open export dialog
await admin.openExportDialog ();Assert
export dialog heading is visible
await expect (admin.locators.exportDialogHeading).toBeVisible ();Act
Download export → download
const download = await admin.downloadExport ();Assert
download.suggestedFilename () is “participants.txt”
expect (download.suggestedFilename ()).toBe ("participants.txt" );Act
Read file download.path (), “utf-8” → content
const content = await fs.readFile (await download.path (), "utf-8" );Assert
content contains “Alpha Squad”
expect (content).toContain ("Alpha Squad" );Source test ("exports the team list" , async ({ page, factories }) => {
const tournament = await createTournament (factories);
const roster = await factories.UserFactory .createMany (ROSTER_SIZE );
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Alpha Squad" ),
memberUserIds: roster.map ((user) => user.id),
});
await impersonate (page, NZAP_TEST_ID );
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.openExportDialog ();
await expect (admin.locators.exportDialogHeading).toBeVisible ();
const download = await admin.downloadExport ();
expect (download.suggestedFilename ()).toBe ("participants.txt" );
const content = await fs.readFile (await download.path (), "utf-8" );
expect (content).toContain ("Alpha Squad" );
})adds a sub post on behalf of a user Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/looking
Tournament admin page Tournament subs page Arrange
registration is closed (start time in the past) so the subs view is shown on the looking page
Create tournament author N-ZAP start times dateToDatabaseTimestamp (subDays (new Date (), 1 )) → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: [dateToDatabaseTimestamp (subDays (new Date (), 1 ))],
});Create user discord name “Subby Sam”
await factories.UserFactory .create ({
discordName: "Subby Sam" ,
});Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open add sub dialog
await admin.openAddSubDialog ();Assert
add sub dialog heading is visible
await expect (admin.locators.addSubDialogHeading).toBeVisible ();Act
Select user User “Subby Sam”
await admin.addSubForm.selectUser ("userId" , "Subby Sam" );Fill message with “Can play backline”
await admin.addSubForm.fill ("message" , "Can play backline" );Submit
await admin.addSubForm.submit ();Assert
add sub dialog heading count is 0
await expect (admin.locators.addSubDialogHeading).toHaveCount (0 );Tournament subs page Act
Open the tournament subs page tournament.id
await subs.goto (tournament.id);Assert
sub post text “Subby Sam” is visible
await expect (subs.subPostText ("Subby Sam" )).toBeVisible ();sub post text “Can play backline” is visible
await expect (subs.subPostText ("Can play backline" )).toBeVisible ();Source test ("adds a sub post on behalf of a user" , async ({ page, factories }) => {
// registration is closed (start time in the past) so the subs view is shown on the looking page
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: [dateToDatabaseTimestamp (subDays (new Date (), 1 ))],
});
await factories.UserFactory .create ({
discordName: "Subby Sam" ,
});
await impersonate (page, NZAP_TEST_ID );
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.openAddSubDialog ();
await expect (admin.locators.addSubDialogHeading).toBeVisible ();
await admin.addSubForm.selectUser ("userId" , "Subby Sam" );
await admin.addSubForm.fill ("message" , "Can play backline" );
await admin.addSubForm.submit ();
await expect (admin.locators.addSubDialogHeading).toHaveCount (0 );
const subs = new TournamentSubsPage (page);
await subs.goto (tournament.id);
await expect (subs.subPostText ("Subby Sam" )).toBeVisible ();
await expect (subs.subPostText ("Can play backline" )).toBeVisible ();
})adds a sub post on behalf of a user whose team dropped out Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/looking
Tournament admin page Tournament subs page Arrange
Create tournament author N-ZAP start times startedTournamentTimes () bracket progression DOUBLE_ELIMINATION → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});Create user discord name “Dropout Dana” → dropout
const dropout = await factories.UserFactory .create ({
discordName: "Dropout Dana" ,
});Create ROSTER_SIZE - 1 × user → droppingRest
const droppingRest = await factories.UserFactory .createMany (
ROSTER_SIZE - 1 ,
);Create tournament team tournament id tournament.id team pickUpTeam ("Recruiting Rays" ) member user ids dropout.id, ...droppingRest.map ((user) => user.id) is checked in true is looking true
await factories.TournamentTeamFactory .create (
{
tournamentId: tournament.id,
team: pickUpTeam ("Recruiting Rays" ),
memberUserIds: [dropout.id, ...droppingRest.map ((user) => user.id)],
},
// the team was recruiting on the LFG page before it dropped out
{ isCheckedIn: true , isLooking: true },
);enough opponents that dropping out one team does not end every match
Create teams tournament.id, teamSeeds (3 )
await createTeams (factories, tournament.id, teamSeeds (3 ));Start bracket tournament tournament.id
await factories.TournamentFactory .startBracket (tournament.id);Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Search teams “Recruiting Rays”
await admin.searchTeams ("Recruiting Rays" );Drop out team 0
await admin.dropOutTeam (0 );Open add sub dialog
await admin.openAddSubDialog ();Assert
add sub dialog heading is visible
await expect (admin.locators.addSubDialogHeading).toBeVisible ();Act
Select user User “Dropout Dana”
await admin.addSubForm.selectUser ("userId" , "Dropout Dana" );Fill message with “Free to sub now”
await admin.addSubForm.fill ("message" , "Free to sub now" );Submit
await admin.addSubForm.submit ();Assert
add sub dialog heading count is 0
await expect (admin.locators.addSubDialogHeading).toHaveCount (0 );Tournament subs page Act
Open the tournament subs page tournament.id
await subs.goto (tournament.id);Assert
sub post text “Dropout Dana” is visible
await expect (subs.subPostText ("Dropout Dana" )).toBeVisible ();sub post text “Free to sub now” is visible
await expect (subs.subPostText ("Free to sub now" )).toBeVisible ();Source test ("adds a sub post on behalf of a user whose team dropped out" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: DOUBLE_ELIMINATION ,
});
const dropout = await factories.UserFactory .create ({
discordName: "Dropout Dana" ,
});
const droppingRest = await factories.UserFactory .createMany (
ROSTER_SIZE - 1 ,
);
await factories.TournamentTeamFactory .create (
{
tournamentId: tournament.id,
team: pickUpTeam ("Recruiting Rays" ),
memberUserIds: [dropout.id, ...droppingRest.map ((user) => user.id)],
},
// the team was recruiting on the LFG page before it dropped out
{ isCheckedIn: true , isLooking: true },
);
// enough opponents that dropping out one team does not end every match
await createTeams (factories, tournament.id, teamSeeds (3 ));
await factories.TournamentFactory .startBracket (tournament.id);
await impersonate (page, NZAP_TEST_ID );
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.searchTeams ("Recruiting Rays" );
await admin.dropOutTeam (0 );
await admin.openAddSubDialog ();
await expect (admin.locators.addSubDialogHeading).toBeVisible ();
await admin.addSubForm.selectUser ("userId" , "Dropout Dana" );
await admin.addSubForm.fill ("message" , "Free to sub now" );
await admin.addSubForm.submit ();
await expect (admin.locators.addSubDialogHeading).toHaveCount (0 );
const subs = new TournamentSubsPage (page);
await subs.goto (tournament.id);
await expect (subs.subPostText ("Dropout Dana" )).toBeVisible ();
await expect (subs.subPostText ("Free to sub now" )).toBeVisible ();
})filters the team list by name and by captain Discord id Renders/to/:id /to/:id/admin /to/:id/admin
Tournament admin page Arrange
Create tournament → tournament
const tournament = await createTournament (factories);Create user discord id “1234567890123456789” → captain
const captain = await factories.UserFactory .create ({
discordId: CAPTAIN_DISCORD_ID ,
});Create ROSTER_SIZE - 1 × user → alphaRest
const alphaRest = await factories.UserFactory .createMany (ROSTER_SIZE - 1 );Create tournament team tournament id tournament.id team pickUpTeam ("Alpha Squad" ) member user ids captain.id, ...alphaRest.map ((user) => user.id)
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Alpha Squad" ),
memberUserIds: [captain.id, ...alphaRest.map ((user) => user.id)],
});Create 4 × user → bravoRoster
const bravoRoster = await factories.UserFactory .createMany (ROSTER_SIZE );Create tournament team tournament id tournament.id team pickUpTeam ("Bravo Squad" ) member user ids bravoRoster.map ((user) => user.id)
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Bravo Squad" ),
memberUserIds: bravoRoster.map ((user) => user.id),
});Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);the captain is on no other team, so their team name + discord id both single it out
Search teams “Alpha Squad”
await admin.searchTeams ("Alpha Squad" );Assert
team rows count is 1
await expect (admin.locators.teamRows).toHaveCount (1 );first team names reads “Alpha Squad”
await expect (admin.locators.teamNames.first ()).toHaveText ("Alpha Squad" );Act
Search teams “1234567890123456789”
await admin.searchTeams (CAPTAIN_DISCORD_ID );Assert
team rows count is 1
await expect (admin.locators.teamRows).toHaveCount (1 );first team names reads “Alpha Squad”
await expect (admin.locators.teamNames.first ()).toHaveText ("Alpha Squad" );Act
Search teams “zzz-no-such-team-zzz”
await admin.searchTeams ("zzz-no-such-team-zzz" );Assert
no search results text is visible
await expect (admin.locators.noSearchResultsText).toBeVisible ();Source test ("filters the team list by name and by captain Discord id" , async ({
page,
factories,
}) => {
const tournament = await createTournament (factories);
const captain = await factories.UserFactory .create ({
discordId: CAPTAIN_DISCORD_ID ,
});
const alphaRest = await factories.UserFactory .createMany (ROSTER_SIZE - 1 );
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Alpha Squad" ),
memberUserIds: [captain.id, ...alphaRest.map ((user) => user.id)],
});
const bravoRoster = await factories.UserFactory .createMany (ROSTER_SIZE );
await factories.TournamentTeamFactory .create ({
tournamentId: tournament.id,
team: pickUpTeam ("Bravo Squad" ),
memberUserIds: bravoRoster.map ((user) => user.id),
});
await impersonate (page, NZAP_TEST_ID );
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
// the captain is on no other team, so their team name + discord id both single it out
await admin.searchTeams ("Alpha Squad" );
await expect (admin.locators.teamRows).toHaveCount (1 );
await expect (admin.locators.teamNames.first ()).toHaveText ("Alpha Squad" );
await admin.searchTeams (CAPTAIN_DISCORD_ID );
await expect (admin.locators.teamRows).toHaveCount (1 );
await expect (admin.locators.teamNames.first ()).toHaveText ("Alpha Squad" );
await admin.searchTeams ("zzz-no-such-team-zzz" );
await expect (admin.locators.noSearchResultsText).toBeVisible ();
})Tournament admin bracket progression editing edits an unstarted follow-up bracket while the started bracket stays locked Renders/to/:id /to/:id/admin /to/:id/admin /to/:id/admin/brackets
Tournament admin page Arrange
Create tournament author N-ZAP start times startedTournamentTimes () bracket progression RR_TO_SE → tournament
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
});Create teams tournament.id, teamSeeds (4 )
await createTeams (factories, tournament.id, teamSeeds (4 ));Start bracket tournament tournament.id
await factories.TournamentFactory .startBracket (tournament.id);Tournament admin page Act
Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open brackets
await admin.openBrackets ();Assert
the started groups stage is locked and can not be removed
first bracket name inputs is disabled
await expect (admin.locators.bracketNameInputs.first ()).toBeDisabled ();bracket name inputs #1 is enabled
await expect (admin.locators.bracketNameInputs.nth (1 )).toBeEnabled ();first remove bracket buttons is disabled
await expect (admin.locators.removeBracketButtons.first ()).toBeDisabled ();remove bracket buttons #1 is enabled
await expect (admin.locators.removeBracketButtons.nth (1 )).toBeEnabled ();Act
Rename bracket 1 , “Top Cut”
await admin.renameBracket (1 , "Top Cut" );Save progression
await admin.saveProgression ();Open the tournament admin page tournament.id
await admin.goto (tournament.id);Open brackets
await admin.openBrackets ();Assert
bracket name inputs #1 has value “Top Cut”
await expect (admin.locators.bracketNameInputs.nth (1 )).toHaveValue (
"Top Cut" ,
);Source test ("edits an unstarted follow-up bracket while the started bracket stays locked" , async ({
page,
factories,
}) => {
const tournament = await factories.TournamentFactory .create ({
authorId: NZAP_TEST_ID ,
startTimes: startedTournamentTimes (),
bracketProgression: RR_TO_SE ,
});
await createTeams (factories, tournament.id, teamSeeds (4 ));
await factories.TournamentFactory .startBracket (tournament.id);
await impersonate (page, NZAP_TEST_ID );
const admin = new TournamentAdminPage (page);
await admin.goto (tournament.id);
await admin.openBrackets ();
// the started groups stage is locked and can not be removed
await expect (admin.locators.bracketNameInputs.first ()).toBeDisabled ();
await expect (admin.locators.bracketNameInputs.nth (1 )).toBeEnabled ();
await expect (admin.locators.removeBracketButtons.first ()).toBeDisabled ();
await expect (admin.locators.removeBracketButtons.nth (1 )).toBeEnabled ();
await admin.renameBracket (1 , "Top Cut" );
await admin.saveProgression ();
await admin.goto (tournament.id);
await admin.openBrackets ();
await expect (admin.locators.bracketNameInputs.nth (1 )).toHaveValue (
"Top Cut" ,
);
})createTournamente2e/tournament-admin.spec.ts:416
async function createTournament (
factories: Factories ,
{
establishedOrganization = false ,
mapPickingStyle,
}: {
establishedOrganization?: boolean ;
mapPickingStyle?: TournamentMapPickingStyle ;
} = {},
) {
const organization = establishedOrganization
? await factories.TournamentOrganizationFactory .create (
{ ownerId: NZAP_TEST_ID },
{ isEstablished: true },
// … 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.locators.editHeadinge2e/pages/tournament/tournament-admin-registration-page.ts:31
No JSDoc
page.getByRole ("heading" , { name: "Edit registration" }) adminRegistrationFormSchema.pickUpNameapp/features/tournament-admin/tournament-admin-registration-schemas.ts:46
pickUpName: textFieldOptional ({
label: "labels.regTeamName" ,
maxLength: TOURNAMENT .TEAM_NAME_MAX_LENGTH ,
}) TournamentAdminRegistrationPage.setTournamentNamee2e/pages/tournament/tournament-admin-registration-page.ts:74
async setTournamentName (index: number , name: string ) {
await this .page.getByLabel ("Tournament name" ).nth (index).fill (name);
}TournamentAdminRegistrationPage.savee2e/pages/tournament/tournament-admin-registration-page.ts:84
No JSDoc
save () {
return submit (this .page);
}TournamentAdminPage.locators.searchInpute2e/pages/tournament/tournament-admin-page.ts:29
No JSDoc
page.getByLabel ("Search teams" ) TournamentAdminPage.teamNamee2e/pages/tournament/tournament-admin-page.ts:73
No JSDoc
teamName (name: string ) {
return this .locators.teamNames.filter ({ hasText: name });
}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.locators.memberNamese2e/pages/tournament/tournament-team-page.ts:13
No JSDoc
page.getByTestId ("team-member-name" ) TournamentAdminPage.gotoe2e/pages/tournament/tournament-admin-page.ts:50
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentAdminPage (tournamentId) });
}TournamentAdminPage.checkTeamIne2e/pages/tournament/tournament-admin-page.ts:85
No JSDoc
async checkTeamIn (nth: number ) {
await this .teamRowActions (nth).click ();
await waitForPOSTResponse (this .page, async () => {
await this .page.getByRole ("menuitem" , { name: /^Check in / }).click ();
});
}TournamentAdminPage.checkTeamOute2e/pages/tournament/tournament-admin-page.ts:135
No JSDoc
async checkTeamOut (nth: number ) {
await this .teamRowActions (nth).click ();
await waitForPOSTResponse (this .page, async () => {
await this .page.getByRole ("menuitem" , { name: /^Check out/ }).click ();
});
}TournamentAdminPage.unregisterTeame2e/pages/tournament/tournament-admin-page.ts:157
No JSDoc
async unregisterTeam (nth: number ) {
await this .teamRowActions (nth).click ();
await this .page.getByRole ("menuitem" , { name: "Unregister" }).click ();
}TournamentAdminPage.locators.unregisterDialogHeadinge2e/pages/tournament/tournament-admin-page.ts:42
No JSDoc
page.getByRole ("heading" , {
name: /Unregister .* and delete its registration info\?/,
}) TournamentAdminPage.confirmUnregistere2e/pages/tournament/tournament-admin-page.ts:162
No JSDoc
confirmUnregister () {
return modalClickConfirmButton (this .page);
}TournamentAdminAuditPage.gotoe2e/pages/tournament/tournament-admin-audit-page.ts:13
No JSDoc
async goto (tournamentId: number ) {
await navigate ({
page: this .page,
url: `${tournamentAdminPage(tournamentId)}/audit` ,
});
}TournamentAdminAuditPage.eventCelle2e/pages/tournament/tournament-admin-audit-page.ts:21
eventCell (name: string ) {
return this .page.getByRole ("cell" , { name });
}TournamentAdminRegistrationPagee2e/pages/tournament/tournament-admin-registration-page.ts:22
TournamentAdminPagee2e/pages/tournament/tournament-admin-page.ts:17
TournamentTeamPagee2e/pages/tournament/tournament-team-page.ts:6
No JSDoc
TournamentAdminAuditPagee2e/pages/tournament/tournament-admin-audit-page.ts:6
TournamentAdminRegistrationPage.gotoNewe2e/pages/tournament/tournament-admin-registration-page.ts:40
No JSDoc
async gotoNew (tournamentId: number ) {
await navigate ({
page: this .page,
url: tournamentAdminRegistrationPage (tournamentId),
});
}TournamentAdminRegistrationPage.locators.addHeadinge2e/pages/tournament/tournament-admin-registration-page.ts:32
No JSDoc
page.getByRole ("heading" , { name: "Add new team" }) TournamentAdminRegistrationPage.selectPlayere2e/pages/tournament/tournament-admin-registration-page.ts:55
async selectPlayer (userName: string ) {
await selectUser ({ page: this .page, userName, labelName: "Player" });
}TournamentAdminRegistrationPage.selectCaptaine2e/pages/tournament/tournament-admin-registration-page.ts:78
No JSDoc
async selectCaptain (userId: number ) {
await this .page
.getByLabel ("Captain" , { exact: true })
.selectOption (String (userId));
}TournamentAdminRegistrationPage.openImportDialoge2e/pages/tournament/tournament-admin-registration-page.ts:105
No JSDoc
async openImportDialog () {
await this .locators.importTeamButton.click ();
}TournamentAdminRegistrationPage.locators.importDialogHeadinge2e/pages/tournament/tournament-admin-registration-page.ts:35
No JSDoc
page.getByRole ("heading" , { name: "Import team" }) TournamentAdminRegistrationPage.importFirstTeamFrome2e/pages/tournament/tournament-admin-registration-page.ts:118
async importFirstTeamFrom (tournamentQuery: string ) {
const dialog = this .page.getByRole ("dialog" );
await selectTournament ({ page: this .page, query: tournamentQuery });
const teamSelect = dialog.getByLabel ("Team" , { exact: true });
await expect (teamSelect.locator ("option" )).not.toHaveCount (0 );
await dialog.getByTestId ("submit-button" ).click ();
}TournamentAdminRegistrationPage.locators.teamNameInpute2e/pages/tournament/tournament-admin-registration-page.ts:33
No JSDoc
page.getByLabel ("Team name" ) TournamentAdminRegistrationPage.memberWithNamee2e/pages/tournament/tournament-admin-registration-page.ts:110
memberWithName (name: string ) {
return this .page
.getByRole ("button" , { name: "User search" })
.filter ({ hasText: name });
}TournamentTeamsPage.gotoe2e/pages/tournament/tournament-teams-page.ts:17
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentTeamsPage (tournamentId) });
}TournamentTeamsPage.teamNamede2e/pages/tournament/tournament-teams-page.ts:25
No JSDoc
teamNamed (name: string ) {
return this .locators.teamNames.getByText (name);
}TournamentTeamsPage.memberNamede2e/pages/tournament/tournament-teams-page.ts:21
No JSDoc
memberNamed (name: string ) {
return this .locators.teamMemberNames.getByText (name);
}TournamentTeamsPagee2e/pages/tournament/tournament-teams-page.ts:5
No JSDoc
TournamentAdminRegistrationPage.pickCounterpickMapse2e/pages/tournament/tournament-admin-registration-page.ts:89
pickCounterpickMaps () {
return pickCounterpickMaps (this .page);
}TournamentTeamPage.mapPoolStagee2e/pages/tournament/tournament-team-page.ts:32
mapPoolStage (mode: ModeShort , stageId: StageId ) {
return this .page.getByTestId (`team-map-pool-${mode}-${stageId}` );
}TournamentAdminRegistrationPage.pickedCounterpickMape2e/pages/tournament/tournament-admin-registration-page.ts:101
No JSDoc
pickedCounterpickMap (mode: ModeShort , stageId: StageId ) {
return pickedCounterpickMap (this .page, mode, stageId);
}TournamentAdminRegistrationPage.unpickCounterpickMape2e/pages/tournament/tournament-admin-registration-page.ts:97
No JSDoc
unpickCounterpickMap (mode: ModeShort , stageId: StageId ) {
return pickedCounterpickMap (this .page, mode, stageId).click ();
}TournamentAdminRegistrationPage.locators.invalidMapPoolErrore2e/pages/tournament/tournament-admin-registration-page.ts:36
No JSDoc
page.getByText ("Invalid map pool" ) TournamentAdminRegistrationPage.pickCounterpickMape2e/pages/tournament/tournament-admin-registration-page.ts:93
No JSDoc
pickCounterpickMap (mode: ModeShort , stageId: StageId ) {
return counterpickMap (this .page, mode, stageId).click ();
}TournamentAdminPage.openExportDialoge2e/pages/tournament/tournament-admin-page.ts:166
No JSDoc
async openExportDialog () {
await this .locators.exportButton.click ();
}TournamentAdminPage.locators.exportDialogHeadinge2e/pages/tournament/tournament-admin-page.ts:38
No JSDoc
page.getByRole ("heading" , {
name: "Export participants" ,
}) TournamentAdminPage.downloadExporte2e/pages/tournament/tournament-admin-page.ts:174
No JSDoc
async downloadExport () {
const downloadPromise = this .page.waitForEvent ("download" );
await this .locators.downloadButton.click ();
return downloadPromise;
}TournamentAdminPage.openAddSubDialoge2e/pages/tournament/tournament-admin-page.ts:170
No JSDoc
async openAddSubDialog () {
await this .locators.addSubButton.click ();
}TournamentAdminPage.locators.addSubDialogHeadinge2e/pages/tournament/tournament-admin-page.ts:35
No JSDoc
page.getByRole ("heading" , {
name: "Add sub post on behalf of a user" ,
}) addSubForUserFormSchema.userIdapp/features/tournament-lfg/tournament-lfg-schemas.ts:23
userId: userSearch ({ label: "labels.user" }) TournamentSubsPage.gotoe2e/pages/tournament/tournament-subs-page.ts:27
No JSDoc
async goto (tournamentId: number ) {
await navigate ({ page: this .page, url: tournamentSubsPage (tournamentId) });
}TournamentSubsPage.subPostTexte2e/pages/tournament/tournament-subs-page.ts:31
No JSDoc
subPostText (text: string ) {
// the poster's name is also in the side nav (as the logged in user) and
// in the closed mobile "You" panel
return this .page
.getByRole ("main" )
.getByText (text)
.filter ({ visible: true });
}TournamentSubsPagee2e/pages/tournament/tournament-subs-page.ts:8
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,
);
// … TournamentAdminPage.searchTeamse2e/pages/tournament/tournament-admin-page.ts:77
No JSDoc
async searchTeams (query: string ) {
await this .locators.searchInput.fill (query);
}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);
}TournamentAdminPage.locators.teamRowse2e/pages/tournament/tournament-admin-page.ts:30
No JSDoc
page.getByTestId ("team-row" ) TournamentAdminPage.locators.teamNamese2e/pages/tournament/tournament-admin-page.ts:31
No JSDoc
page.getByTestId ("team-name" ) TournamentAdminPage.locators.noSearchResultsTexte2e/pages/tournament/tournament-admin-page.ts:32
No JSDoc
page.getByText ("No registrations match your search" ) TournamentAdminPage.openBracketse2e/pages/tournament/tournament-admin-page.ts:114
async openBrackets () {
await this .adminTab ("Brackets" ).click ();
}TournamentAdminPage.locators.bracketNameInputse2e/pages/tournament/tournament-admin-page.ts:45
No JSDoc
page.getByLabel (/^Bracket name *\*?$/) TournamentAdminPage.locators.removeBracketButtonse2e/pages/tournament/tournament-admin-page.ts:46
No JSDoc
page.getByTestId ("brackets-remove-item-button" ) TournamentAdminPage.renameBrackete2e/pages/tournament/tournament-admin-page.ts:118
No JSDoc
async renameBracket (nth: number , name: string ) {
await this .locators.bracketNameInputs.nth (nth).fill (name);
}TournamentAdminPage.saveProgressione2e/pages/tournament/tournament-admin-page.ts:122
No JSDoc
async saveProgression () {
await submit (this .page);
}