fix(stalker): align season markers and preserve episode loading (#1545)

This commit is contained in:
4gray authored and GitHub committed 2026-09-05 22:22:46 +02:00
1 parent 812ec69fd4
commit 249cd38a66
16 files changed
+546 -74

No files matched your search

@@ -0,0 +1,162 @@
import type { Page } from '@playwright/test';
import { expect } from './fixtures';
/** Complete lazy VOD season workflow, using fictional provider/TMDB replies. */
export async function verifyStalkerSeasonMarkers(
page: Page,
addPortal: () => Promise<void>
): Promise<void> {
const titles = ['Signal House s02', 'Сигнальный дом (3 сезон)'];
const seasonRequests: number[] = [];
const searchLanguages: string[] = [];
await page.route('https://api.themoviedb.org/**', async (route) => {
const url = new URL(route.request().url());
const seasonMatch = url.pathname.match(/\/tv\/777\/season\/(\d+)$/);
if (seasonMatch) {
const season = Number(seasonMatch[1]);
seasonRequests.push(season);
return route.fulfill({
json: {
season_number: season,
overview: `Season ${season} overview`,
episodes: [
{
episode_number: 1,
season_number: season,
name: `Season ${season} premiere`,
overview: `Season ${season} episode plot`,
still_path: `/season-${season}.jpg`,
},
],
},
});
}
const details = {
id: 777,
name: 'Signal House',
original_name: 'Signal House',
original_language: 'en',
first_air_date: '2024-01-01',
overview: 'A fictional series.',
vote_count: 500,
vote_average: 7,
genres: [],
credits: { cast: [], crew: [] },
};
if (url.pathname.endsWith('/search/tv')) {
searchLanguages.push(url.searchParams.get('language') ?? '');
return route.fulfill({
json: {
results: [
{
...details,
name: /[а-я]/i.test(
url.searchParams.get('query') ?? ''
)
? 'Сигнальный дом'
: details.name,
},
],
},
});
}
return route.fulfill({ json: details });
});
await page.route('https://image.tmdb.org/**', (route) =>
route.fulfill({
contentType: 'image/svg+xml',
body: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20"/>',
})
);
await page.route('**/localhost:3000/stalker**', async (route) => {
const params = new URL(route.request().url()).searchParams;
if (
params.get('action') !== 'get_ordered_list' ||
params.get('type') !== 'vod'
)
return route.fallback();
const movieId = params.get('movie_id');
const rows = !movieId
? titles.map((name, index) => ({
id: String(99001 + index),
name,
o_name: name,
is_series: '1',
category_id: '2001',
}))
: params.has('season_id')
? [
{
id: `episode-${movieId}`,
is_episode: true,
series_number: 1,
name: 'Episode 1',
},
]
: [
{
id: 'provider-season-1',
video_id: movieId,
name: 'Season 1',
season_number: '1',
is_season: true,
},
];
return route.fulfill({
json: {
payload: {
js: {
data: rows,
total_items: rows.length,
max_page_items: 20,
},
},
},
});
});
await page.goto('/workspace/settings/tmdb');
await page.locator('[data-test-id="tmdb-enabled"] input').check();
await page.locator('[data-test-id="tmdb-api-key"]').fill('e2e-key');
const save = page.getByRole('button', { name: 'Save changes' });
await save.click();
await expect(save).toBeHidden();
await page.goto('/');
await addPortal();
for (const [index, title] of titles.entries()) {
const season = index + 2;
await page.getByText(title, { exact: true }).first().click();
await expect(
page.getByRole('tab', { name: `Season ${season}`, exact: true })
).toBeVisible();
await expect(page.getByTestId('series-quick-start')).toContainText(
`S0${season}E01`
);
await expect(
page.getByRole('heading', {
name: `1. Season ${season} premiere`,
exact: true,
})
).toBeVisible();
await expect(page.getByTestId('season-description')).toContainText(
`Season ${season} overview`
);
await expect(page.locator('.episode-card')).toContainText(
`Season ${season} episode plot`
);
await expect(page.locator('.episode-card img')).toHaveAttribute(
'src',
new RegExp(`/season-${season}\\.jpg$`)
);
await page.locator('[data-test-id="toggle-season-watched"]').click();
await expect(page.locator('.episode-card--watched')).toHaveCount(1);
await page.getByRole('button', { name: 'Back', exact: true }).click();
}
await page.getByText(titles[0], { exact: true }).first().click();
await expect(
page.getByRole('tab', { name: 'Season 2', exact: true })
).toBeVisible();
await expect(page.locator('.episode-card--watched')).toHaveCount(1);
expect(seasonRequests).toEqual(expect.arrayContaining([2, 3]));
expect(seasonRequests).not.toContain(1);
expect(searchLanguages).toEqual(expect.arrayContaining(['en-US', 'ru-RU']));
}
+11 -8
View File
@@ -1,7 +1,5 @@
import {
type APIRequestContext,
type Page,
} from '@playwright/test';
import { verifyStalkerSeasonMarkers } from './stalker-season-markers.fixture';
import { type APIRequestContext, type Page } from '@playwright/test';
import { setInputValue } from './e2e-helpers';
import { expect, test } from './fixtures';
import {
@@ -1030,10 +1028,7 @@ test('@stalker favorites — embedded-series favorite refreshes newly released e
body?.payload?.js?.data ?? body?.js?.data ?? [];
for (const row of rows) {
if (Array.isArray(row.series) && row.series.length > 0) {
row.series = [
...row.series,
String(row.series.length + 1),
];
row.series = [...row.series, String(row.series.length + 1)];
}
}
await route.fulfill({ response, body: JSON.stringify(body) });
@@ -1066,6 +1061,14 @@ test('@stalker favorites — embedded-series favorite refreshes newly released e
).toBeVisible({ timeout: 10_000 });
});
test('@stalker title season markers align lazy VOD labels, TMDB episodes and watched state', async ({
page,
}) => {
await verifyStalkerSeasonMarkers(page, () =>
addStalkerPortal(page, { name: 'Season Marker Portal' })
);
});
test('@stalker season watched toggle — embedded series marks and clears every episode', async ({
page,
request,