From 0f3a75f2b2165f94ed2d42584e5a7e6ecf8fe395 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 06:07:26 +0000 Subject: [PATCH] fix(e2e): fix category-management navigation race and grid card click race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two separate root causes: 1. category-management.e2e.ts: openWorkspaceSection('Live TV') was not awaited — pickSidebarCategory ran before the route settled on the live section, so it read VOD categories from the stale sidebar (e.g. 'Action' instead of a live category). The manage-categories dialog opens for the live section, so searching for a VOD name found nothing. Fix: add waitForURL(/\/workspace\/xtreams\/[^/]+\/live/) after both openWorkspaceSection('Live TV') calls. 2. recent.e2e.ts / favorites.e2e.ts: waitForFirstGridListCardTitle read the first card's title, but clickGridListCardByTitle then did a fresh DOM search by that title. Between those two calls the grid could re-sort/paginate, making the card unfindable. Fix: new clickFirstGridListCard helper atomically reads the title and clicks the same card reference, replacing the two-step pattern. https://claude.ai/code/session_01VU6ZZTG7YrhKhKBQvBfmpp --- .../src/category-management.e2e.ts | 9 +++++++++ .../src/electron-test-fixtures.ts | 19 +++++++++++++++++++ .../electron-backend-e2e/src/favorites.e2e.ts | 15 +++++++-------- apps/electron-backend-e2e/src/recent.e2e.ts | 12 ++++++------ 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/apps/electron-backend-e2e/src/category-management.e2e.ts b/apps/electron-backend-e2e/src/category-management.e2e.ts index 77f94b635..a7480e4db 100644 --- a/apps/electron-backend-e2e/src/category-management.e2e.ts +++ b/apps/electron-backend-e2e/src/category-management.e2e.ts @@ -30,6 +30,12 @@ test.describe('Electron Xtream Category Management', () => { }); await waitForXtreamWorkspaceReady(app.mainWindow); await openWorkspaceSection(app.mainWindow, 'Live TV'); + // Wait for the route to settle on the live TV section so that + // the sidebar shows live categories (not VOD/series from a + // previous section) before we read from it. + await app.mainWindow.waitForURL( + /\/workspace\/xtreams\/[^/]+\/live/ + ); const targetCategory = await pickSidebarCategory(app.mainWindow); @@ -73,6 +79,9 @@ test.describe('Electron Xtream Category Management', () => { await sourceRowByTitle(app.mainWindow, portalName).first().click(); await waitForXtreamWorkspaceReady(app.mainWindow); await openWorkspaceSection(app.mainWindow, 'Live TV'); + await app.mainWindow.waitForURL( + /\/workspace\/xtreams\/[^/]+\/live/ + ); await expect(sidebarCategoryById(app.mainWindow, targetCategory.id)).toHaveCount( 0 ); diff --git a/apps/electron-backend-e2e/src/electron-test-fixtures.ts b/apps/electron-backend-e2e/src/electron-test-fixtures.ts index e05ecb13a..247f1e10c 100644 --- a/apps/electron-backend-e2e/src/electron-test-fixtures.ts +++ b/apps/electron-backend-e2e/src/electron-test-fixtures.ts @@ -635,6 +635,25 @@ export async function waitForFirstGridListCardTitle(page: Page): Promise return ((await card.locator('.title').textContent()) ?? '').trim(); } +/** + * Waits for the first grid card to appear with a non-empty title, clicks it, + * and returns the title. Use this instead of waitForFirstGridListCardTitle + + * clickGridListCardByTitle to avoid a race condition where the grid re-renders + * between the title read and the subsequent search-by-title click. + */ +export async function clickFirstGridListCard(page: Page): Promise { + const card = page.locator('.category-content-layout mat-card').first(); + await expect(card).toBeVisible({ timeout: 20000 }); + const titleEl = card.locator('.title'); + let title = ''; + await expect(async () => { + title = ((await titleEl.textContent()) ?? '').trim(); + expect(title.length).toBeGreaterThan(0); + }).toPass({ timeout: 10000 }); + await card.click(); + return title; +} + export async function clickGridListCardByTitle( page: Page, title: string diff --git a/apps/electron-backend-e2e/src/favorites.e2e.ts b/apps/electron-backend-e2e/src/favorites.e2e.ts index bdba8d573..05660ff32 100644 --- a/apps/electron-backend-e2e/src/favorites.e2e.ts +++ b/apps/electron-backend-e2e/src/favorites.e2e.ts @@ -5,7 +5,7 @@ import { channelItemByTitle, clickCategoryById, clickCategoryByNameExact, - clickGridListCardByTitle, + clickFirstGridListCard, closeElectronApp, contentCardByTitle, defaultXtreamPassword, @@ -22,7 +22,6 @@ import { switchUnifiedCollectionContent, switchUnifiedCollectionScope, test, - waitForFirstGridListCardTitle, waitForM3uCatalog, waitForStalkerCatalog, waitForXtreamWorkspaceReady, @@ -138,10 +137,11 @@ test.describe('Electron Favorites', () => { app.mainWindow, vodFixture.categoryName ); - // Pick the title from the first displayed card (grid sorts by date-desc, - // so fixture order ≠ display order; pagination may hide some items). - const movieTitle = await waitForFirstGridListCardTitle(app.mainWindow); - await clickGridListCardByTitle(app.mainWindow, movieTitle); + // Use clickFirstGridListCard: grid sorts by date-desc so fixture order ≠ + // display order; this atomically reads the first card's title and clicks + // it, avoiding a race where the grid re-renders between the title read + // and a separate search-by-title click. + const movieTitle = await clickFirstGridListCard(app.mainWindow); await addCurrentDetailToFavorites(app.mainWindow); await goBackFromDetail(app.mainWindow); @@ -150,8 +150,7 @@ test.describe('Electron Favorites', () => { app.mainWindow, seriesFixture.categoryName ); - const seriesTitle = await waitForFirstGridListCardTitle(app.mainWindow); - await clickGridListCardByTitle(app.mainWindow, seriesTitle); + const seriesTitle = await clickFirstGridListCard(app.mainWindow); await addCurrentDetailToFavorites(app.mainWindow); await goBackFromDetail(app.mainWindow); diff --git a/apps/electron-backend-e2e/src/recent.e2e.ts b/apps/electron-backend-e2e/src/recent.e2e.ts index 4257ad69c..31a741bc7 100644 --- a/apps/electron-backend-e2e/src/recent.e2e.ts +++ b/apps/electron-backend-e2e/src/recent.e2e.ts @@ -5,7 +5,7 @@ import { channelItemByTitle, clickCategoryById, clickCategoryByNameExact, - clickGridListCardByTitle, + clickFirstGridListCard, closeElectronApp, contentCardByTitle, defaultXtreamPassword, @@ -23,7 +23,6 @@ import { switchUnifiedCollectionContent, switchUnifiedCollectionScope, test, - waitForFirstGridListCardTitle, waitForM3uCatalog, waitForStalkerCatalog, waitForXtreamWorkspaceReady, @@ -142,8 +141,10 @@ test.describe('Electron Recently Viewed', () => { ); // Pick from the displayed grid: fixture order ≠ display order (date-desc // sort + pagination), so the first fixture item may not be on page 1. - const movieTitle = await waitForFirstGridListCardTitle(app.mainWindow); - await clickGridListCardByTitle(app.mainWindow, movieTitle); + // Use clickFirstGridListCard to atomically read + click the first visible + // card, avoiding a race where the grid re-renders between title read and + // a separate search-by-title click. + const movieTitle = await clickFirstGridListCard(app.mainWindow); await playCurrentDetail(app.mainWindow); await goBackFromDetail(app.mainWindow); @@ -152,8 +153,7 @@ test.describe('Electron Recently Viewed', () => { app.mainWindow, seriesFixture.categoryName ); - const seriesTitle = await waitForFirstGridListCardTitle(app.mainWindow); - await clickGridListCardByTitle(app.mainWindow, seriesTitle); + const seriesTitle = await clickFirstGridListCard(app.mainWindow); await playFirstSeriesEpisode(app.mainWindow); await openPlaylistRecent(app.mainWindow);