mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
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:
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' });
|
||||
}
|
||||
|
||||
|
||||
@@ -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/
|
||||
);
|
||||
}
|
||||
@@ -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 });
|
||||
|
||||
Reference in new issue
Block a user