test(e2e): streamline expect statements and improve inline detail tests for favorites and recent items

- Simplified expect statements for channel items and content cards in favorites and recent tests.
- Added new tests for opening Xtream favorite movies and series inline from favorites without switching playlists.
- Enhanced existing tests to ensure consistent behavior when navigating back from detail views.
- Introduced utility function expectInlineCollectionDetail to reduce code duplication in inline detail checks.
- Improved error handling for back button clicks in detail views.

Entire-Checkpoint: c6e522b4276c
This commit is contained in:
4gray committed 2026-04-18 10:17:13 +02:00
1 parent 2edb5fe296
commit 7f92dd359c
4 files changed
+754 -182

No files matched your search

@@ -6,6 +6,7 @@ import {
closeElectronApp,
defaultXtreamPassword,
defaultXtreamUsername,
expectPathname,
expect,
goToDashboard,
launchElectronApp,
@@ -23,7 +24,7 @@ import {
} 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 ({
test('opens live favorites in the collection route and movies/series in global collection detail views from the dashboard', async ({
dataDir,
request,
}) => {
@@ -67,6 +68,7 @@ test.describe('Dashboard Activation', () => {
vodFixture.categoryName
);
const movieTitle = await clickFirstGridListCard(app.mainWindow);
await addCurrentDetailToFavorites(app.mainWindow);
await playCurrentDetail(app.mainWindow);
await goBackFromDetail(app.mainWindow);
@@ -97,32 +99,44 @@ test.describe('Dashboard Activation', () => {
await app.mainWindow.goBack();
await app.mainWindow.waitForURL(/\/workspace\/dashboard$/);
await dashboardActivityItemByTitle(
app.mainWindow,
'Global Favorites',
movieTitle
).click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/global-favorites$/,
title: movieTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/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 expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/global-recent$/,
title: movieTitle,
});
await app.mainWindow.goBack();
await app.mainWindow.waitForURL(/\/workspace\/dashboard$/);
await expectPathname(app.mainWindow, /\/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);
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/global-recent$/,
title: seriesTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(app.mainWindow, /\/workspace\/dashboard$/);
} finally {
await closeElectronApp(app);
}
@@ -154,7 +168,40 @@ async function goBackFromDetail(page: Page): Promise<void> {
.first();
await expect(backButton).toBeVisible({ timeout: 20000 });
await backButton.click();
try {
await backButton.click({ timeout: 5000 });
} catch {
await backButton.evaluate((button: HTMLButtonElement) =>
button.click()
);
}
}
async function addCurrentDetailToFavorites(page: Page): Promise<void> {
const addButton = page.locator('button.favorite-btn').first();
await expect(addButton).toBeVisible({ timeout: 20000 });
await addButton.click();
await expect(
page.locator('button.favorite-btn--active').first()
).toBeVisible({
timeout: 20000,
});
}
async function expectInlineCollectionDetail(
page: Page,
params: {
pathname: RegExp;
title: string;
}
): Promise<void> {
await expectPathname(page, params.pathname);
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
).toBeVisible({ timeout: 20000 });
}
async function playCurrentDetail(page: Page): Promise<void> {
@@ -195,6 +195,13 @@ async function waitForAppReady(page: Page): Promise<void> {
export async function openAddPlaylistDialog(page: Page): Promise<void> {
await page.getByRole('button', { name: 'Add playlist' }).click();
await expect(page.locator('mat-dialog-container').last()).toBeVisible();
}
async function getActiveDialog(page: Page): Promise<Locator> {
const dialog = page.locator('mat-dialog-container').last();
await expect(dialog).toBeVisible();
return dialog;
}
export async function stubNativePlaylistFileDialog(
@@ -214,15 +221,22 @@ export async function importM3uPlaylistFromNativeDialog(
app: LaunchedElectronApp,
filePath: string
): Promise<void> {
await stubNativePlaylistFileDialog(app.electronApp, filePath);
await openAddPlaylistDialog(app.mainWindow);
await app.mainWindow
.locator('mat-dialog-container mat-button-toggle[value="file"]')
.click();
await app.mainWindow.locator('mat-dialog-container .file-upload').click();
await app.mainWindow.waitForSelector('mat-dialog-container', {
state: 'detached',
});
const dialog = await getActiveDialog(app.mainWindow);
await clickDialogCategoryOption(dialog, /^m3u$/i);
await clickDialogSubtypeOption(
dialog,
/add\s+via\s+file\s+upload/i,
'mat-button-toggle[value="file"]'
);
await dialog
.locator('input[type="file"][name="playlist"]')
.setInputFiles(filePath);
await expect(
dialog.getByRole('button', { name: /add playlist/i })
).toBeEnabled({ timeout: 10000 });
await dialog.getByRole('button', { name: /add playlist/i }).click();
await dialog.waitFor({ state: 'detached' });
}
export async function addXtreamPortal(
@@ -242,18 +256,24 @@ export async function addXtreamPortal(
} = options;
await openAddPlaylistDialog(page);
const dialog = page.locator('mat-dialog-container');
await dialog.locator('mat-button-toggle[value="xtream"]').click();
const dialog = await getActiveDialog(page);
await clickDialogCategoryOption(
dialog,
/^xtream$/i,
'mat-button-toggle[value="xtream"]'
);
await setInputValue(dialog.locator('#title'), name);
await setInputValue(dialog.locator('#serverUrl'), serverUrl);
await setInputValue(dialog.locator('#username'), username);
await setInputValue(dialog.locator('#password'), password);
const addButton = dialog.getByRole('button', { name: 'Add', exact: true });
const addButton = dialog
.getByRole('button', { name: /^(add|add playlist)$/i })
.last();
await expect(addButton).toBeEnabled({ timeout: 10000 });
await addButton.click();
await page.waitForSelector('mat-dialog-container', { state: 'detached' });
await dialog.waitFor({ state: 'detached' });
}
async function setInputValue(input: Locator, value: string): Promise<void> {
@@ -284,6 +304,80 @@ async function setInputValue(input: Locator, value: string): Promise<void> {
await expect(input).toHaveValue(value);
}
async function clickDialogCategoryOption(
dialog: Locator,
label: RegExp,
legacySelector?: string
): Promise<void> {
await clickDialogSegmentedOption(
dialog,
'Playlist category',
label,
legacySelector
);
}
async function clickDialogSubtypeOption(
dialog: Locator,
label: RegExp,
legacySelector?: string
): Promise<void> {
await clickDialogSegmentedOption(
dialog,
'M3U source',
label,
legacySelector
);
}
async function clickDialogSegmentedOption(
dialog: Locator,
tablistLabel: string,
label: RegExp,
legacySelector?: string
): Promise<void> {
const tablist = dialog
.locator(`[role="tablist"][aria-label="${tablistLabel}"]`)
.first();
if ((await tablist.count()) > 0) {
const optionByTabRole = tablist
.getByRole('tab', { name: label })
.first();
if ((await optionByTabRole.count()) > 0) {
await optionByTabRole.click();
return;
}
}
const optionByGlobalTabRole = dialog
.getByRole('tab', { name: label })
.first();
if ((await optionByGlobalTabRole.count()) > 0) {
await optionByGlobalTabRole.click();
return;
}
const optionByButtonRole = dialog
.getByRole('button', { name: label })
.first();
if ((await optionByButtonRole.count()) > 0) {
await optionByButtonRole.click();
return;
}
if (!legacySelector) {
throw new Error(
`Could not find dialog option matching ${label} in "${tablistLabel}".`
);
}
await dialog.locator(legacySelector).click();
}
export async function addStalkerPortal(
page: Page,
options: {
@@ -299,17 +393,23 @@ export async function addStalkerPortal(
} = options;
await openAddPlaylistDialog(page);
const dialog = page.locator('mat-dialog-container');
await dialog.locator('mat-button-toggle[value="stalker"]').click();
const dialog = await getActiveDialog(page);
await clickDialogCategoryOption(
dialog,
/^stalker$/i,
'mat-button-toggle[value="stalker"]'
);
await setInputValue(dialog.locator('input#title'), name);
await setInputValue(dialog.locator('input#portalUrl'), portalUrl);
await setInputValue(dialog.locator('input#macAddress'), macAddress);
const addButton = dialog.getByRole('button', { name: 'Add', exact: true });
const addButton = dialog
.getByRole('button', { name: /^(add|add playlist)$/i })
.last();
await expect(addButton).toBeEnabled({ timeout: 10000 });
await addButton.click();
await page.waitForSelector('mat-dialog-container', { state: 'detached' });
await dialog.waitFor({ state: 'detached' });
}
export async function openSettings(page: Page): Promise<void> {
@@ -367,15 +467,20 @@ export async function importM3uPlaylistFromUrl(
playlistUrl: string
): Promise<void> {
await openAddPlaylistDialog(page);
const dialog = page.locator('mat-dialog-container');
await dialog.locator('mat-button-toggle[value="url"]').click();
const dialog = await getActiveDialog(page);
await clickDialogCategoryOption(dialog, /^m3u$/i);
await clickDialogSubtypeOption(
dialog,
/add\s+via\s+url/i,
'mat-button-toggle[value="url"]'
);
await setInputValue(
dialog.locator('input[formcontrolname="playlistUrl"]'),
playlistUrl
);
await dialog.getByRole('button', { name: /Add playlist/i }).click();
await page.waitForSelector('mat-dialog-container', { state: 'detached' });
await dialog.waitFor({ state: 'detached' });
}
export function buildM3uContent(channels: M3uTestChannel[]): string {
@@ -437,8 +542,7 @@ export function parseM3uFixture(filePath: string): M3uTestChannel[] {
logo: line.match(/tvg-logo="([^"]*)"/)?.[1]?.trim() ?? '',
name: line.split(',').at(-1)?.trim() ?? '',
tvgId: line.match(/tvg-id="([^"]*)"/)?.[1]?.trim() ?? '',
tvgName:
line.match(/tvg-name="([^"]*)"/)?.[1]?.trim() ?? '',
tvgName: line.match(/tvg-name="([^"]*)"/)?.[1]?.trim() ?? '',
};
continue;
}
@@ -520,9 +624,7 @@ export async function openPlaylistFavorites(page: Page): Promise<void> {
export async function openPlaylistRecent(page: Page): Promise<void> {
await openWorkspaceSection(page, 'Recently viewed');
await expect
.poll(() => new URL(page.url()).pathname)
.toMatch(/\/recent$/);
await expect.poll(() => new URL(page.url()).pathname).toMatch(/\/recent$/);
}
export async function openGlobalFavorites(page: Page): Promise<void> {
@@ -539,9 +641,7 @@ export async function openGlobalRecent(page: Page): Promise<void> {
const dialog = await openCommandPalette(page);
await dialog.locator('input[type="search"]').fill('recent');
await dialog
.getByRole('button', { name: /Open recently viewed/i })
.click();
await dialog.getByRole('button', { name: /Open recently viewed/i }).click();
await page.waitForURL(/\/workspace\/global-recent(?:\?.*)?$/);
}
@@ -622,6 +722,19 @@ export async function expectVisibleContentCardTitle(
.toBe(true);
}
export async function expectPathname(
page: Page,
pattern: RegExp
): Promise<void> {
await expect.poll(() => new URL(page.url()).pathname).toMatch(pattern);
}
export function playlistSwitcherTitle(page: Page): Locator {
return page
.locator('.playlist-switcher-trigger .playlist-info .name')
.first();
}
export function gridListCardByTitle(page: Page, title: string): Locator {
return page.locator('.category-content-layout mat-card').filter({
has: page.locator('.title', {
@@ -635,7 +748,9 @@ export function gridListCardByTitle(page: Page, title: string): Locator {
* its display title. Use this instead of picking from fixture order, since the
* grid sorts by date-desc and may paginate items off the first page.
*/
export async function waitForFirstGridListCardTitle(page: Page): Promise<string> {
export async function waitForFirstGridListCardTitle(
page: Page
): Promise<string> {
const card = page.locator('.category-content-layout mat-card').first();
await expect(card).toBeVisible({ timeout: 20000 });
return ((await card.locator('.title').textContent()) ?? '').trim();
@@ -787,7 +902,9 @@ export async function dragSourceBefore(
targetTitle: string
): Promise<void> {
const source = sourceRowByTitle(page, sourceTitle).locator('.drag-icon');
const target = sourceRowByTitle(page, targetTitle).locator('.playlist-item');
const target = sourceRowByTitle(page, targetTitle).locator(
'.playlist-item'
);
await expect(source.first()).toBeVisible();
await expect(target.first()).toBeVisible();
@@ -930,10 +1047,7 @@ export async function expectSourceDialogValues(
}
}
export async function deleteSource(
page: Page,
title: string
): Promise<void> {
export async function deleteSource(page: Page, title: string): Promise<void> {
const row = sourceRowByTitle(page, title).first();
await expect(row).toBeVisible();
@@ -1011,9 +1125,12 @@ export async function waitForXtreamWorkspaceReady(page: Page): Promise<void> {
export async function expectPlaylistUpdatedToast(page: Page): Promise<void> {
await expect(
page.locator('.mat-mdc-snack-bar-label').filter({
hasText: 'Success! The playlist was successfully updated.',
}).last()
page
.locator('.mat-mdc-snack-bar-label')
.filter({
hasText: 'Success! The playlist was successfully updated.',
})
.last()
).toBeVisible({
timeout: 20000,
});
@@ -1364,7 +1481,9 @@ async function confirmDialog(page: Page, buttonLabel = 'Yes'): Promise<void> {
const dialog = page.locator('mat-dialog-container');
await expect(dialog).toBeVisible();
await dialog.getByRole('button', { name: buttonLabel, exact: true }).click();
await dialog
.getByRole('button', { name: buttonLabel, exact: true })
.click();
await page.waitForSelector('mat-dialog-container', { state: 'detached' });
}
+273 -72
View File
@@ -11,6 +11,7 @@ import {
contentCardByTitle,
defaultXtreamPassword,
defaultXtreamUsername,
expectPathname,
expectVisibleContentCardTitle,
expect,
importM3uPlaylistFromNativeDialog,
@@ -24,6 +25,7 @@ import {
switchUnifiedCollectionContent,
switchUnifiedCollectionScope,
test,
playlistSwitcherTitle,
waitForM3uCatalog,
waitForStalkerCatalog,
waitForXtreamWorkspaceReady,
@@ -66,20 +68,20 @@ test.describe('Electron Favorites', () => {
await toggleFavoriteForChannel(app.mainWindow, 'M3U Favorite Two');
await openPlaylistFavorites(app.mainWindow);
await expect(channelItemByTitle(app.mainWindow, 'M3U Favorite One')).toHaveCount(
1
);
await expect(channelItemByTitle(app.mainWindow, 'M3U Favorite Two')).toHaveCount(
1
);
await expect(
channelItemByTitle(app.mainWindow, 'M3U Favorite One')
).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, 'M3U Favorite Two')
).toHaveCount(1);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await expect(channelItemByTitle(app.mainWindow, 'M3U Favorite One')).toHaveCount(
1
);
await expect(channelItemByTitle(app.mainWindow, 'M3U Favorite Two')).toHaveCount(
1
);
await expect(
channelItemByTitle(app.mainWindow, 'M3U Favorite One')
).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, 'M3U Favorite Two')
).toHaveCount(1);
const restarted = await restartElectronApp(app, dataDir);
app.electronApp = restarted.electronApp;
@@ -91,10 +93,7 @@ test.describe('Electron Favorites', () => {
.click();
await waitForM3uCatalog(app.mainWindow);
await openPlaylistFavorites(app.mainWindow);
await switchUnifiedCollectionScope(
app.mainWindow,
'All playlists'
);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await expect(
channelItemByTitle(app.mainWindow, 'M3U Favorite One')
).toHaveCount(1);
@@ -111,13 +110,22 @@ test.describe('Electron Favorites', () => {
request,
}) => {
await resetMockServers(request, ['xtream']);
const liveFixture = await fetchXtreamLiveFixture(request, xtreamCredentials);
const vodFixture = await fetchXtreamVodFixture(request, xtreamCredentials);
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 [liveTitle] = pickDistinctTitles(
liveFixture.items,
getXtreamTitle
);
const portalTitle = 'Xtream Favorites Source';
const app = await launchElectronApp(dataDir);
@@ -134,7 +142,9 @@ test.describe('Electron Favorites', () => {
);
await toggleFavoriteForChannel(app.mainWindow, liveTitle);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Movies', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
vodFixture.categoryName
@@ -147,7 +157,9 @@ test.describe('Electron Favorites', () => {
await addCurrentDetailToFavorites(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Series', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
seriesFixture.categoryName
@@ -157,7 +169,9 @@ test.describe('Electron Favorites', () => {
await goBackFromDetail(app.mainWindow);
await openPlaylistFavorites(app.mainWindow);
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
@@ -167,7 +181,9 @@ test.describe('Electron Favorites', () => {
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -181,10 +197,7 @@ test.describe('Electron Favorites', () => {
await sourceRowByTitle(app.mainWindow, portalTitle).first().click();
await waitForXtreamWorkspaceReady(app.mainWindow);
await openPlaylistFavorites(app.mainWindow);
await switchUnifiedCollectionScope(
app.mainWindow,
'All playlists'
);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
@@ -198,6 +211,108 @@ test.describe('Electron Favorites', () => {
}
});
test('opens Xtream favorite movies and series inline from favorites without switching the playlist or showing the sidebar', async ({
dataDir,
request,
}) => {
await resetMockServers(request, ['xtream']);
const vodFixture = await fetchXtreamVodFixture(
request,
xtreamCredentials
);
const seriesFixture = await fetchXtreamSeriesFixture(
request,
xtreamCredentials
);
const hostPortalTitle = 'Xtream Favorite Detail Host';
const sourcePortalTitle = 'Xtream Favorite Detail Source';
const app = await launchElectronApp(dataDir);
try {
await addXtreamPortal(app.mainWindow, {
name: hostPortalTitle,
});
await waitForXtreamWorkspaceReady(app.mainWindow);
await addXtreamPortal(app.mainWindow, {
name: sourcePortalTitle,
});
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 addCurrentDetailToFavorites(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 addCurrentDetailToFavorites(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await openSources(app.mainWindow);
await sourceRowByTitle(app.mainWindow, hostPortalTitle)
.first()
.click();
await waitForXtreamWorkspaceReady(app.mainWindow);
await openPlaylistFavorites(app.mainWindow);
await expect(playlistSwitcherTitle(app.mainWindow)).toContainText(
hostPortalTitle
);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await contentCardByTitle(app.mainWindow, movieTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/xtreams\/[^/]+\/favorites$/,
title: movieTitle,
playlistTitle: hostPortalTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(
app.mainWindow,
/\/workspace\/xtreams\/[^/]+\/favorites$/
);
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
await contentCardByTitle(app.mainWindow, seriesTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/xtreams\/[^/]+\/favorites$/,
title: seriesTitle,
playlistTitle: hostPortalTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(
app.mainWindow,
/\/workspace\/xtreams\/[^/]+\/favorites$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
await closeElectronApp(app);
}
});
test('shows Stalker live, movie, and series favorites in playlist and all-playlists scope, and preserves them after restart', async ({
dataDir,
request,
@@ -205,10 +320,22 @@ test.describe('Electron Favorites', () => {
await resetMockServers(request, ['stalker']);
const liveFixture = await fetchStalkerCategoryFixture(request, 'itv');
const vodFixture = await fetchStalkerCategoryFixture(request, 'vod');
const seriesFixture = await fetchStalkerCategoryFixture(request, 'series');
const [liveTitle] = pickDistinctTitles(liveFixture.items, getStalkerTitle);
const [movieTitle] = pickDistinctTitles(vodFixture.items, getStalkerTitle);
const [seriesTitle] = pickDistinctTitles(seriesFixture.items, getStalkerTitle);
const seriesFixture = await fetchStalkerCategoryFixture(
request,
'series'
);
const [liveTitle] = pickDistinctTitles(
liveFixture.items,
getStalkerTitle
);
const [movieTitle] = pickDistinctTitles(
vodFixture.items,
getStalkerTitle
);
const [seriesTitle] = pickDistinctTitles(
seriesFixture.items,
getStalkerTitle
);
const portalTitle = 'Stalker Favorites Source';
const app = await launchElectronApp(dataDir);
@@ -218,38 +345,56 @@ test.describe('Electron Favorites', () => {
});
await waitForStalkerCatalog(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Live TV', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Live TV', exact: true })
.click();
await clickCategoryById(app.mainWindow, liveFixture.categoryId);
await toggleFavoriteForChannel(app.mainWindow, liveTitle);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
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 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 expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(1);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(1);
const restarted = await restartElectronApp(app, dataDir);
app.electronApp = restarted.electronApp;
@@ -259,10 +404,7 @@ test.describe('Electron Favorites', () => {
await sourceRowByTitle(app.mainWindow, portalTitle).first().click();
await waitForStalkerCatalog(app.mainWindow);
await openPlaylistFavorites(app.mainWindow);
await switchUnifiedCollectionScope(
app.mainWindow,
'All playlists'
);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
@@ -280,63 +422,88 @@ test.describe('Electron Favorites', () => {
}
});
test('opens Stalker favorite movies and series in source detail routes and returns back to favorites', async ({
test('opens Stalker favorite movies and series inline from favorites without showing the sidebar', 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 seriesFixture = await fetchStalkerCategoryFixture(
request,
'series'
);
const [movieTitle] = pickDistinctTitles(
vodFixture.items,
getStalkerTitle
);
const [seriesTitle] = pickDistinctTitles(
seriesFixture.items,
getStalkerTitle
);
const portalTitle = 'Stalker Favorite Detail Source';
const app = await launchElectronApp(dataDir);
try {
await addStalkerPortal(app.mainWindow, {
name: 'Stalker Favorite Detail Source',
name: portalTitle,
});
await waitForStalkerCatalog(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
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 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 expect(playlistSwitcherTitle(app.mainWindow)).toContainText(
portalTitle
);
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 contentCardByTitle(app.mainWindow, movieTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/stalker\/[^/]+\/favorites$/,
title: movieTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expect
.poll(() => new URL(app.mainWindow.url()).pathname)
.toMatch(/\/favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/stalker\/[^/]+\/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 contentCardByTitle(app.mainWindow, seriesTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/stalker\/[^/]+\/favorites$/,
title: seriesTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expect
.poll(() => new URL(app.mainWindow.url()).pathname)
.toMatch(/\/favorites$/);
await expectPathname(
app.mainWindow,
/\/workspace\/stalker\/[^/]+\/favorites$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
@@ -355,23 +522,57 @@ async function addCurrentDetailToFavorites(page: Page): Promise<void> {
await expect(addButton).toBeVisible({ timeout: 20000 });
await addButton.click();
await expect(page.locator('button.favorite-btn--active').first()).toBeVisible({
await expect(
page.locator('button.favorite-btn--active').first()
).toBeVisible({
timeout: 20000,
});
}
async function goBackFromDetail(page: Page): Promise<void> {
const backButton = page.locator('app-content-hero .hero__back-button').first();
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
await expect(backButton).toBeVisible({ timeout: 20000 });
await backButton.click();
try {
await backButton.click({ timeout: 5000 });
} catch {
await backButton.evaluate((button: HTMLButtonElement) =>
button.click()
);
}
}
async function toggleFavoriteForChannel(page: Page, title: string): Promise<void> {
async function expectInlineCollectionDetail(
page: Page,
params: {
pathname: RegExp;
title: string;
playlistTitle: string;
}
): Promise<void> {
await expectPathname(page, params.pathname);
await expect(playlistSwitcherTitle(page)).toContainText(
params.playlistTitle
);
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
).toBeVisible({ timeout: 20000 });
}
async function toggleFavoriteForChannel(
page: Page,
title: string
): Promise<void> {
const item = channelItemByTitle(page, 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/);
await expect(item.locator('.favorite-button mat-icon').first()).toHaveText(
/star/
);
}
+263 -58
View File
@@ -11,6 +11,7 @@ import {
contentCardByTitle,
defaultXtreamPassword,
defaultXtreamUsername,
expectPathname,
expectVisibleContentCardTitle,
expect,
importM3uPlaylistFromNativeDialog,
@@ -25,6 +26,7 @@ import {
switchUnifiedCollectionContent,
switchUnifiedCollectionScope,
test,
playlistSwitcherTitle,
waitForM3uCatalog,
waitForStalkerCatalog,
waitForXtreamWorkspaceReady,
@@ -44,13 +46,17 @@ 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 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 {
@@ -150,13 +156,22 @@ test.describe('Electron Recently Viewed', () => {
request,
}) => {
await resetMockServers(request, ['xtream']);
const liveFixture = await fetchXtreamLiveFixture(request, xtreamCredentials);
const vodFixture = await fetchXtreamVodFixture(request, xtreamCredentials);
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 [liveTitle] = pickDistinctTitles(
liveFixture.items,
getXtreamTitle
);
const portalTitle = 'Xtream Recent Source';
const app = await launchElectronApp(dataDir);
@@ -176,7 +191,9 @@ test.describe('Electron Recently Viewed', () => {
await channelItemByTitle(app.mainWindow, liveTitle).first().click();
await closeUnifiedLiveDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Movies', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
vodFixture.categoryName
@@ -190,7 +207,9 @@ test.describe('Electron Recently Viewed', () => {
await playCurrentDetail(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Series', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
seriesFixture.categoryName
@@ -199,7 +218,9 @@ test.describe('Electron Recently Viewed', () => {
await playFirstSeriesEpisode(app.mainWindow);
await openPlaylistRecent(app.mainWindow);
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -207,7 +228,9 @@ test.describe('Electron Recently Viewed', () => {
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -223,7 +246,9 @@ test.describe('Electron Recently Viewed', () => {
await openPlaylistRecent(app.mainWindow);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
@@ -233,20 +258,29 @@ test.describe('Electron Recently Viewed', () => {
await clearRecentItems(app.mainWindow);
// After clearing all items, the content toggle disappears.
// All content types are cleared simultaneously, so no toggle switch needed.
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(0);
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(0);
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(0);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(0);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(0);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(0);
} finally {
await closeElectronApp(app);
}
});
test('opens Xtream recent movies and series in source detail routes and returns back to recent', async ({
test('opens Xtream recent movies and series inline from recent without switching the playlist or showing the sidebar', async ({
dataDir,
request,
}) => {
await resetMockServers(request, ['xtream']);
const vodFixture = await fetchXtreamVodFixture(request, xtreamCredentials);
const vodFixture = await fetchXtreamVodFixture(
request,
xtreamCredentials
);
const seriesFixture = await fetchXtreamSeriesFixture(
request,
xtreamCredentials
@@ -260,7 +294,9 @@ test.describe('Electron Recently Viewed', () => {
});
await waitForXtreamWorkspaceReady(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Movies', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
vodFixture.categoryName
@@ -269,7 +305,9 @@ test.describe('Electron Recently Viewed', () => {
await playCurrentDetail(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Series', exact: true })
.click();
await clickCategoryByNameExact(
app.mainWindow,
seriesFixture.categoryName
@@ -278,33 +316,44 @@ test.describe('Electron Recently Viewed', () => {
await playFirstSeriesEpisode(app.mainWindow);
await openPlaylistRecent(app.mainWindow);
await expect(playlistSwitcherTitle(app.mainWindow)).toContainText(
portalTitle
);
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 contentCardByTitle(app.mainWindow, movieTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/xtreams\/[^/]+\/recent$/,
title: movieTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expect
.poll(() => new URL(app.mainWindow.url()).pathname)
.toMatch(/\/recent$/);
await expectPathname(
app.mainWindow,
/\/workspace\/xtreams\/[^/]+\/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 contentCardByTitle(app.mainWindow, seriesTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/xtreams\/[^/]+\/recent$/,
title: seriesTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expect
.poll(() => new URL(app.mainWindow.url()).pathname)
.toMatch(/\/recent$/);
await expectPathname(
app.mainWindow,
/\/workspace\/xtreams\/[^/]+\/recent$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
@@ -319,10 +368,22 @@ test.describe('Electron Recently Viewed', () => {
await resetMockServers(request, ['stalker']);
const liveFixture = await fetchStalkerCategoryFixture(request, 'itv');
const vodFixture = await fetchStalkerCategoryFixture(request, 'vod');
const seriesFixture = await fetchStalkerCategoryFixture(request, 'series');
const [liveTitle] = pickDistinctTitles(liveFixture.items, getStalkerTitle);
const [movieTitle] = pickDistinctTitles(vodFixture.items, getStalkerTitle);
const [seriesTitle] = pickDistinctTitles(seriesFixture.items, getStalkerTitle);
const seriesFixture = await fetchStalkerCategoryFixture(
request,
'series'
);
const [liveTitle] = pickDistinctTitles(
liveFixture.items,
getStalkerTitle
);
const [movieTitle] = pickDistinctTitles(
vodFixture.items,
getStalkerTitle
);
const [seriesTitle] = pickDistinctTitles(
seriesFixture.items,
getStalkerTitle
);
const portalTitle = 'Stalker Recent Source';
const app = await launchElectronApp(dataDir);
@@ -332,38 +393,56 @@ test.describe('Electron Recently Viewed', () => {
});
await waitForStalkerCatalog(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Live TV', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Live TV', exact: true })
.click();
await clickCategoryById(app.mainWindow, liveFixture.categoryId);
await toggleFavoriteForChannel(app.mainWindow, liveTitle);
await openPlaylistFavorites(app.mainWindow);
await channelItemByTitle(app.mainWindow, liveTitle).first().click();
await closeUnifiedLiveDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Movies', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Movies', exact: true })
.click();
await clickCategoryById(app.mainWindow, vodFixture.categoryId);
await clickGridListCardByTitle(app.mainWindow, movieTitle);
await playCurrentDetail(app.mainWindow);
await goBackFromDetail(app.mainWindow);
await app.mainWindow.getByRole('link', { name: 'Series', exact: true }).click();
await app.mainWindow
.getByRole('link', { name: 'Series', exact: true })
.click();
await clickCategoryById(app.mainWindow, seriesFixture.categoryId);
await clickGridListCardByTitle(app.mainWindow, seriesTitle);
await playFirstSeriesEpisode(app.mainWindow);
await openPlaylistRecent(app.mainWindow);
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(1);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(1);
const restarted = await restartElectronApp(app, dataDir);
app.electronApp = restarted.electronApp;
@@ -375,11 +454,105 @@ test.describe('Electron Recently Viewed', () => {
await openPlaylistRecent(app.mainWindow);
await switchUnifiedCollectionScope(app.mainWindow, 'All playlists');
await switchUnifiedCollectionContent(app.mainWindow, 'Live TV');
await expect(channelItemByTitle(app.mainWindow, liveTitle)).toHaveCount(1);
await expect(
channelItemByTitle(app.mainWindow, liveTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expect(contentCardByTitle(app.mainWindow, movieTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, movieTitle)
).toHaveCount(1);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expect(contentCardByTitle(app.mainWindow, seriesTitle)).toHaveCount(1);
await expect(
contentCardByTitle(app.mainWindow, seriesTitle)
).toHaveCount(1);
} finally {
await closeElectronApp(app);
}
});
test('opens Stalker recent movies and series inline from recent without showing the sidebar', 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 portalTitle = 'Stalker Recent Detail Source';
const app = await launchElectronApp(dataDir);
try {
await addStalkerPortal(app.mainWindow, {
name: portalTitle,
});
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 playCurrentDetail(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 playFirstSeriesEpisode(app.mainWindow);
await openPlaylistRecent(app.mainWindow);
await expect(playlistSwitcherTitle(app.mainWindow)).toContainText(
portalTitle
);
await switchUnifiedCollectionContent(app.mainWindow, 'Movies');
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await contentCardByTitle(app.mainWindow, movieTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/stalker\/[^/]+\/recent$/,
title: movieTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(
app.mainWindow,
/\/workspace\/stalker\/[^/]+\/recent$/
);
await expectVisibleContentCardTitle(app.mainWindow, movieTitle);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
await contentCardByTitle(app.mainWindow, seriesTitle)
.first()
.click();
await expectInlineCollectionDetail(app.mainWindow, {
pathname: /\/workspace\/stalker\/[^/]+\/recent$/,
title: seriesTitle,
playlistTitle: portalTitle,
});
await goBackFromDetail(app.mainWindow);
await expectPathname(
app.mainWindow,
/\/workspace\/stalker\/[^/]+\/recent$/
);
await switchUnifiedCollectionContent(app.mainWindow, 'Series');
await expectVisibleContentCardTitle(app.mainWindow, seriesTitle);
} finally {
await closeElectronApp(app);
}
@@ -405,10 +578,37 @@ async function closeUnifiedLiveDetail(page: Page): Promise<void> {
}
async function goBackFromDetail(page: Page): Promise<void> {
const backButton = page.locator('app-content-hero .hero__back-button').first();
const backButton = page
.locator('app-content-hero .hero__back-button')
.first();
await expect(backButton).toBeVisible({ timeout: 20000 });
await backButton.click();
try {
await backButton.click({ timeout: 5000 });
} catch {
await backButton.evaluate((button: HTMLButtonElement) =>
button.click()
);
}
}
async function expectInlineCollectionDetail(
page: Page,
params: {
pathname: RegExp;
title: string;
playlistTitle: string;
}
): Promise<void> {
await expectPathname(page, params.pathname);
await expect(playlistSwitcherTitle(page)).toContainText(
params.playlistTitle
);
await expect(page.locator('app-workspace-context-panel')).toHaveCount(0);
await expect(page.locator('app-content-hero')).toContainText(params.title);
await expect(
page.locator('app-content-hero .hero__back-button').first()
).toBeVisible({ timeout: 20000 });
}
async function playCurrentDetail(page: Page): Promise<void> {
@@ -420,7 +620,9 @@ async function playCurrentDetail(page: Page): Promise<void> {
async function playFirstSeriesEpisode(page: Page): Promise<void> {
const seasonCard = page.locator('.season-card').first();
const episodeCard = page.locator('.episode-card, .episode-list-item').first();
const episodeCard = page
.locator('.episode-card, .episode-list-item')
.first();
await expect
.poll(
@@ -449,7 +651,10 @@ async function removeLiveRecentItem(page: Page, title: string): Promise<void> {
await item.locator('.favorite-button').first().click();
}
async function toggleFavoriteForChannel(page: Page, title: string): Promise<void> {
async function toggleFavoriteForChannel(
page: Page,
title: string
): Promise<void> {
const item = channelItemByTitle(page, title).first();
await expect(item).toBeVisible({ timeout: 20000 });