mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
test(e2e): add comprehensive tests for dashboard activation, favorites, recent items, and settings behavior
Entire-Checkpoint: 02062a64b591
This commit is contained in:
1 parent
bf09b39ffc
commit
26fa697afe
6 files changed
+618
No files matched your search
@@ -0,0 +1,179 @@
|
||||
import { Page } from '@playwright/test';
|
||||
import {
|
||||
addXtreamPortal,
|
||||
clickCategoryByNameExact,
|
||||
clickFirstGridListCard,
|
||||
closeElectronApp,
|
||||
defaultXtreamPassword,
|
||||
defaultXtreamUsername,
|
||||
expect,
|
||||
goToDashboard,
|
||||
launchElectronApp,
|
||||
openWorkspaceSection,
|
||||
resetMockServers,
|
||||
test,
|
||||
waitForXtreamWorkspaceReady,
|
||||
} from './electron-test-fixtures';
|
||||
import {
|
||||
fetchXtreamLiveFixture,
|
||||
fetchXtreamSeriesFixture,
|
||||
fetchXtreamVodFixture,
|
||||
getXtreamTitle,
|
||||
pickDistinctTitles,
|
||||
} from './portal-mock-fixtures';
|
||||
|
||||
test.describe('Dashboard Activation', () => {
|
||||
test('opens live favorites in the collection route and movies/series in source detail routes from the dashboard', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
const liveFixture = await fetchXtreamLiveFixture(request, xtreamCredentials);
|
||||
const vodFixture = await fetchXtreamVodFixture(request, xtreamCredentials);
|
||||
const seriesFixture = await fetchXtreamSeriesFixture(
|
||||
request,
|
||||
xtreamCredentials
|
||||
);
|
||||
const [liveTitle] = pickDistinctTitles(liveFixture.items, getXtreamTitle);
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: 'Xtream Dashboard Activation Source',
|
||||
});
|
||||
await waitForXtreamWorkspaceReady(app.mainWindow);
|
||||
|
||||
await openWorkspaceSection(app.mainWindow, 'Live TV');
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
liveFixture.categoryName
|
||||
);
|
||||
await toggleFavoriteForChannel(app.mainWindow, liveTitle);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
vodFixture.categoryName
|
||||
);
|
||||
const movieTitle = await clickFirstGridListCard(app.mainWindow);
|
||||
await playCurrentDetail(app.mainWindow);
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
seriesFixture.categoryName
|
||||
);
|
||||
const seriesTitle = await clickFirstGridListCard(app.mainWindow);
|
||||
await playFirstSeriesEpisode(app.mainWindow);
|
||||
|
||||
await goToDashboard(app.mainWindow);
|
||||
|
||||
await dashboardActivityItemByTitle(
|
||||
app.mainWindow,
|
||||
'Global Favorites',
|
||||
liveTitle
|
||||
).click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/favorites$/);
|
||||
await expect(
|
||||
app.mainWindow.locator('.player-toolbar .close-btn').first()
|
||||
).toBeVisible({ timeout: 20000 });
|
||||
|
||||
await app.mainWindow.goBack();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/dashboard$/);
|
||||
|
||||
await dashboardActivityItemByTitle(
|
||||
app.mainWindow,
|
||||
'Recently Watched',
|
||||
movieTitle
|
||||
).click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/vod\/[^/]+\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
movieTitle
|
||||
);
|
||||
|
||||
await app.mainWindow.goBack();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/dashboard$/);
|
||||
|
||||
await dashboardActivityItemByTitle(
|
||||
app.mainWindow,
|
||||
'Recently Watched',
|
||||
seriesTitle
|
||||
).click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/series\/[^/]+\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
seriesTitle
|
||||
);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const xtreamCredentials = {
|
||||
username: defaultXtreamUsername,
|
||||
password: defaultXtreamPassword,
|
||||
};
|
||||
|
||||
function dashboardActivityItemByTitle(
|
||||
page: Page,
|
||||
widgetTitle: string,
|
||||
title: string
|
||||
) {
|
||||
return page
|
||||
.locator('.widget-slot')
|
||||
.filter({ hasText: widgetTitle })
|
||||
.first()
|
||||
.locator('.activity-item')
|
||||
.filter({ hasText: title })
|
||||
.first();
|
||||
}
|
||||
|
||||
async function goBackFromDetail(page: Page): Promise<void> {
|
||||
const backButton = page.locator('app-content-hero .hero__back-button').first();
|
||||
|
||||
await expect(backButton).toBeVisible({ timeout: 20000 });
|
||||
await backButton.click();
|
||||
}
|
||||
|
||||
async function playCurrentDetail(page: Page): Promise<void> {
|
||||
const playButton = page.locator('button.play-btn').first();
|
||||
|
||||
await expect(playButton).toBeVisible({ timeout: 20000 });
|
||||
await playButton.click();
|
||||
}
|
||||
|
||||
async function playFirstSeriesEpisode(page: Page): Promise<void> {
|
||||
const seasonCard = page.locator('.season-card').first();
|
||||
const episodeCard = page.locator('.episode-card, .episode-list-item').first();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await seasonCard.count()) + (await episodeCard.count()),
|
||||
{ timeout: 20000 }
|
||||
)
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
if ((await seasonCard.count()) > 0) {
|
||||
await seasonCard.scrollIntoViewIfNeeded();
|
||||
await expect(seasonCard).toBeVisible({ timeout: 20000 });
|
||||
await seasonCard.click();
|
||||
}
|
||||
|
||||
await episodeCard.scrollIntoViewIfNeeded();
|
||||
await expect(episodeCard).toBeVisible({ timeout: 20000 });
|
||||
await episodeCard.click();
|
||||
}
|
||||
|
||||
async function toggleFavoriteForChannel(page: Page, title: string): Promise<void> {
|
||||
const item = page
|
||||
.locator('[data-test-id="channel-item"]')
|
||||
.filter({ hasText: title })
|
||||
.first();
|
||||
|
||||
await expect(item).toBeVisible({ timeout: 20000 });
|
||||
await item.hover();
|
||||
await item.locator('.favorite-button').first().click();
|
||||
await expect(item.locator('.favorite-button mat-icon').first()).toHaveText(/star/);
|
||||
}
|
||||
@@ -279,6 +279,70 @@ test.describe('Electron Favorites', () => {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('opens Stalker favorite movies and series in source detail routes and returns back to favorites', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['stalker']);
|
||||
const vodFixture = await fetchStalkerCategoryFixture(request, 'vod');
|
||||
const seriesFixture = await fetchStalkerCategoryFixture(request, 'series');
|
||||
const [movieTitle] = pickDistinctTitles(vodFixture.items, getStalkerTitle);
|
||||
const [seriesTitle] = pickDistinctTitles(seriesFixture.items, getStalkerTitle);
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addStalkerPortal(app.mainWindow, {
|
||||
name: 'Stalker Favorite Detail Source',
|
||||
});
|
||||
await waitForStalkerCatalog(app.mainWindow);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
|
||||
await clickCategoryById(app.mainWindow, vodFixture.categoryId);
|
||||
await clickGridListCardByTitle(app.mainWindow, movieTitle);
|
||||
await addCurrentDetailToFavorites(app.mainWindow);
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
|
||||
await clickCategoryById(app.mainWindow, seriesFixture.categoryId);
|
||||
await clickGridListCardByTitle(app.mainWindow, seriesTitle);
|
||||
await addCurrentDetailToFavorites(app.mainWindow);
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
|
||||
await openPlaylistFavorites(app.mainWindow);
|
||||
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
|
||||
await contentCardByTitle(app.mainWindow, movieTitle).first().click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/stalker\/[^/]+\/vod\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
movieTitle
|
||||
);
|
||||
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
await expect
|
||||
.poll(() => new URL(app.mainWindow.url()).pathname)
|
||||
.toMatch(/\/favorites$/);
|
||||
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
|
||||
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
|
||||
await contentCardByTitle(app.mainWindow, seriesTitle).first().click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/stalker\/[^/]+\/series\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
seriesTitle
|
||||
);
|
||||
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
await expect
|
||||
.poll(() => new URL(app.mainWindow.url()).pathname)
|
||||
.toMatch(/\/favorites$/);
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const xtreamCredentials = {
|
||||
|
||||
@@ -41,6 +41,46 @@ import {
|
||||
} from './portal-mock-fixtures';
|
||||
|
||||
test.describe('Electron Recently Viewed', () => {
|
||||
test('keeps unified live detail open when re-clicking the active M3U recent item', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const filePath = writeTemporaryM3uFile(dataDir, 'm3u-live-reclick.m3u', [
|
||||
{
|
||||
groupTitle: 'Live',
|
||||
name: 'Stable Recent Channel',
|
||||
url: 'https://streams.example.test/stable-recent.m3u8',
|
||||
},
|
||||
]);
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await importM3uPlaylistFromNativeDialog(app, filePath);
|
||||
await waitForM3uCatalog(app.mainWindow);
|
||||
|
||||
await channelItemByTitle(app.mainWindow, 'Stable Recent Channel')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await openPlaylistRecent(app.mainWindow);
|
||||
|
||||
const item = channelItemByTitle(
|
||||
app.mainWindow,
|
||||
'Stable Recent Channel'
|
||||
).first();
|
||||
const closeButton = app.mainWindow
|
||||
.locator('.player-toolbar .close-btn')
|
||||
.first();
|
||||
|
||||
await item.click();
|
||||
await expect(closeButton).toBeVisible({ timeout: 20000 });
|
||||
|
||||
await item.click();
|
||||
await expect(closeButton).toBeVisible({ timeout: 20000 });
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('tracks M3U recent channels in newest-first order, supports all-playlists scope, and persists removals after restart', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
@@ -201,6 +241,77 @@ test.describe('Electron Recently Viewed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('opens Xtream recent movies and series in source detail routes and returns back to recent', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
const vodFixture = await fetchXtreamVodFixture(request, xtreamCredentials);
|
||||
const seriesFixture = await fetchXtreamSeriesFixture(
|
||||
request,
|
||||
xtreamCredentials
|
||||
);
|
||||
const portalTitle = 'Xtream Recent Detail Source';
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: portalTitle,
|
||||
});
|
||||
await waitForXtreamWorkspaceReady(app.mainWindow);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
vodFixture.categoryName
|
||||
);
|
||||
const movieTitle = await clickFirstGridListCard(app.mainWindow);
|
||||
await playCurrentDetail(app.mainWindow);
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
|
||||
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
seriesFixture.categoryName
|
||||
);
|
||||
const seriesTitle = await clickFirstGridListCard(app.mainWindow);
|
||||
await playFirstSeriesEpisode(app.mainWindow);
|
||||
|
||||
await openPlaylistRecent(app.mainWindow);
|
||||
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
|
||||
await contentCardByTitle(app.mainWindow, movieTitle).first().click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/vod\/[^/]+\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
movieTitle
|
||||
);
|
||||
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
await expect
|
||||
.poll(() => new URL(app.mainWindow.url()).pathname)
|
||||
.toMatch(/\/recent$/);
|
||||
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
|
||||
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
|
||||
await contentCardByTitle(app.mainWindow, seriesTitle).first().click();
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/series\/[^/]+\/[^/]+$/);
|
||||
await expect(app.mainWindow.locator('app-content-hero')).toContainText(
|
||||
seriesTitle
|
||||
);
|
||||
|
||||
await goBackFromDetail(app.mainWindow);
|
||||
await expect
|
||||
.poll(() => new URL(app.mainWindow.url()).pathname)
|
||||
.toMatch(/\/recent$/);
|
||||
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
|
||||
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('tracks Stalker live, movie, and series history across playlist and all-playlists scope, and preserves it after restart', async ({
|
||||
dataDir,
|
||||
request,
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { Page } from '@playwright/test';
|
||||
import {
|
||||
addXtreamPortal,
|
||||
clickCategoryByNameExact,
|
||||
clickFirstGridListCard,
|
||||
closeElectronApp,
|
||||
createMutableTextServer,
|
||||
enableRemoteControl,
|
||||
expect,
|
||||
launchElectronApp,
|
||||
openGlobalRecent,
|
||||
openSettings,
|
||||
openWorkspaceSection,
|
||||
resetMockServers,
|
||||
restartElectronApp,
|
||||
saveSettings,
|
||||
test,
|
||||
waitForXtreamWorkspaceReady,
|
||||
} from './electron-test-fixtures';
|
||||
import {
|
||||
defaultXtreamPassword,
|
||||
defaultXtreamUsername,
|
||||
} from './electron-test-fixtures';
|
||||
import { fetchXtreamVodFixture } from './portal-mock-fixtures';
|
||||
|
||||
const epgFixtureXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<tv>
|
||||
@@ -108,6 +121,111 @@ test.describe('Electron Settings', () => {
|
||||
await epgServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('starts on sources when dashboard is disabled', async ({ dataDir }) => {
|
||||
const firstLaunch = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(firstLaunch.mainWindow);
|
||||
await firstLaunch.mainWindow
|
||||
.locator(
|
||||
'mat-checkbox[formcontrolname="showDashboard"] input[type="checkbox"]'
|
||||
)
|
||||
.uncheck();
|
||||
await saveSettings(firstLaunch.mainWindow);
|
||||
} finally {
|
||||
await closeElectronApp(firstLaunch);
|
||||
}
|
||||
|
||||
const secondLaunch = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await secondLaunch.mainWindow.waitForURL(/\/workspace\/sources$/);
|
||||
await expect(
|
||||
secondLaunch.mainWindow.getByRole('link', {
|
||||
name: 'Dashboard',
|
||||
exact: true,
|
||||
})
|
||||
).toHaveCount(0);
|
||||
await expect(secondLaunch.mainWindow.locator('a.brand')).toHaveAttribute(
|
||||
'href',
|
||||
/\/workspace\/sources$/
|
||||
);
|
||||
} finally {
|
||||
await closeElectronApp(secondLaunch);
|
||||
}
|
||||
});
|
||||
|
||||
test('restores the last section-level view across restart when configured', async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
let app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(app.mainWindow);
|
||||
await selectSettingsOption(
|
||||
app.mainWindow,
|
||||
'select-startup-behavior',
|
||||
'restore-last-view'
|
||||
);
|
||||
await saveSettings(app.mainWindow);
|
||||
|
||||
await openGlobalRecent(app.mainWindow);
|
||||
} finally {
|
||||
app = await restartElectronApp(app, dataDir);
|
||||
}
|
||||
|
||||
try {
|
||||
await app.mainWindow.waitForURL(/\/workspace\/global-recent$/);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('ignores settings and restores only the section root after a detail route', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
const vodFixture = await fetchXtreamVodFixture(request, {
|
||||
password: defaultXtreamPassword,
|
||||
username: defaultXtreamUsername,
|
||||
});
|
||||
let app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await openSettings(app.mainWindow);
|
||||
await selectSettingsOption(
|
||||
app.mainWindow,
|
||||
'select-startup-behavior',
|
||||
'restore-last-view'
|
||||
);
|
||||
await saveSettings(app.mainWindow);
|
||||
|
||||
await addXtreamPortal(app.mainWindow);
|
||||
await waitForXtreamWorkspaceReady(app.mainWindow);
|
||||
|
||||
await openWorkspaceSection(app.mainWindow, 'Movies');
|
||||
await clickCategoryByNameExact(
|
||||
app.mainWindow,
|
||||
vodFixture.categoryName
|
||||
);
|
||||
await clickFirstGridListCard(app.mainWindow);
|
||||
await app.mainWindow.waitForURL(
|
||||
/\/workspace\/xtreams\/[^/]+\/vod\/[^/]+\/[^/]+$/
|
||||
);
|
||||
|
||||
await openSettings(app.mainWindow);
|
||||
} finally {
|
||||
app = await restartElectronApp(app, dataDir);
|
||||
}
|
||||
|
||||
try {
|
||||
await app.mainWindow.waitForURL(/\/workspace\/xtreams\/[^/]+\/vod$/);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function selectSettingsOption(
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
waitForM3uCatalog,
|
||||
waitForPortalDebugEvent,
|
||||
waitForXtreamCatalog,
|
||||
waitForSourceRowIdle,
|
||||
writeTemporaryM3uFile,
|
||||
} from './electron-test-fixtures';
|
||||
|
||||
@@ -447,6 +448,13 @@ https://streams.example.test/refreshed-url.m3u8
|
||||
provider: 'xtream',
|
||||
timeoutMs: 30000,
|
||||
});
|
||||
await openSources(app.mainWindow);
|
||||
await waitForSourceRowIdle(app.mainWindow, 'Refresh Xtream Source');
|
||||
await expect(
|
||||
sourceRowByTitle(app.mainWindow, 'Refresh Xtream Source')
|
||||
.first()
|
||||
.locator('.meta')
|
||||
).toContainText('Updated:');
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
await urlServer.close();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
test,
|
||||
waitForDbOperationEvent,
|
||||
waitForXtreamCatalog,
|
||||
xtreamMockServer,
|
||||
} from './electron-test-fixtures';
|
||||
|
||||
const stressPortalName = 'Stress Xtream Portal';
|
||||
@@ -25,6 +26,10 @@ async function openSources(page: Parameters<typeof addXtreamPortal>[0]) {
|
||||
await page.waitForURL(/\/workspace\/sources$/);
|
||||
}
|
||||
|
||||
function xtreamBlockedStateTitle(page: Parameters<typeof addXtreamPortal>[0]) {
|
||||
return page.locator('.xtream-content-gate .error-view__title');
|
||||
}
|
||||
|
||||
test.describe('Electron Xtream Responsiveness', () => {
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
test.setTimeout(120000);
|
||||
@@ -101,6 +106,139 @@ test.describe('Electron Xtream Responsiveness', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('stops importing after cancel until the user explicitly retries', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
|
||||
const app = await launchElectronApp(dataDir, {
|
||||
env: {
|
||||
IPTVNATOR_DB_WORKER_BATCH_DELAY_MS: dbWorkerBatchDelayMs,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: stressPortalName,
|
||||
password: stressXtreamPassword,
|
||||
username: stressXtreamUsername,
|
||||
});
|
||||
|
||||
const overlay = app.mainWindow.locator('.workspace-loading-overlay');
|
||||
const cancelButton = overlay.getByRole('button', {
|
||||
name: /cancel import/i,
|
||||
});
|
||||
|
||||
await expect(overlay).toBeVisible({ timeout: 15000 });
|
||||
await expect(cancelButton).toBeVisible({ timeout: 20000 });
|
||||
|
||||
await waitForDbOperationEvent(app.mainWindow, {
|
||||
operation: 'save-content',
|
||||
predicate: (event) =>
|
||||
event.status === 'progress' &&
|
||||
(event.current ?? 0) > 0 &&
|
||||
(event.total ?? 0) > 0,
|
||||
timeoutMs: 30000,
|
||||
});
|
||||
|
||||
await cancelButton.click();
|
||||
|
||||
await expect(overlay).toHaveCount(0, { timeout: 20000 });
|
||||
await expect(
|
||||
xtreamBlockedStateTitle(app.mainWindow)
|
||||
).toHaveText('Import cancelled', { timeout: 20000 });
|
||||
|
||||
await app.mainWindow.waitForTimeout(1200);
|
||||
await expect(overlay).toHaveCount(0);
|
||||
|
||||
const retryButton = app.mainWindow.getByRole('button', {
|
||||
name: 'Retry',
|
||||
exact: true,
|
||||
});
|
||||
await retryButton.click();
|
||||
|
||||
await waitForXtreamCatalog(app.mainWindow);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('blocks expired and inactive Xtream accounts before import starts', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: 'Expired Xtream Portal',
|
||||
password: 'expired',
|
||||
username: 'expired',
|
||||
});
|
||||
|
||||
await expect(xtreamBlockedStateTitle(app.mainWindow)).toHaveText(
|
||||
'Account expired',
|
||||
{ timeout: 20000 }
|
||||
);
|
||||
await expect(
|
||||
app.mainWindow.locator('.workspace-loading-overlay')
|
||||
).toHaveCount(0);
|
||||
|
||||
await openSources(app.mainWindow);
|
||||
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: 'Inactive Xtream Portal',
|
||||
password: 'inactive',
|
||||
username: 'inactive',
|
||||
});
|
||||
|
||||
await expect(xtreamBlockedStateTitle(app.mainWindow)).toHaveText(
|
||||
'Account inactive',
|
||||
{ timeout: 20000 }
|
||||
);
|
||||
await expect(
|
||||
app.mainWindow.locator('.workspace-loading-overlay')
|
||||
).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('blocks unreachable Xtream portals immediately without an import loop', async ({
|
||||
dataDir,
|
||||
request,
|
||||
}) => {
|
||||
await resetMockServers(request, ['xtream']);
|
||||
|
||||
const app = await launchElectronApp(dataDir);
|
||||
|
||||
try {
|
||||
await addXtreamPortal(app.mainWindow, {
|
||||
name: 'Unavailable Xtream Portal',
|
||||
password: stressXtreamPassword,
|
||||
serverUrl: xtreamMockServer.replace(/:\d+$/, ':65530'),
|
||||
username: stressXtreamUsername,
|
||||
});
|
||||
|
||||
await expect(xtreamBlockedStateTitle(app.mainWindow)).toHaveText(
|
||||
'Portal unavailable',
|
||||
{ timeout: 20000 }
|
||||
);
|
||||
await expect(
|
||||
app.mainWindow.locator('.workspace-loading-overlay')
|
||||
).toHaveCount(0);
|
||||
await app.mainWindow.waitForTimeout(1200);
|
||||
await expect(
|
||||
app.mainWindow.locator('.workspace-loading-overlay')
|
||||
).toHaveCount(0);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps the UI responsive during large Xtream playlist deletion', async ({
|
||||
dataDir,
|
||||
request,
|
||||
|
||||
Reference in new issue
Block a user