Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions packages/e2e/src/helpers/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

// VORTEX_E2E=1 disables automatic discovery, so all games go through the
// "Game not discovered" dialog and have their path set via a dialog.showOpenDialog stub.
// A logged-in app may still list the game under "Games detected": that section comes
// from the user's store libraries via the Nexus API, and doesn't give Vortex a path.
export type ManagedGameId = "stardewvalley";

export interface ManagedGame {
Expand All @@ -27,40 +29,21 @@

await test.step(`Manage game: ${gameId}`, async () => {
const navbar = new NavBar(vortexWindow);
const gamesPage = new GamesPage(vortexWindow);

// First interaction after a cold app launch — allow the nav to finish
// mounting rather than racing the default 5s UI budget.
await expect(navbar.gamesLink).toBeVisible({ timeout: Timeouts.NETWORK });

Check failure on line 35 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check-file-requirement.spec.ts:193:5 › Health Check - file requirement warnings › free user › Check the 'Install via mod page' link opens the required mod and its download resolves the requirement

3) src/tests/health-check-file-requirement.spec.ts:193:5 › Health Check - file requirement warnings › free user › Check the 'Install via mod page' link opens the required mod and its download resolves the requirement Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeVisible() failed Locator: getByText('Games', { exact: true }).first() Expected: visible Received: undefined at ../helpers/games.ts:35 33 | // First interaction after a cold app launch — allow the nav to finish 34 | // mounting rather than racing the default 5s UI budget. > 35 | await expect(navbar.gamesLink).toBeVisible({ timeout: Timeouts.NETWORK }); | ^ 36 | await navbar.gamesLink.click(); 37 | 38 | await stubOpenDialog(electronApp, fakeGame.gamePath); at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:35:36 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)
await navbar.gamesLink.click();

Check failure on line 36 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check-file-requirement.spec.ts:193:5 › Health Check - file requirement warnings › free user › Check the 'Install via mod page' link opens the required mod and its download resolves the requirement

3) src/tests/health-check-file-requirement.spec.ts:193:5 › Health Check - file requirement warnings › free user › Check the 'Install via mod page' link opens the required mod and its download resolves the requirement Error: locator.click: Target page, context or browser has been closed at ../helpers/games.ts:36 34 | // mounting rather than racing the default 5s UI budget. 35 | await expect(navbar.gamesLink).toBeVisible({ timeout: Timeouts.NETWORK }); > 36 | await navbar.gamesLink.click(); | ^ 37 | 38 | await stubOpenDialog(electronApp, fakeGame.gamePath); 39 | at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:36:28 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)

await stubOpenDialog(electronApp, fakeGame.gamePath);

// The supported games list is windowed, so the target game's tile isn't in
// the DOM until the list is filtered down to it. Search by name first.
await expect(gamesPage.searchInput).toBeVisible();
await gamesPage.searchInput.fill(gameName);

const tile = gamesPage.gameTileInSection(gamesPage.supportedSection, gameName);
await expect(tile).toBeVisible({ timeout: Timeouts.NETWORK });
await tile.scrollIntoViewIfNeeded();
await tile.hover();

const manualAddButton = gamesPage.manualAddButton(gameName);
await expect(manualAddButton).toBeVisible();
await manualAddButton.click();

await expect(gamesPage.notDiscoveredDialog).toBeVisible();
await expect(gamesPage.notDiscoveredDialog).toContainText(
"hasn't been automatically discovered",
);
await gamesPage.continueButton.click();
await expect(gamesPage.notDiscoveredDialog).toBeHidden();
const gamesPage = new GamesPage(vortexWindow);
await addGameManually(gamesPage, gameName);

try {
await expect(navbar.modsLink).toBeVisible({ timeout: Timeouts.NETWORK });
await openAddedGame(gamesPage, navbar, gameName);
} catch (e) {
throw new Error(

Check failure on line 46 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check-file-requirement.spec.ts:297:5 › Health Check - file requirement warnings › premium user › Check the warnings section 1-click install all resolves all requirements

6) src/tests/health-check-file-requirement.spec.ts:297:5 › Health Check - file requirement warnings › premium user › Check the warnings section 1-click install all resolves all requirements Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: Stardew Valley did not become the active game. On screen: {"headings":[],"dialogs":[],"alerts":[],"notifications":[]} at ../helpers/games.ts:46 44 | await openAddedGame(gamesPage, navbar, gameName); 45 | } catch (e) { > 46 | throw new Error( | ^ 47 | `${gameName} did not become the active game. On screen: ${await describeScreen(vortexWindow)}`, 48 | { cause: e }, 49 | ); at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:46:13 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18) [cause]: Error: locator.hover: Page crashed Call log: - waiting for locator('section').filter({ has: getByText('Added games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') - locator resolved to <div class="nxm-image nxm-image-game relative w-full rounded-lg shadow-sm">…</div> - attempting hover action - waiting for element to be visible and stable - element was detached from the DOM, retrying at openAddedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:91:19) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:44:7 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)

Check failure on line 46 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check.spec.ts:89:5 › Health Check - screen behaviour and settings › free user › Check the settings gear opens Settings at the Health Check section

4) src/tests/health-check.spec.ts:89:5 › Health Check - screen behaviour and settings › free user › Check the settings gear opens Settings at the Health Check section Error: Stardew Valley did not become the active game. On screen: {"headings":[],"dialogs":[],"alerts":[],"notifications":[]} at ../helpers/games.ts:46 44 | await openAddedGame(gamesPage, navbar, gameName); 45 | } catch (e) { > 46 | throw new Error( | ^ 47 | `${gameName} did not become the active game. On screen: ${await describeScreen(vortexWindow)}`, 48 | { cause: e }, 49 | ); at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:46:13 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18) [cause]: Error: locator.hover: Page crashed Call log: - waiting for locator('section').filter({ has: getByText('Added games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') - locator resolved to <div class="nxm-image nxm-image-game relative w-full rounded-lg shadow-sm">…</div> - attempting hover action - waiting for element to be visible and stable - element was detached from the DOM, retrying at openAddedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:91:19) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:44:7 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)
`${gameName} did not become the active game. On screen: ${await describeScreen(vortexWindow)}`,
{ cause: e },
);
Expand All @@ -69,3 +52,47 @@

