Scanner coach mode opens a scanned file's game in coach mode and plays it again after a reload Renders/scanner
Scanner page Scanner coach page Scanner page Act
Open the scanner page
await scanner.goto ();Scan file VOD_PATH
await scanner.scanFile (VOD_PATH );Click open in coach mode link
await scanner.locators.openInCoachModeLink.click ();Scanner coach page Assert
title is visible
await expect (coach.locators.title).toBeVisible ();games summary reads “1 of 1 games”
await expect (coach.locators.gamesSummary).toHaveText ("1 of 1 games" );no events is visible
await expect (coach.locators.noEvents).toBeVisible ();() => coach.currentTime () be greater than or equal INTRO_SECONDS .from
await expect
.poll (() => coach.currentTime ())
.toBeGreaterThanOrEqual (INTRO_SECONDS .from );Act
Current time → startedAt
const startedAt = await coach.currentTime ();Assert
startedAt is less than INTRO_SECONDS .to
expect (startedAt).toBeLessThan (INTRO_SECONDS .to);Act
Click forward seconds button
await coach.locators.forwardSecondsButton.click ();Assert
() => coach.currentTime () is more than startedAt + 4
await expect.poll (() => coach.currentTime ()).toBeGreaterThan (startedAt + 4 );Act
Reload
await coach.reload ();Assert
reopen prompt is visible
await expect (coach.locators.reopenPrompt).toBeVisible ();Act
Open file VOD_PATH
await coach.openFile (VOD_PATH );Assert
play button is visible
await expect (coach.locators.playButton).toBeVisible ();Source test ("opens a scanned file's game in coach mode and plays it again after a reload" , async ({
page,
}) => {
const scanner = new ScannerPage (page);
await scanner.goto ();
await scanner.scanFile (VOD_PATH );
await scanner.locators.openInCoachModeLink.click ();
const coach = new ScannerCoachPage (page);
await expect (coach.locators.title).toBeVisible ();
await expect (coach.locators.gamesSummary).toHaveText ("1 of 1 games" );
await expect (coach.locators.noEvents).toBeVisible ();
await expect
.poll (() => coach.currentTime ())
.toBeGreaterThanOrEqual (INTRO_SECONDS .from );
const startedAt = await coach.currentTime ();
expect (startedAt).toBeLessThan (INTRO_SECONDS .to);
await coach.locators.forwardSecondsButton.click ();
await expect.poll (() => coach.currentTime ()).toBeGreaterThan (startedAt + 4 );
await coach.reload ();
await expect (coach.locators.reopenPrompt).toBeVisible ();
await coach.openFile (VOD_PATH );
await expect (coach.locators.playButton).toBeVisible ();
})steps through a saved scan's coach events and coaches a cast from either team Renders/scanner
Scanner coach page Scanner coach page Act
Open the scanner coach page “scanner-coach-vod.webm”
await coach.goto (COACH_VOD_NAME );Assert
not found is visible
await expect (coach.locators.notFound).toBeVisible ();Act
Seed scan name “scanner-coach-vod.webm” duration 315 events COACH_EVENTS
await coach.seedScan ({
name: COACH_VOD_NAME ,
duration: COACH_VOD_DURATION ,
events: COACH_EVENTS ,
});Reload
await coach.reload ();Assert
reopen prompt is visible
await expect (coach.locators.reopenPrompt).toBeVisible ();Act
Open file COACH_VOD_PATH
await coach.openFile (COACH_VOD_PATH );Assert
games summary reads “2 of 2 games”
await expect (coach.locators.gamesSummary).toHaveText ("2 of 2 games" );event entry “Special used” is visible
await expect (coach.eventEntry ("Special used" )).toBeVisible ();Act
Select category “Zone”
await coach.selectCategory ("Zone" );Arrange
[first, second] = [ coach.locators.eventEntries.nth (0 ), coach.locators.eventEntries.nth (1 ), ]
const [first, second] = [
coach.locators.eventEntries.nth (0 ),
coach.locators.eventEntries.nth (1 ),
];Act
Click next event button
await coach.locators.nextEventButton.click ();Assert
first has “aria-current” = “true”
await expect (first).toHaveAttribute ("aria-current" , "true" );async () => Math .floor (await coach.currentTime ()) is coach.entryPosition (first)
await expect
.poll (async () => Math .floor (await coach.currentTime ()))
.toBe (await coach.entryPosition (first));Act
Click next event button
await coach.locators.nextEventButton.click ();Assert
second has “aria-current” = “true”
await expect (second).toHaveAttribute ("aria-current" , "true" );async () => Math .floor (await coach.currentTime ()) is coach.entryPosition (second)
await expect
.poll (async () => Math .floor (await coach.currentTime ()))
.toBe (await coach.entryPosition (second));Act
Filter by mode “Rainmaker”
await coach.filterByMode ("Rainmaker" );Assert
games summary reads “1 of 2 games”
await expect (coach.locators.gamesSummary).toHaveText ("1 of 2 games" );Act
Click play button
await coach.locators.playButton.click ();Assert
() => coach.currentTime () be greater than or equal 215
await expect
.poll (() => coach.currentTime ())
.toBeGreaterThanOrEqual (CAST_GAME_STARTS_AT );cast picker is visible
await expect (coach.locators.castPicker).toBeVisible ();Act
Select category “Opening”
await coach.selectCategory ("Opening" );Assert
event entry “Opening lost” is visible
await expect (coach.eventEntry ("Opening lost" )).toBeVisible ();Act
Click cast team game 2 team “Bravo”
await coach.castTeam ({ game: 2 , team: "Bravo" }).click ();Assert
event entry “Opening won” is visible
await expect (coach.eventEntry ("Opening won" )).toBeVisible ();event entry “Opening lost” count is 0
await expect (coach.eventEntry ("Opening lost" )).toHaveCount (0 );Source test ("steps through a saved scan's coach events and coaches a cast from either team" , async ({
page,
}) => {
const coach = new ScannerCoachPage (page);
await coach.goto (COACH_VOD_NAME );
await expect (coach.locators.notFound).toBeVisible ();
await coach.seedScan ({
name: COACH_VOD_NAME ,
duration: COACH_VOD_DURATION ,
events: COACH_EVENTS ,
});
await coach.reload ();
await expect (coach.locators.reopenPrompt).toBeVisible ();
await coach.openFile (COACH_VOD_PATH );
await expect (coach.locators.gamesSummary).toHaveText ("2 of 2 games" );
await expect (coach.eventEntry ("Special used" )).toBeVisible ();
await coach.selectCategory ("Zone" );
const [first, second] = [
coach.locators.eventEntries.nth (0 ),
coach.locators.eventEntries.nth (1 ),
];
await coach.locators.nextEventButton.click ();
await expect (first).toHaveAttribute ("aria-current" , "true" );
await expect
.poll (async () => Math .floor (await coach.currentTime ()))
.toBe (await coach.entryPosition (first));
await coach.locators.nextEventButton.click ();
await expect (second).toHaveAttribute ("aria-current" , "true" );
await expect
.poll (async () => Math .floor (await coach.currentTime ()))
.toBe (await coach.entryPosition (second));
await coach.filterByMode ("Rainmaker" );
await expect (coach.locators.gamesSummary).toHaveText ("1 of 2 games" );
await coach.locators.playButton.click ();
await expect
.poll (() => coach.currentTime ())
.toBeGreaterThanOrEqual (CAST_GAME_STARTS_AT );
await expect (coach.locators.castPicker).toBeVisible ();
await coach.selectCategory ("Opening" );
await expect (coach.eventEntry ("Opening lost" )).toBeVisible ();
await coach.castTeam ({ game: 2 , team: "Bravo" }).click ();
await expect (coach.eventEntry ("Opening won" )).toBeVisible ();
await expect (coach.eventEntry ("Opening lost" )).toHaveCount (0 );
})Scanner live capture captures a game from a capture card into a session Renders/scanner
Fake capture card Scanner page Fake capture card Act
Install
await capture.install ();Scanner page Act
Open the scanner page
await scanner.goto ();Start capture scanner
await startCapture (scanner);Play fixture game scanner, capture
await playFixtureGame (scanner, capture);Assert
the scanner page captured game
await expectCapturedGame (scanner);Act
Click stop button
await scanner.locators.stopButton.click ();Click first live session links
await scanner.locators.liveSessionLinks.first ().click ();Assert
the scanner page captured game
await expectCapturedGame (scanner);Source test ("captures a game from a capture card into a session" , async ({
page,
}) => {
const capture = new FakeCaptureCard (page);
await capture.install ();
const scanner = new ScannerPage (page);
await scanner.goto ();
await startCapture (scanner);
await playFixtureGame (scanner, capture);
await expectCapturedGame (scanner);
await scanner.locators.stopButton.click ();
await scanner.locators.liveSessionLinks.first ().click ();
await expectCapturedGame (scanner);
})uploads a captured game, linking it to the SendouQ match it was played in Renders/q/match/:id /scanner
Fake capture card Scanner page SendouQ match page Arrange
Create players FIXTURE_GAME .winners → winners
const winners = await createPlayers (factories, FIXTURE_GAME .winners);Create players FIXTURE_GAME .losers → losers
const losers = await createPlayers (factories, FIXTURE_GAME .losers);Create SQ match alpha user ids winners.map ((user) => user.id) bravo user ids losers.map ((user) => user.id) map list SET_STAGE_IDS .map ((stageId) => ({ mode: "SZ" , stageId, source: "BOTH" , })) is concluded true → match
const match = await factories.SQMatchFactory .create (
{
alphaUserIds: winners.map ((user) => user.id),
bravoUserIds: losers.map ((user) => user.id),
mapList: SET_STAGE_IDS .map ((stageId) => ({
mode: "SZ" ,
stageId,
source: "BOTH" ,
})),
},
{ isConcluded: true },
);povUser = winners[FIXTURE_GAME .winners.indexOf (FIXTURE_GAME .pov.name)]
const povUser =
winners[FIXTURE_GAME .winners.indexOf (FIXTURE_GAME .pov.name)];Fake capture card Act
Install
await capture.install ();Scanner page Act
Open the scanner page
await scanner.goto ();Start capture scanner
await startCapture (scanner);Play fixture game scanner, capture
await playFixtureGame (scanner, capture);Click upload status button “Uploaded to sendou.ink”
await scanner.uploadStatusButton ("Uploaded to sendou.ink" ).click ();Assert
open match link has “href” = new RegExp (`/q/match/${match.id}$` )
await expect (scanner.locators.openMatchLink).toHaveAttribute (
"href" ,
new RegExp (`/q/match/${match.id}$` ),
);SendouQ match page Act
Open the SendouQ match page match.id, “result”
await matchPage.goto (match.id, "result" );Click first scoreboard details button
await matchPage.locators.scoreboardDetailsButton.first ().click ();Assert
scoreboard row FIXTURE_GAME .pov.name contains `${FIXTURE_GAME.pov.ka}${FIXTURE_GAME.pov.d}${FIXTURE_GAME.pov.s}`
await expect (matchPage.scoreboardRow (FIXTURE_GAME .pov.name)).toContainText (
`${FIXTURE_GAME.pov.ka}${FIXTURE_GAME.pov.d}${FIXTURE_GAME.pov.s}` ,
);Source test ("uploads a captured game, linking it to the SendouQ match it was played in" , async ({
page,
factories,
}) => {
const winners = await createPlayers (factories, FIXTURE_GAME .winners);
const losers = await createPlayers (factories, FIXTURE_GAME .losers);
const match = await factories.SQMatchFactory .create (
{
alphaUserIds: winners.map ((user) => user.id),
bravoUserIds: losers.map ((user) => user.id),
mapList: SET_STAGE_IDS .map ((stageId) => ({
mode: "SZ" ,
stageId,
source: "BOTH" ,
})),
},
{ isConcluded: true },
);
const povUser =
winners[FIXTURE_GAME .winners.indexOf (FIXTURE_GAME .pov.name)];
await impersonate (page, povUser.id);
const capture = new FakeCaptureCard (page);
await capture.install ();
const scanner = new ScannerPage (page);
await scanner.goto ();
await startCapture (scanner);
await playFixtureGame (scanner, capture);
await scanner.uploadStatusButton ("Uploaded to sendou.ink" ).click ();
await expect (scanner.locators.openMatchLink).toHaveAttribute (
"href" ,
new RegExp (`/q/match/${match.id}$` ),
);
const matchPage = new SendouQMatchPage (page);
await matchPage.goto (match.id, "result" );
await matchPage.locators.scoreboardDetailsButton.first ().click ();
await expect (matchPage.scoreboardRow (FIXTURE_GAME .pov.name)).toContainText (
`${FIXTURE_GAME.pov.ka}${FIXTURE_GAME.pov.d}${FIXTURE_GAME.pov.s}` ,
);
})refuses to capture in a second tab while one is capturing Renders/scanner
Fake capture card Scanner page Fake capture card Scanner page Fake capture card Act
Install
await capture.install ();Scanner page Act
Open the scanner page
await scanner.goto ();Start capture scanner
await startCapture (scanner);New page → secondTab
const secondTab = await context.newPage ();Fake capture card Act
Install
await new FakeCaptureCard (secondTab).install ();Scanner page Act
Open the scanner page
await secondScanner.goto ();Click start capture button
await secondScanner.locators.startCaptureButton.click ();Assert
another tab capturing error is visible
await expect (secondScanner.locators.anotherTabCapturingError).toBeVisible ();Source test ("refuses to capture in a second tab while one is capturing" , async ({
page,
context,
}) => {
const capture = new FakeCaptureCard (page);
await capture.install ();
const scanner = new ScannerPage (page);
await scanner.goto ();
await startCapture (scanner);
const secondTab = await context.newPage ();
await new FakeCaptureCard (secondTab).install ();
const secondScanner = new ScannerPage (secondTab);
await secondScanner.goto ();
await secondScanner.locators.startCaptureButton.click ();
await expect (secondScanner.locators.anotherTabCapturingError).toBeVisible ();
})tells when no capture card is connected Renders/scanner
Fake capture card Scanner page Fake capture card Act
Install kind “webcam”
await new FakeCaptureCard (page).install ({ kind: "webcam" });Scanner page Act
Open the scanner page
await scanner.goto ();Click start capture button
await scanner.locators.startCaptureButton.click ();Assert
no capture source error is visible
await expect (scanner.locators.noCaptureSourceError).toBeVisible ();Source test ("tells when no capture card is connected" , async ({ page }) => {
await new FakeCaptureCard (page).install ({ kind: "webcam" });
const scanner = new ScannerPage (page);
await scanner.goto ();
await scanner.locators.startCaptureButton.click ();
await expect (scanner.locators.noCaptureSourceError).toBeVisible ();
})Scanner reads a screenshot in the debug view Renders/scanner
Scanner page Scanner page Act
Open the scanner page
await scanner.goto ();Choose file SCOREBOARD_SCREENSHOT_PATH
await scanner.chooseFile (SCOREBOARD_SCREENSHOT_PATH );Assert
main contains `scores [85,82] · ${GAME.lobby} · ${GAME.mode} · ${GAME.stage}`
await expect (scanner.locators.main).toContainText (
`scores [85,82] · ${GAME.lobby} · ${GAME.mode} · ${GAME.stage}` ,
{ timeout: SCAN_TIMEOUT },
);Source test ("reads a screenshot in the debug view" , async ({ page }) => {
const scanner = new ScannerPage (page);
await scanner.goto ();
await scanner.chooseFile (SCOREBOARD_SCREENSHOT_PATH );
await expect (scanner.locators.main).toContainText (
`scores [85,82] · ${GAME.lobby} · ${GAME.mode} · ${GAME.stage}` ,
{ timeout: SCAN_TIMEOUT },
);
})scans a file into a match card that survives a reload Renders/scanner
Scanner page Act
Scan vod scanner
await scanVod (scanner);Assert
the scanner page scanned game
await expectScannedGame (scanner);Scanner page Act
Click show details button
await scanner.locators.showDetailsButton.click ();Assert
For each player of ...GAME .winners, ...GAME .losers
for (const player of [...GAME .winners, ...GAME .losers]) {
await expect (scanner.locators.main).toContainText (player);
}main contains player
await expect (scanner.locators.main).toContainText (player);Act
Reload
await scanner.reload ();Assert
the scanner page scanned game
await expectScannedGame (scanner);Act
Open the scanner page
await scanner.goto ();Click session link “scanner-vod.webm”
await scanner.sessionLink (VOD_NAME ).click ();Assert
the scanner page scanned game
await expectScannedGame (scanner);Source test ("scans a file into a match card that survives a reload" , async ({
page,
}) => {
const scanner = new ScannerPage (page);
await scanVod (scanner);
await expectScannedGame (scanner);
await scanner.locators.showDetailsButton.click ();
for (const player of [...GAME .winners, ...GAME .losers]) {
await expect (scanner.locators.main).toContainText (player);
}
await scanner.reload ();
await expectScannedGame (scanner);
await scanner.goto ();
await scanner.sessionLink (VOD_NAME ).click ();
await expectScannedGame (scanner);
})exports the scanned matches as CSV Renders/scanner
Scanner page Act
Scan vod scanner
await scanVod (scanner);Scanner page Act
Download matches csv → download
const download = await scanner.downloadMatchesCsv ();Arrange
[header, row, ...rest] = ( await fs.readFile (await download.path (), "utf-8" ) ) .trim () .split (/\r?\n/)…
const [header, row, ...rest] = (
await fs.readFile (await download.path (), "utf-8" )
)
.trim ()
.split (/\r?\n/)
.map ((line) => line.split ("," ));game = Object .fromEntries (header.map ((key, i) => [key, row[i]]))
const game = Object .fromEntries (header.map ((key, i) => [key, row[i]]));Assert
rest has 0 items
expect (rest).toHaveLength (0 );Number (game.t_seconds) be greater than or equal INTRO_SECONDS .from
expect (Number (game.t_seconds)).toBeGreaterThanOrEqual (INTRO_SECONDS .from );Number (game.t_seconds) is less than INTRO_SECONDS .to
expect (Number (game.t_seconds)).toBeLessThan (INTRO_SECONDS .to);game equals expect.objectContaining ({ source: VOD_NAME , lobby: GAME .lobby, mode: GAME .mod…
expect (game).toEqual (
expect.objectContaining ({
source: VOD_NAME ,
lobby: GAME .lobby,
mode: GAME .mode,
stage: GAME .stage,
result: "WIN" ,
score_for: "85" ,
score_against: "82" ,
weapon: GAME .pov.weapon,
ka: String (GAME .pov.ka),
d: String (GAME .pov.d),
s: String (GAME .pov.s),
}),
);Source test ("exports the scanned matches as CSV" , async ({ page }) => {
const scanner = new ScannerPage (page);
await scanVod (scanner);
const download = await scanner.downloadMatchesCsv ();
const [header, row, ...rest] = (
await fs.readFile (await download.path (), "utf-8" )
)
.trim ()
.split (/\r?\n/)
.map ((line) => line.split ("," ));
const game = Object .fromEntries (header.map ((key, i) => [key, row[i]]));
expect (rest).toHaveLength (0 );
expect (Number (game.t_seconds)).toBeGreaterThanOrEqual (INTRO_SECONDS .from );
expect (Number (game.t_seconds)).toBeLessThan (INTRO_SECONDS .to);
expect (game).toEqual (
expect.objectContaining ({
source: VOD_NAME ,
lobby: GAME .lobby,
mode: GAME .mode,
stage: GAME .stage,
result: "WIN" ,
score_for: "85" ,
score_against: "82" ,
weapon: GAME .pov.weapon,
ka: String (GAME .pov.ka),
d: String (GAME .pov.d),
s: String (GAME .pov.s),
}),
);
})downloads a scanned game's frames and reads as a zip Renders/scanner
Scanner page Act
Scan vod scanner
await scanVod (scanner);Scanner page Act
Click show details button
await scanner.locators.showDetailsButton.click ();Download game data → download
const download = await scanner.downloadGameData ();Arrange
files = unzipSync (await fs.readFile (await download.path ()))
const files = unzipSync (await fs.readFile (await download.path ()));names = Object .keys (files)
const names = Object .keys (files);events = JSON .parse (strFromU8 (files["events.json" ]!))
const events = JSON .parse (strFromU8 (files["events.json" ]!));Assert
names contains “match.json”
expect (names).toContain ("match.json" );names.some ((name) => /^frames\/[^/]+-MapStart -[^/]+\/frame\.webp$/.test (name), ) is true
expect (
names.some ((name) =>
/^frames\/[^/]+-MapStart -[^/]+\/frame\.webp$/.test (name),
),
).toBe (true );events.some ( (event: { type : string ; frame?: string }) => event.type === "Map … is true
expect (
events.some (
(event: { type : string ; frame?: string }) =>
event.type === "MapStart" &&
files[`${event.frame}/expected.json` ] !== undefined ,
),
).toBe (true );Source test ("downloads a scanned game's frames and reads as a zip" , async ({
page,
}) => {
const scanner = new ScannerPage (page);
await scanVod (scanner);
await scanner.locators.showDetailsButton.click ();
const download = await scanner.downloadGameData ();
const files = unzipSync (await fs.readFile (await download.path ()));
const names = Object .keys (files);
const events = JSON .parse (strFromU8 (files["events.json" ]!));
expect (names).toContain ("match.json" );
expect (
names.some ((name) =>
/^frames\/[^/]+-MapStart -[^/]+\/frame\.webp$/.test (name),
),
).toBe (true );
expect (
events.some (
(event: { type : string ; frame?: string }) =>
event.type === "MapStart" &&
files[`${event.frame}/expected.json` ] !== undefined ,
),
).toBe (true );
})prefills a new VoD from a scanned file Renders/scanner /vods/new
Scanner page Vod match fields Arrange
Grant user N-ZAP roles “VIDEO_ADDER”
await factories.UserFactory .grant (NZAP_TEST_ID , { roles: ["VIDEO_ADDER" ] });Act
Scan vod scanner
await scanVod (scanner);Scanner page Act
Click add to vods button
await scanner.locators.addToVodsButton.click ();New vod page
Act
Match 0 → newVodMatch
const newVodMatch = new NewVodPage (page).match (0 );Vod match fields Assert
starts at has value “/^0:0[3-8]$/”
await expect (newVodMatch.locators.startsAt).toHaveValue (INTRO_TIMESTAMP );mode radio GAME .mode is checked
await expect (newVodMatch.modeRadio (GAME .mode)).toBeChecked ();stage select contains GAME .stage
await expect (newVodMatch.locators.stageSelect).toContainText (GAME .stage);weapon select contains GAME .pov.weapon
await expect (newVodMatch.locators.weaponSelect).toContainText (
GAME .pov.weapon,
);Source test ("prefills a new VoD from a scanned file" , async ({
page,
factories,
}) => {
await factories.UserFactory .grant (NZAP_TEST_ID , { roles: ["VIDEO_ADDER" ] });
await impersonate (page, NZAP_TEST_ID );
const scanner = new ScannerPage (page);
await scanVod (scanner);
await scanner.locators.addToVodsButton.click ();
const newVodMatch = new NewVodPage (page).match (0 );
await expect (newVodMatch.locators.startsAt).toHaveValue (INTRO_TIMESTAMP );
await expect (newVodMatch.modeRadio (GAME .mode)).toBeChecked ();
await expect (newVodMatch.locators.stageSelect).toContainText (GAME .stage);
await expect (newVodMatch.locators.weaponSelect).toContainText (
GAME .pov.weapon,
);
})asks a logged out visitor to log in before adding a scanned file to VoDs Renders/scanner
Scanner page Log in popover Act
Scan vod scanner
await scanVod (scanner);Scanner page Act
Click add to vods logged out button
await scanner.locators.addToVodsLoggedOutButton.click ();Log in popover Assert
log in button is visible
await expect (new LogInPopover (page).locators.logInButton).toBeVisible ();Source test ("asks a logged out visitor to log in before adding a scanned file to VoDs" , async ({
page,
}) => {
const scanner = new ScannerPage (page);
await scanVod (scanner);
await scanner.locators.addToVodsLoggedOutButton.click ();
await expect (new LogInPopover (page).locators.logInButton).toBeVisible ();
})Renders/scanner
Scanner page Act
Scan vod scanner
await scanVod (scanner);Scanner page Act
Delete scan
await scanner.deleteScan ();Assert
sessions heading is visible
await expect (scanner.locators.sessionsHeading).toBeVisible ();session link “scanner-vod.webm” is not visible
await isNotVisible (scanner.sessionLink (VOD_NAME ));Source test ("deletes a scanned file" , async ({ page }) => {
const scanner = new ScannerPage (page);
await scanVod (scanner);
await scanner.deleteScan ();
await expect (scanner.locators.sessionsHeading).toBeVisible ();
await isNotVisible (scanner.sessionLink (VOD_NAME ));
})tells when an opened VoD is no longer saved Renders/scanner
Scanner page Scanner page Act
Go to vod “missing.webm”
await scanner.gotoVod ("missing.webm" );Assert
not found is visible
await expect (scanner.locators.notFound).toBeVisible ();Source test ("tells when an opened VoD is no longer saved" , async ({ page }) => {
const scanner = new ScannerPage (page);
await scanner.gotoVod ("missing.webm" );
await expect (scanner.locators.notFound).toBeVisible ();
})ScannerPage.gotoe2e/pages/scanner/scanner-page.ts:52
async goto () {
await this .page.addInitScript (
([key]) => localStorage.setItem (key, JSON .stringify ({ webgpu: false })),
[SETTINGS_STORAGE_KEY ],
);
await navigate ({ page: this .page, url: SCANNER_PAGE });
}ScannerPage.scanFilee2e/pages/scanner/scanner-page.ts:77
async scanFile (filePath: string ) {
await this .chooseFile (filePath);
await expect (this .locators.deleteButton).toBeVisible ({
timeout: SCAN_TIMEOUT ,
});
}ScannerPage.locators.openInCoachModeLinke2e/pages/scanner/scanner-page.ts:45
No JSDoc
main.getByRole ("link" , {
name: "Open in coach mode" ,
}) ScannerCoachPage.locators.titlee2e/pages/scanner/scanner-coach-page.ts:25
No JSDoc
main.getByText ("Coach mode" , { exact: true }) ScannerCoachPage.locators.gamesSummarye2e/pages/scanner/scanner-coach-page.ts:29
No JSDoc
main.getByText (/^\d+ of \d+ games$/) ScannerCoachPage.locators.noEventse2e/pages/scanner/scanner-coach-page.ts:33
No JSDoc
main.getByText ("No events were found in this file." ) ScannerCoachPage.currentTimee2e/pages/scanner/scanner-coach-page.ts:124
currentTime () {
return this .locators.video.evaluate (
(video: HTMLVideoElement ) => video.currentTime,
);
}ScannerCoachPage.locators.forwardSecondsButtone2e/pages/scanner/scanner-coach-page.ts:36
No JSDoc
main.getByRole ("button" , {
name: /^Forward \d+ seconds$/,
}) ScannerCoachPage.reloade2e/pages/scanner/scanner-coach-page.ts:56
No JSDoc
async reload () {
await this .page.reload ();
await expectIsHydrated (this .page);
}ScannerCoachPage.locators.reopenPrompte2e/pages/scanner/scanner-coach-page.ts:27
No JSDoc
main.getByText (/again to watch it here/) ScannerCoachPage.openFilee2e/pages/scanner/scanner-coach-page.ts:112
async openFile (filePath: string ) {
await this .locators.openFileInput.setInputFiles (filePath);
await expect
.poll (() =>
this .locators.video.evaluate (
(video: HTMLVideoElement ) => video.readyState,
),
)
.toBeGreaterThanOrEqual (HAVE_METADATA );
}ScannerCoachPage.locators.playButtone2e/pages/scanner/scanner-coach-page.ts:34
No JSDoc
main.getByRole ("button" , { name: "Play" , exact: true }) ScannerPagee2e/pages/scanner/scanner-page.ts:13
ScannerCoachPagee2e/pages/scanner/scanner-coach-page.ts:16
ScannerCoachPage.gotoe2e/pages/scanner/scanner-coach-page.ts:49
No JSDoc
async goto (name: string ) {
await navigate ({
page: this .page,
url: scannerSearchParams.href (SCANNER_PAGE , { view: "coach" , name }),
});
}ScannerCoachPage.locators.notFounde2e/pages/scanner/scanner-coach-page.ts:45
No JSDoc
main.getByText ("This VoD is no longer saved." ) ScannerCoachPage.seedScane2e/pages/scanner/scanner-coach-page.ts:66
async seedScan ({
name,
duration,
events,
}: {
name: string ;
duration: number ;
events: readonly DetectedEvent [];
}) {
const vod = {
name,
savedAt: Date .now (),
duration,
eventCount: events.length,
// … ScannerCoachPage.eventEntrye2e/pages/scanner/scanner-coach-page.ts:131
eventEntry (label: string ) {
return this .locators.eventEntries.filter ({ hasText: label });
}ScannerCoachPage.selectCategorye2e/pages/scanner/scanner-coach-page.ts:153
No JSDoc
async selectCategory (category: string ) {
await this .locators.categorySelect.click ();
await this .page
.getByRole ("option" , { name: new RegExp (`^${category} \\(\\d+\\)$` ) })
.click ();
}ScannerCoachPage.locators.nextEventButtone2e/pages/scanner/scanner-coach-page.ts:35
No JSDoc
main.getByRole ("button" , { name: "Next event" }) ScannerCoachPage.filterByModee2e/pages/scanner/scanner-coach-page.ts:160
No JSDoc
async filterByMode (mode: string ) {
await this .locators.modeFilterPill.click ();
await this .locators.modeFilterSelect.click ();
await this .page.getByRole ("option" ).filter ({ hasText: mode }).click ();
}ScannerCoachPage.locators.castPickere2e/pages/scanner/scanner-coach-page.ts:44
No JSDoc
main.getByText ("Cast · coaching" ) ScannerCoachPage.castTeame2e/pages/scanner/scanner-coach-page.ts:146
castTeam ({ game, team }: { game: number ; team: "Alpha" | "Bravo" }) {
const side = team === "Alpha" ? 0 : 1 ;
return this .page.locator (
`label[for="chip-radio-coach-cast-team-${game}-${side}"]` ,
);
}FakeCaptureCard.installe2e/pages/scanner/fake-capture-card.ts:30
async install ({
kind = "captureCard" ,
}: {
kind?: keyof typeof DEVICE_LABELS ;
} = {}) {
await this .page.addInitScript (installFakeDevice, DEVICE_LABELS [kind]);
}startCapturee2e/scanner-live.spec.ts:123
No JSDoc
async function startCapture (scanner: ScannerPage ) {
await scanner.locators.startCaptureButton.click ();
await expect (scanner.locators.stopButton).toBeVisible ({
timeout: SCAN_TIMEOUT ,
});
}playFixtureGamee2e/scanner-live.spec.ts:131
async function playFixtureGame (scanner: ScannerPage , capture: FakeCaptureCard ) {
await capture.show ("intro" );
await expect (scanner.locators.main).toContainText (
`Game started: ${FIXTURE_GAME.mode} on ${FIXTURE_GAME.stage}` ,
{ timeout: SCAN_TIMEOUT },
);
await capture.show (null );
await capture.show ("results" );
await expect (scanner.locators.main).toContainText ("WIN" , {
timeout: SCAN_TIMEOUT ,
});
}expectCapturedGamee2e/scanner-live.spec.ts:144
No JSDoc
async function expectCapturedGame (scanner: ScannerPage ) {
for (const text of FIXTURE_GAME .card) {
await expect (scanner.locators.main).toContainText (text);
}
}ScannerPage.locators.stopButtone2e/pages/scanner/scanner-page.ts:23
No JSDoc
main.getByRole ("button" , { name: "Stop" , exact: true }) ScannerPage.locators.liveSessionLinkse2e/pages/scanner/scanner-page.ts:24
No JSDoc
main.locator ('a[href*="view=session"]' ) FakeCaptureCarde2e/pages/scanner/fake-capture-card.ts:22
createPlayerse2e/scanner-live.spec.ts:111
async function createPlayers (factories: Factories , names: readonly string []) {
const users: Array <{ id: number }> = [];
for (const name of names) {
users.push (
await factories.UserFactory .create ({
profile: { inGameName: `${name}#1234` },
}),
);
}
return users;
}ScannerPage.uploadStatusButtone2e/pages/scanner/scanner-page.ts:90
uploadStatusButton (state: string ) {
return this .locators.main.getByRole ("button" , { name: state });
}ScannerPage.locators.openMatchLinke2e/pages/scanner/scanner-page.ts:25
No JSDoc
page.getByRole ("link" , { name: "Open match" }) SendouQMatchPage.gotoe2e/pages/sendouq/sendouq-match-page.ts:70
No JSDoc
async goto (matchId: number , tab: Tab = "action" ) {
await navigate ({
page: this .page,
url: `${sendouQMatchPage(matchId)}?tab=${tab}` ,
});
}SendouQMatchPage.locators.scoreboardDetailsButtone2e/pages/sendouq/sendouq-match-page.ts:60
No JSDoc
page.getByRole ("button" , { name: "Details" }) SendouQMatchPage.scoreboardRowe2e/pages/sendouq/sendouq-match-page.ts:100
scoreboardRow (playerName: string ) {
return this .page.getByRole ("row" ).filter ({
has: this .page.getByRole ("rowheader" , { name: playerName, exact: true }),
});
}SendouQMatchPagee2e/pages/sendouq/sendouq-match-page.ts:33
No JSDoc
ScannerPage.locators.startCaptureButtone2e/pages/scanner/scanner-page.ts:22
No JSDoc
page.getByRole ("button" , { name: "Start capture" }) ScannerPage.locators.anotherTabCapturingErrore2e/pages/scanner/scanner-page.ts:26
No JSDoc
main.getByText (
"The scanner is already capturing in another tab" ,
) ScannerPage.locators.noCaptureSourceErrore2e/pages/scanner/scanner-page.ts:29
No JSDoc
main.getByText (
"No capture card or OBS Virtual Camera found" ,
) ScannerPage.chooseFilee2e/pages/scanner/scanner-page.ts:72
No JSDoc
async chooseFile (filePath: string ) {
await this .locators.fileInput.setInputFiles (filePath);
}ScannerPage.locators.maine2e/pages/scanner/scanner-page.ts:21
No JSDoc
main scanVode2e/scanner.spec.ts:176
No JSDoc
async function scanVod (scanner: ScannerPage ) {
await scanner.goto ();
await scanner.scanFile (VOD_PATH );
}expectScannedGamee2e/scanner.spec.ts:181
No JSDoc
async function expectScannedGame (scanner: ScannerPage ) {
for (const text of GAME .card) {
await expect (scanner.locators.main).toContainText (text);
}
}ScannerPage.locators.showDetailsButtone2e/pages/scanner/scanner-page.ts:41
No JSDoc
main.getByRole ("button" , { name: "Show details" }) ScannerPage.reloade2e/pages/scanner/scanner-page.ts:67
No JSDoc
async reload () {
await this .page.reload ();
await expectIsHydrated (this .page);
}ScannerPage.sessionLinke2e/pages/scanner/scanner-page.ts:85
sessionLink (name: string ) {
return this .page.getByRole ("link" , { name: new RegExp (name) });
}ScannerPage.downloadMatchesCsve2e/pages/scanner/scanner-page.ts:105
No JSDoc
async downloadMatchesCsv () {
await this .locators.csvButton.click ();
const downloadPromise = this .page.waitForEvent ("download" );
await this .page.getByRole ("menuitem" , { name: "Matches" }).click ();
return downloadPromise;
}ScannerPage.downloadGameDatae2e/pages/scanner/scanner-page.ts:99
No JSDoc
async downloadGameData () {
const downloadPromise = this .page.waitForEvent ("download" );
await this .locators.gameDataButton.click ();
return downloadPromise;
}ScannerPage.locators.addToVodsButtone2e/pages/scanner/scanner-page.ts:37
No JSDoc
main.getByRole ("link" , { name: "Add to VoDs" }) NewVodPage.matche2e/pages/vods/new-vod-page.ts:43
No JSDoc
match (index: number ) {
return new VodMatchFields (this .page, index);
}VodMatchFields.locators.startsAte2e/pages/vods/new-vod-page.ts:68
No JSDoc
page.getByLabel ("Start timestamp" ).nth (index) VodMatchFields.modeRadioe2e/pages/vods/new-vod-page.ts:74
No JSDoc
modeRadio (modeName: string ) {
return this .page.getByRole ("radio" , { name: modeName }).nth (this .index);
}VodMatchFields.locators.stageSelecte2e/pages/vods/new-vod-page.ts:69
No JSDoc
page.getByTestId ("stage-select" ).nth (index) VodMatchFields.locators.weaponSelecte2e/pages/vods/new-vod-page.ts:70
No JSDoc
page.getByTestId (`match-${index}-weapon` ) NewVodPagee2e/pages/vods/new-vod-page.ts:15
VodMatchFieldse2e/pages/vods/new-vod-page.ts:58
ScannerPage.locators.addToVodsLoggedOutButtone2e/pages/scanner/scanner-page.ts:38
No JSDoc
main.getByRole ("button" , {
name: "Add to VoDs" ,
}) LogInPopover.locators.logInButtone2e/pages/layout/log-in-popover.ts:9
No JSDoc
page.getByTestId ("log-in-popover-button" ) LogInPopovere2e/pages/layout/log-in-popover.ts:4
ScannerPage.deleteScane2e/pages/scanner/scanner-page.ts:94
No JSDoc
async deleteScan () {
await this .locators.deleteButton.click ();
await this .locators.confirmDeleteButton.click ();
}ScannerPage.locators.sessionsHeadinge2e/pages/scanner/scanner-page.ts:33
No JSDoc
page.getByRole ("heading" , { name: "Sessions" }) isNotVisiblee2e/helpers/playwright.ts:745
No JSDoc
export function isNotVisible (locator: Locator ) {
return expect (locator).toHaveCount (0 );
}ScannerPage.gotoVode2e/pages/scanner/scanner-page.ts:60
No JSDoc
async gotoVod (name: string ) {
await navigate ({
page: this .page,
url: scannerSearchParams.href (SCANNER_PAGE , { view: "vod" , name }),
});
}ScannerPage.locators.notFounde2e/pages/scanner/scanner-page.ts:43
No JSDoc
main.getByText ("This VoD is no longer saved." )