mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
feat(stalker): add radio support
Entire-Checkpoint: f957cd9849e0
This commit is contained in:
1 parent
e9875f9f29
commit
ab905af285
59 files changed
+1059
-179
No files matched your search
@@ -1,4 +1,10 @@
|
||||
import { expect, Locator, Page, test } from '@playwright/test';
|
||||
import {
|
||||
expect,
|
||||
type APIRequestContext,
|
||||
type Locator,
|
||||
type Page,
|
||||
test,
|
||||
} from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Stalker Portal E2E Tests
|
||||
@@ -8,7 +14,7 @@ import { expect, Locator, Page, test } from '@playwright/test';
|
||||
* dev server when running e2e tests (see playwright.config.ts).
|
||||
*
|
||||
* Default scenario MAC (00:1A:79:00:00:01) provides:
|
||||
* - 8 categories per content type (VOD / Series / ITV)
|
||||
* - 8 categories per content type (VOD / Series / ITV / Radio)
|
||||
* - 40 items per category
|
||||
* - 3 seasons × 8 episodes per series item
|
||||
*
|
||||
@@ -62,6 +68,31 @@ async function setInputValue(input: Locator, value: string): Promise<void> {
|
||||
await expect(input).toHaveValue(value);
|
||||
}
|
||||
|
||||
async function resetMockServer(request: APIRequestContext): Promise<void> {
|
||||
let lastError: unknown;
|
||||
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
try {
|
||||
const response = await request.post(`${MOCK_SERVER}/reset`);
|
||||
if (response.ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastError = new Error(
|
||||
`Reset failed with status ${response.status()}`
|
||||
);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, 250 * (attempt + 1))
|
||||
);
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Stalker portal via the UI:
|
||||
* 1. Click the "add playlist" button to open the unified dialog
|
||||
@@ -97,7 +128,7 @@ async function addStalkerPortal(
|
||||
|
||||
test.beforeEach(async ({ page, request }) => {
|
||||
// Reset mock server state (clears in-memory favorites and cache)
|
||||
await request.post(`${MOCK_SERVER}/reset`);
|
||||
await resetMockServer(request);
|
||||
|
||||
// Playwright creates a fresh browser context per test, so extra
|
||||
// IndexedDB cleanup here only risks racing with app-managed DB handles.
|
||||
@@ -208,6 +239,58 @@ test('@stalker EPG data loads for ITV channel', async ({ page }) => {
|
||||
).toBeVisible({ timeout: 20_000 });
|
||||
});
|
||||
|
||||
test('@stalker radio — stations use the inline audio player without EPG', async ({
|
||||
page,
|
||||
}) => {
|
||||
await addStalkerPortal(page);
|
||||
|
||||
const radioListRequests: string[] = [];
|
||||
const epgRequests: string[] = [];
|
||||
page.on('request', (request) => {
|
||||
const url = request.url();
|
||||
if (
|
||||
url.includes('type=radio') &&
|
||||
url.includes('action=get_ordered_list')
|
||||
) {
|
||||
radioListRequests.push(url);
|
||||
}
|
||||
if (
|
||||
url.includes('action=get_epg_info') ||
|
||||
url.includes('action=get_short_epg')
|
||||
) {
|
||||
epgRequests.push(url);
|
||||
}
|
||||
});
|
||||
|
||||
await page.getByRole('link', { name: /radio/i }).click();
|
||||
await page.waitForURL(/stalker.*radio/);
|
||||
|
||||
const categories = page.locator('.category-item');
|
||||
await expect(categories.nth(1)).toBeVisible({ timeout: 10_000 });
|
||||
await categories.nth(1).click();
|
||||
|
||||
const stations = page.locator('[data-test-id="channel-item"]');
|
||||
await expect(stations.first()).toBeVisible({ timeout: 20_000 });
|
||||
await expect.poll(() => radioListRequests.length).toBeGreaterThan(0);
|
||||
|
||||
await stations.first().click();
|
||||
await expect(stations.first()).toHaveClass(/active/, { timeout: 20_000 });
|
||||
await expect(page.locator('app-audio-player')).toBeVisible({
|
||||
timeout: 20_000,
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: 'Hide channels list' }).click();
|
||||
const restoreButton = page.getByRole('button', {
|
||||
name: 'Show channels list',
|
||||
});
|
||||
await expect(restoreButton).toBeVisible();
|
||||
await restoreButton.click();
|
||||
await expect(stations.first()).toBeVisible();
|
||||
|
||||
await expect(page.locator('app-epg-list')).toHaveCount(0);
|
||||
expect(epgRequests).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('@stalker bulk EPG is fetched once and reused across channel switches', async ({
|
||||
page,
|
||||
}) => {
|
||||
@@ -284,6 +367,31 @@ test('@stalker create_link returns a playable stream URL', async ({
|
||||
expect(streamUrl).toMatch(/\.m3u8$/);
|
||||
});
|
||||
|
||||
test('@stalker mock server returns radio categories and stations', async ({
|
||||
request,
|
||||
}) => {
|
||||
const categoriesResponse = await request.get(
|
||||
`${MOCK_SERVER}/stalker?action=get_categories&type=radio&macAddress=${DEFAULT_MAC}`
|
||||
);
|
||||
expect(categoriesResponse.ok()).toBeTruthy();
|
||||
const categoriesBody = await categoriesResponse.json();
|
||||
expect(categoriesBody.payload.js.length).toBeGreaterThan(0);
|
||||
|
||||
const firstCategory = categoriesBody.payload.js[0].id;
|
||||
const stationsResponse = await request.get(
|
||||
`${MOCK_SERVER}/stalker?action=get_ordered_list&type=radio&category=${firstCategory}&p=1&macAddress=${DEFAULT_MAC}&JsHttpRequest=1-xml`
|
||||
);
|
||||
expect(stationsResponse.ok()).toBeTruthy();
|
||||
const stationsBody = await stationsResponse.json();
|
||||
const firstStation = stationsBody.payload.js.data[0];
|
||||
expect(firstStation).toEqual(
|
||||
expect.objectContaining({
|
||||
category_id: firstCategory,
|
||||
radio: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('@stalker series — seasons load for a series item', async ({
|
||||
request,
|
||||
}) => {
|
||||
|
||||
Reference in new issue
Block a user