return fakeGame;
}

/** Adds a game from "All supported games", answering the "Game not discovered" dialog. */
async function addGameManually(gamesPage: GamesPage, gameName: string): Promise<void> {
// The supported games list is windowed, so the target game's tile isn't in
// the DOM until the list is filtered down to it. Search by name first.
await expect(gamesPage.searchInput).toBeVisible();
await gamesPage.searchInput.fill(gameName);

const tile = gamesPage.gameTileInSection(gamesPage.supportedSection, gameName);
await expect(tile).toBeVisible({ timeout: Timeouts.NETWORK });

Check failure on line 64 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/mods-manual.spec.ts:30:7 › Mods - Manual Downloads › free › [QA-176] free user can manually download SMAPI and Install From File

7) src/tests/mods-manual.spec.ts:30:7 › Mods - Manual Downloads › free › [QA-176] free user can manually download SMAPI and Install From File Error: expect(locator).toBeVisible() failed Locator: locator('section').filter({ has: getByText('All supported games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') Expected: visible Received: undefined Call log: - Expect "toBeVisible" with timeout 60000ms - waiting for locator('section').filter({ has: getByText('All supported games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') at ../helpers/games.ts:64 62 | 63 | const tile = gamesPage.gameTileInSection(gamesPage.supportedSection, gameName); > 64 | await expect(tile).toBeVisible({ timeout: Timeouts.NETWORK }); | ^ 65 | // hover scrolls the tile into view itself, and unlike scrollIntoViewIfNeeded it 66 | // retries if the list re-renders the tile in the meantime 67 | await tile.hover(); at addGameManually (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:64:22) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:41:5 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)
// hover scrolls the tile into view itself, and unlike scrollIntoViewIfNeeded it
// retries if the list re-renders the tile in the meantime
await tile.hover();

Check failure on line 67 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/mods-manual.spec.ts:30:7 › Mods - Manual Downloads › free › [QA-176] free user can manually download SMAPI and Install From File

7) src/tests/mods-manual.spec.ts:30:7 › Mods - Manual Downloads › free › [QA-176] free user can manually download SMAPI and Install From File Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: locator.hover: Page crashed Call log: - waiting for locator('section').filter({ has: getByText('All supported games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') - locator resolved to <div class="nxm-image nxm-image-game relative w-full rounded-lg shadow-sm">…</div> - attempting hover action - waiting for element to be visible and stable - element is visible and stable - scrolling into view if needed at ../helpers/games.ts:67 65 | // hover scrolls the tile into view itself, and unlike scrollIntoViewIfNeeded it 66 | // retries if the list re-renders the tile in the meantime > 67 | await tile.hover(); | ^ 68 | 69 | const manualAddButton = gamesPage.manualAddButton(gameName); 70 | await expect(manualAddButton).toBeVisible(); at addGameManually (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:67:14) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:41:5 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)

Check failure on line 67 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check-mod-requirement.spec.ts:28:5 › Health Check - mod requirement suggestions › free user › Check a page-level requirement renders as a suggestion with its detail and disclaimer

2) src/tests/health-check-mod-requirement.spec.ts:28:5 › Health Check - mod requirement suggestions › free user › Check a page-level requirement renders as a suggestion with its detail and disclaimer Error: locator.hover: Page crashed Call log: - waiting for locator('section').filter({ has: getByText('All supported games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..') - locator resolved to <div class="nxm-image nxm-image-game relative w-full rounded-lg shadow-sm">…</div> - attempting hover action - waiting for element to be visible and stable - element was detached from the DOM, retrying at ../helpers/games.ts:67 65 | // hover scrolls the tile into view itself, and unlike scrollIntoViewIfNeeded it 66 | // retries if the list re-renders the tile in the meantime > 67 | await tile.hover(); | ^ 68 | 69 | const manualAddButton = gamesPage.manualAddButton(gameName); 70 | await expect(manualAddButton).toBeVisible(); at addGameManually (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:67:14) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:41:5 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)

const manualAddButton = gamesPage.manualAddButton(gameName);
await expect(manualAddButton).toBeVisible();
await manualAddButton.click();

Check failure on line 71 in packages/e2e/src/helpers/games.ts

View workflow job for this annotation

GitHub Actions / e2e (platform-cluster-public)

src/tests/health-check-file-requirement.spec.ts:297:5 › Health Check - file requirement warnings › premium user › Check the warnings section 1-click install all resolves all requirements

6) src/tests/health-check-file-requirement.spec.ts:297:5 › Health Check - file requirement warnings › premium user › Check the warnings section 1-click install all resolves all requirements Error: locator.click: Page crashed Call log: - waiting for locator('section').filter({ has: getByText('All supported games', { exact: true }) }).getByTestId('game-tile').and(getByAltText('Stardew Valley', { exact: true })).first().locator('..').getByRole('button', { name: 'Manual add', exact: true }) - locator resolved to <button type="button" class="nxm-button nxm-button-neutral nxm-button-strong w-full">…</button> - attempting click action - waiting for element to be visible, enabled and stable - element was detached from the DOM, retrying at ../helpers/games.ts:71 69 | const manualAddButton = gamesPage.manualAddButton(gameName); 70 | await expect(manualAddButton).toBeVisible(); > 71 | await manualAddButton.click(); | ^ 72 | 73 | await expect(gamesPage.notDiscoveredDialog).toBeVisible(); 74 | await expect(gamesPage.notDiscoveredDialog).toContainText("hasn't been automatically discovered"); at addGameManually (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:71:25) at /home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:41:5 at manageGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/helpers/games.ts:30:3) at Object.managedGame (/home/runner/_work/Vortex/Vortex/packages/e2e/src/fixtures/vortex-app.ts:440:18)

await expect(gamesPage.notDiscoveredDialog).toBeVisible();
await expect(gamesPage.notDiscoveredDialog).toContainText("hasn't been automatically discovered");
await gamesPage.continueButton.click();
await expect(gamesPage.notDiscoveredDialog).toBeHidden();
}

/**
* Adding a game switches to its new profile, but the spine follows the open page,
* so the user is left on the Games page in the Home workspace. The game's tile
* moves to "Added games", and its Open goes to the Mods page.
*/
async function openAddedGame(
gamesPage: GamesPage,
navbar: NavBar,
gameName: string,
): Promise<void> {
const addedTile = gamesPage.gameTileInSection(gamesPage.addedSection, gameName);
await expect(addedTile).toBeVisible({ timeout: Timeouts.GAME_ACTIVATION });
await addedTile.hover();

const openButton = gamesPage.openButton(gameName);
await expect(openButton).toBeVisible();
await openButton.click();

await expect(navbar.modsLink).toBeVisible({ timeout: Timeouts.GAME_ACTIVATION });
}
5 changes: 5 additions & 0 deletions packages/e2e/src/helpers/timeouts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const Timeouts = {
* are throttled, so SMAPI alone takes ~30s locally — past a single round-trip.
*/
MOD_INSTALL: min(2) * scalingFactor,
/**
* Making a game the active one: the profile switch, the staging folder set up
* on first manage, and the game's workspace loading afterwards.
*/
GAME_ACTIVATION: sec(30) * scalingFactor,
/** Bounded wait for a client-rendered modal to appear (or confirm it won't). */
MODAL: sec(10) * scalingFactor,
/** Cold-start and worker fixture setup. */
Expand Down
33 changes: 27 additions & 6 deletions packages/e2e/src/selectors/games.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Locator, Page } from "@playwright/test";

export class GamesPage {
readonly page: Page;
/** "Games detected": installed games Vortex found but the user hasn't added. */
/**
* "Games detected", or "New games detected" when it has games the user hasn't
* seen: installed games Vortex found but the user hasn't added.
*/
readonly detectedSection: Locator;
/** "Added games": the games the user is managing. */
readonly addedSection: Locator;
Expand All @@ -19,7 +22,7 @@ export class GamesPage {

constructor(page: Page) {
this.page = page;
this.detectedSection = this.section("Games detected");
this.detectedSection = this.section(/^(New games|Games) detected$/);
this.addedSection = this.section("Added games");
this.supportedSection = this.section("All supported games");
this.notDiscoveredDialog = page
Expand All @@ -40,15 +43,33 @@ export class GamesPage {

/**
* The primary action on a tile in "All supported games". It's revealed on
* hover, so hover the tile before clicking.
* hover, so hover the tile before clicking. Scoped to that section because a
* detected game also has a tile under "Games detected", whose action is
* "Add game" instead.
*/
manualAddButton(gameName: string): Locator {
return this.gameTile(gameName).getByRole("button", { name: "Manual add", exact: true });
return this.gameTileInSection(this.supportedSection, gameName).getByRole("button", {
name: "Manual add",
exact: true,
});
}

/**
* The primary action on a tile in "Added games". Adding a game activates it
* but leaves the user on the Games page; Open is what takes them into the
* game's workspace.
*/
openButton(gameName: string): Locator {
return this.gameTileInSection(this.addedSection, gameName).getByRole("button", {
name: "Open",
exact: true,
});
}

// A section is a <section> whose header carries its title; the sections have
// no accessible name, so anchor on the exact header text.
private section(title: string): Locator {
// no accessible name, so anchor on the header text. The title may also be
// given as a pattern for a section whose title varies.
private section(title: string | RegExp): Locator {
return this.page
.locator("section")
.filter({ has: this.page.getByText(title, { exact: true }) });
Expand Down
Loading