diff --git a/apps/electron-backend-e2e/src/category-management.e2e.ts b/apps/electron-backend-e2e/src/category-management.e2e.ts index c4b9c8a50..a9de2a5e4 100644 --- a/apps/electron-backend-e2e/src/category-management.e2e.ts +++ b/apps/electron-backend-e2e/src/category-management.e2e.ts @@ -231,6 +231,20 @@ async function refreshFromWorkspaceHeader(page: Page): Promise { const dialog = page.locator('mat-dialog-container'); await expect(dialog).toBeVisible(); await dialog.getByRole('button', { name: 'Yes', exact: true }).click(); + + const refreshOverlay = page.locator('app-workspace-shell-import-overlay'); + await expect(refreshOverlay).toBeVisible({ timeout: 5000 }); + await expect( + refreshOverlay.getByRole('heading', { + name: 'Refreshing playlist', + exact: true, + }) + ).toBeVisible(); + await expect(refreshOverlay).toContainText(/Local library/); + await expect(refreshOverlay).toContainText( + /Preserving your library data|Removing cached streams|Removing cached categories/ + ); + await page.waitForSelector('mat-dialog-container', { state: 'detached' }); } @@ -240,66 +254,60 @@ function sidebarCategoryById(page: Page, categoryId: string): Locator { ); } -async function expectVisibleSidebarCategoryIds( - page: Page, - expectedIds: string[] -): Promise { - const categories = page.locator( - 'app-workspace-context-panel .category-item:visible' - ); - const actualIds: string[] = []; - const count = await categories.count(); - - for (let index = 0; index < count; index += 1) { - const categoryId = await categories - .nth(index) - .getAttribute('data-category-id'); - - if (categoryId) { - actualIds.push(categoryId.trim()); - } - } - - expect(actualIds).toEqual(expectedIds); -} - async function expectVisibleSidebarCategoryNames( page: Page, expectedNames: string[] ): Promise { - await expect - .poll(async () => { - const categories = page.locator( - 'app-workspace-context-panel .category-item:visible' - ); - const actualNames: string[] = []; - const count = await categories.count(); + try { + await expect + .poll(() => readVisibleSidebarCategoryNames(page), { + timeout: 30000, + }) + .toEqual(expectedNames); + } catch (error) { + const actualNames = await readVisibleSidebarCategoryNames(page); + throw new Error( + `Expected visible sidebar categories ${JSON.stringify( + expectedNames + )}, received ${JSON.stringify(actualNames)}`, + { cause: error } + ); + } +} - for (let index = 0; index < count; index += 1) { - const categoryName = - ( - await categories - .nth(index) - .locator('.nav-item-label') - .textContent() - )?.trim() ?? ''; +async function readVisibleSidebarCategoryNames(page: Page): Promise { + const categories = page.locator( + 'app-workspace-context-panel .category-item' + ); + const actualNames: string[] = []; + const count = await categories.count(); - if (categoryName) { - actualNames.push(categoryName); - } - } + for (let index = 0; index < count; index += 1) { + const category = categories.nth(index); + if (!(await category.isVisible())) { + continue; + } - return actualNames; - }) - .toEqual(expectedNames); + const categoryName = + (await category.locator('.nav-item-label').textContent())?.trim() ?? + ''; + + if (categoryName) { + actualNames.push(categoryName); + } + } + + return actualNames; } async function pickSidebarCategory( page: Page ): Promise<{ id: string; itemCount: number; name: string }> { - let preferredCandidate: - | { id: string; itemCount: number; name: string } - | null = null; + let preferredCandidate: { + id: string; + itemCount: number; + name: string; + } | null = null; await expect .poll(async () => { diff --git a/apps/electron-backend-e2e/src/providers.e2e.ts b/apps/electron-backend-e2e/src/providers.e2e.ts index 236c935fc..c9ebd4eb7 100644 --- a/apps/electron-backend-e2e/src/providers.e2e.ts +++ b/apps/electron-backend-e2e/src/providers.e2e.ts @@ -13,6 +13,7 @@ import { resetMockServers, stalkerMockServer, test, + waitForXtreamImportToFinish, waitForStalkerCatalog, waitForXtreamCatalog, } from './electron-test-fixtures'; @@ -65,23 +66,93 @@ test.describe('Electron Provider Smoke Tests', () => { await closeElectronApp(app); } }); + + test('shows refresh overlay immediately from the dashboard Xtream source menu', async ({ + dataDir, + request, + }) => { + await resetMockServers(request, ['xtream']); + + const app = await launchElectronApp(dataDir); + + try { + await addXtreamPortal(app.mainWindow); + await waitForXtreamCatalog(app.mainWindow); + await expectPortalDebugSuccess(app.mainWindow, 'xtream'); + + await goToDashboard(app.mainWindow); + await refreshRecentXtreamSourceFromDashboard( + app.mainWindow, + defaultXtreamPortalName + ); + await waitForXtreamImportToFinish(app.mainWindow); + } finally { + await closeElectronApp(app); + } + }); }); async function expectRecentSourceCard( page: Page, title: string ): Promise { - await expect( - page.getByTestId('dashboard-recent-sources-rail') - ).toBeVisible({ - timeout: 20000, - }); + await expect(page.getByTestId('dashboard-recent-sources-rail')).toBeVisible( + { + timeout: 20000, + } + ); await expect( - page.getByTestId('dashboard-recent-sources-rail-card').filter({ - hasText: title, - }).first() + page + .getByTestId('dashboard-recent-sources-rail-card') + .filter({ + hasText: title, + }) + .first() ).toBeVisible({ timeout: 20000, }); } + +async function refreshRecentXtreamSourceFromDashboard( + page: Page, + title: string +): Promise { + const sourceCard = page + .getByTestId('dashboard-recent-sources-rail-card') + .filter({ + hasText: title, + }) + .first(); + + await expect(sourceCard).toBeVisible({ timeout: 20000 }); + await sourceCard.hover(); + await sourceCard + .getByTestId('dashboard-recent-sources-rail-card-actions') + .click(); + await page + .getByRole('menuitem', { + name: 'Refresh Xtream playlist from remote', + exact: true, + }) + .click(); + + const dialog = page.locator('mat-dialog-container'); + await expect(dialog).toBeVisible(); + await dialog.getByRole('button', { name: 'Yes', exact: true }).click(); + + const refreshOverlay = page.locator('app-workspace-shell-import-overlay'); + await expect(refreshOverlay).toBeVisible({ timeout: 5000 }); + await expect( + refreshOverlay.getByRole('heading', { + name: 'Refreshing playlist', + exact: true, + }) + ).toBeVisible(); + await expect(refreshOverlay).toContainText(/Local library/); + await expect(refreshOverlay).toContainText( + /Preserving your library data|Removing cached streams|Removing cached categories/ + ); + + await page.waitForSelector('mat-dialog-container', { state: 'detached' }); +}