diff --git a/apps/web-e2e/playwright.config.ts b/apps/web-e2e/playwright.config.ts index c1045404c..bcf94dc43 100644 --- a/apps/web-e2e/playwright.config.ts +++ b/apps/web-e2e/playwright.config.ts @@ -46,6 +46,12 @@ export default defineConfig({ reuseExistingServer: !process.env['CI'], cwd: workspaceRoot, }, + { + command: 'pnpm nx run web-backend:serve', + url: 'http://localhost:3333/health', + reuseExistingServer: !process.env['CI'], + cwd: workspaceRoot, + }, ], projects: [ { diff --git a/apps/web-e2e/src/self-hosted.e2e.ts b/apps/web-e2e/src/self-hosted.e2e.ts new file mode 100644 index 000000000..080cc1e8c --- /dev/null +++ b/apps/web-e2e/src/self-hosted.e2e.ts @@ -0,0 +1,106 @@ +import { expect, type Locator, type Page, test } from '@playwright/test'; + +const WEB_BACKEND_URL = 'http://localhost:3333'; +const XTREAM_MOCK_PORT = process.env['XTREAM_MOCK_PORT'] ?? '3211'; +const STALKER_MOCK_PORT = process.env['MOCK_PORT'] ?? '3210'; +const XTREAM_MOCK_SERVER = `http://localhost:${XTREAM_MOCK_PORT}`; +const STALKER_MOCK_SERVER = `http://localhost:${STALKER_MOCK_PORT}`; +const STALKER_PORTAL_URL = `${STALKER_MOCK_SERVER}/portal.php`; +const DEFAULT_MAC = '00:1A:79:00:00:01'; + +async function installRuntimeConfig(page: Page): Promise { + await page.route('**/assets/app-config.js', async (route) => { + await route.fulfill({ + contentType: 'application/javascript', + body: `window.__IPTVNATOR_CONFIG__ = { BACKEND_URL: ${JSON.stringify(WEB_BACKEND_URL)} };\n`, + }); + }); +} + +async function setInputValue(input: Locator, value: string): Promise { + await input.fill(''); + await input.fill(value); + + if ((await input.inputValue()) === value) { + return; + } + + await input.click(); + await input.press('ControlOrMeta+A'); + await input.press('Backspace'); + await input.type(value); + await expect(input).toHaveValue(value); +} + +async function addXtreamPortal(page: Page): Promise { + await page.getByRole('button', { name: 'Add playlist' }).click(); + const dialog = page.locator('mat-dialog-container'); + await expect(dialog).toBeVisible(); + await dialog.getByRole('tab', { name: 'Xtream', exact: true }).click(); + + await dialog.locator('#title').fill('Self-hosted Xtream'); + await dialog.locator('#serverUrl').fill(XTREAM_MOCK_SERVER); + await dialog.locator('#username').fill('user1'); + await dialog.locator('#password').fill('pass1'); + + await dialog.getByRole('button', { name: 'Add', exact: true }).click(); + await page.waitForSelector('mat-dialog-container', { state: 'detached' }); + await page.waitForURL(/xtreams.*vod/); +} + +async function addStalkerPortal(page: Page): Promise { + await page.getByRole('button', { name: 'Add playlist' }).click(); + const dialog = page.locator('mat-dialog-container'); + await expect(dialog).toBeVisible(); + await dialog.getByRole('tab', { name: 'Stalker', exact: true }).click(); + + await setInputValue(dialog.locator('input#title'), 'Self-hosted Stalker'); + await setInputValue(dialog.locator('input#portalUrl'), STALKER_PORTAL_URL); + await setInputValue(dialog.locator('input#macAddress'), DEFAULT_MAC); + + const addButton = dialog.getByRole('button', { name: 'Add', exact: true }); + await expect(addButton).toBeEnabled({ timeout: 10_000 }); + await addButton.click(); + await page.waitForSelector('mat-dialog-container', { state: 'detached' }); + await page.waitForURL(/stalker.*vod/); +} + +test.beforeEach(async ({ page, request }) => { + await request.post(`${XTREAM_MOCK_SERVER}/reset`); + await request.post(`${STALKER_MOCK_SERVER}/reset`); + await installRuntimeConfig(page); + await page.goto('/'); +}); + +test('@self-hosted runtime config points PWA calls at the monorepo backend', async ({ + page, + request, +}) => { + await expect + .poll(() => + page.evaluate(() => window.__IPTVNATOR_CONFIG__?.BACKEND_URL) + ) + .toBe(WEB_BACKEND_URL); + + const response = await request.get(`${WEB_BACKEND_URL}/health`); + expect(response.ok()).toBeTruthy(); + await expect(response).toBeOK(); +}); + +test('@self-hosted Xtream portal loads through web-backend proxy', async ({ + page, +}) => { + await addXtreamPortal(page); + + const categoryItems = page.locator('.category-item'); + await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 }); +}); + +test('@self-hosted Stalker portal loads through web-backend proxy', async ({ + page, +}) => { + await addStalkerPortal(page); + + const categoryItems = page.locator('.category-item'); + await expect(categoryItems.first()).toBeVisible({ timeout: 15_000 }); +});