fix(xtream): preserve content identity in PWA collections (#1870)

* fix(xtream): invalidate detail initialization on close

* fix(xtream): preserve typed PWA collection identity

* fix(xtream): complete typed live collection actions

* test(xtream): cover PWA collection identity through reloads

* fix(xtream): keep typed collection actions local

* fix(xtream): tolerate quota limits during collection migration

* fix(xtream): prioritize requested collection saves

* fix(xtream): align collection identity with current playback contracts

* test(xtream): confirm playback before collection history assertions
This commit is contained in:
4gray authored and GitHub committed 2026-10-10 12:27:03 +02:00
1 parent 0060330b5e
commit 98784815b1
31 files changed
+2349 -551

No files matched your search

+115
View File
@@ -15,7 +15,20 @@ import {
DEFAULT_USERNAME,
interceptXtreamRequests,
MOCK_SERVER,
selectWebPlayer,
} from './xtream-series-playback.fixture';
import {
addCollisionFavorite,
collisionCollectionItem,
collisionItems,
expectCollectionDetailRoundTrip,
expectCollisionCollection,
interceptCollidingXtreamItems,
playCollisionItem,
removeCollisionFavorite,
routeCollisionMedia,
selectCollectionType,
} from './xtream-collection-helpers';
/**
* Xtream Codes E2E Tests
@@ -413,6 +426,108 @@ test('@xtream add portal and see it in the playlist list', async ({ page }) => {
).toBeVisible();
});
test('@xtream @collections PWA favorites keep colliding movie, series and live IDs independent after reload and removal', async ({
page,
request,
}) => {
test.setTimeout(90_000);
const categories = await interceptCollidingXtreamItems(
page,
request,
MOCK_SERVER
);
await addXtreamPortal(page, {
name: 'Collision Favorites Portal',
username: 'minimal',
password: 'minimal',
});
const playlistPath = new URL(page.url()).pathname.replace(/\/vod.*$/, '');
for (const item of collisionItems) {
await addCollisionFavorite(page, item, categories[item.type]);
}
await page.goto(`${playlistPath}/favorites`);
await expectCollisionCollection(page, collisionItems);
await page.reload();
await expectCollisionCollection(page, collisionItems);
await expectCollectionDetailRoundTrip(page, collisionItems[0]);
await expectCollectionDetailRoundTrip(page, collisionItems[1]);
for (let index = 0; index < collisionItems.length; index++) {
const item = collisionItems[index];
await removeCollisionFavorite(
page,
item,
collisionItems.length - index
);
await expect(collisionCollectionItem(page, item)).toHaveCount(0);
await page.reload();
await expectCollisionCollection(page, collisionItems.slice(index + 1));
}
});
test('@xtream @collections PWA recent items require confirmed playback and retain colliding content IDs', async ({
page,
request,
browserName,
}) => {
test.skip(
browserName !== 'chromium',
'Synthetic live media uses Chromium codecs'
);
test.setTimeout(90_000);
const categories = await interceptCollidingXtreamItems(
page,
request,
MOCK_SERVER
);
const allowPlayback = await routeCollisionMedia(page, MOCK_SERVER);
await selectWebPlayer(page, 'HTML5 video player');
await page.goto('/');
await addXtreamPortal(page, {
name: 'Collision Recent Portal',
username: 'minimal',
password: 'minimal',
});
const playlistPath = new URL(page.url()).pathname.replace(/\/vod.*$/, '');
for (const item of collisionItems) {
const failedMedia = page.waitForResponse(
(response) =>
response.url().startsWith(`${MOCK_SERVER}/${item.type}/`) &&
response.status() === 503
);
await playCollisionItem(page, item, categories[item.type], false);
await failedMedia;
await page.goto(`${playlistPath}/recent`);
await expectCollisionCollection(page, []);
}
allowPlayback();
for (let index = 0; index < collisionItems.length; index++) {
const item = collisionItems[index];
await playCollisionItem(page, item, categories[item.type]);
await page.goto(`${playlistPath}/recent`);
await expectCollisionCollection(
page,
collisionItems.slice(0, index + 1)
);
}
await page.reload();
await expectCollisionCollection(page, collisionItems);
await expectCollectionDetailRoundTrip(page, collisionItems[0]);
await expectCollectionDetailRoundTrip(page, collisionItems[1]);
await selectCollectionType(page, collisionItems[0]);
const movie = collisionCollectionItem(page, collisionItems[0]);
await movie.hover();
await movie.locator('.remove-button').click();
await expect(movie).toHaveCount(0);
await page.reload();
await expectCollisionCollection(page, collisionItems.slice(1));
});
test('@xtream playlist details edit is retained in the PWA browser context', async ({
page,
}) => {