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);