const { test, expect } = require('@playwright/test'); const { TOKEN } = require('./global-setup'); // The token sets a cookie, so every test starts by presenting it once. test.beforeEach(async ({ page }) => { await page.goto(`/?token=${TOKEN}`); await expect(page.locator('#feedlist')).toBeVisible(); }); test('the page loads and lists the configured feeds', async ({ page }) => { // Regression: a ReferenceError in the script left the shell rendered and the sidebar // empty, with every handler below the error dead. Server-side checks all passed. // Four top-level feeds in the fixture config; the OPML's children are inside a closed folder. await expect(page.locator('.feed')).toHaveCount(5, { timeout: 15_000 }); await expect(page.getByText('Test Show')).toBeVisible(); const errors = []; page.on('pageerror', e => errors.push(e.message)); await page.reload(); await expect(page.locator('.feed').first()).toBeVisible(); expect(errors, 'the page script must not throw at load').toEqual([]); }); test('the theme toggle actually changes the theme', async ({ page }) => { // Regression: this button was wired after a line that threw, so it did nothing. const before = await page.evaluate(() => document.documentElement.dataset.theme || 'system'); await page.locator('#theme').click(); await expect .poll(() => page.evaluate(() => document.documentElement.dataset.theme)) .not.toBe(before); }); test('settings opens and saves the global schedule', async ({ page }) => { await page.locator('#prefs').click(); await expect(page.locator('#modal.on')).toBeVisible(); await expect(page.locator('#gnum')).toBeVisible(); await page.locator('#gnum').fill('4'); await page.locator('#gunit').selectOption('h'); await page.locator('#gsave').click(); await expect(page.locator('#modal.on')).toBeHidden(); // It must survive a reload, i.e. actually reach the config. await page.locator('#prefs').click(); await expect(page.locator('#gnum')).toHaveValue('4'); await expect(page.locator('#gunit')).toHaveValue('h'); }); test('episodes show with their metadata, and the text opens below', async ({ page }) => { await page.getByText('Test Show').click(); await expect(page.locator('.ep').first()).toBeVisible({ timeout: 20_000 }); await expect(page.getByText('First Episode')).toBeVisible(); // Newest first, so target the episode by name rather than by position. const first = page.locator('.ep', { hasText: 'First Episode' }); await expect(first).toContainText('S1E1'); await expect(first).toContainText('30:30'); // itunes:duration 1830 await expect(page.locator('.ep', { hasText: 'Second Episode' })).toContainText('15:00'); // Selecting an item shows its text in the pane below, not inline in the row. await first.click(); await expect(first).toHaveClass(/sel/); await expect(page.locator('#detail')).toContainText('Show notes for the first one'); await expect(page.locator('#detail .dt')).toHaveText('First Episode'); }); test('the three panes are there and the item text lands in the bottom one', async ({ page }) => { await page.getByText('Test Show').click(); await expect(page.locator('#list')).toBeVisible(); await expect(page.locator('#grab')).toBeVisible(); // the draggable divider await expect(page.locator('#detail')).toContainText('Pick an item'); await page.locator('.ep', { hasText: 'First Episode' }).click(); await expect(page.locator('#detail .dt')).toHaveText('First Episode'); // The enclosure goes to the Files pane beside the list, as the original's did. await expect(page.locator('#files')).toBeVisible(); await expect(page.locator('#files .encbox')).toHaveCount(1); // Only the downloaded one gets a player, and max_new_per_check is 1, so find it by // its chip rather than assuming which episode the daemon happened to fetch. const downloaded = page.locator('.ep', { hasText: 'downloaded' }).first(); await downloaded.click(); await expect(page.locator('#files audio')).toBeVisible(); await expect(page.locator('#files .encbox [title="Save to this computer"]')).toBeVisible(); // Selecting another item replaces the pane rather than stacking. await page.locator('.ep', { hasText: 'First Episode' }).click(); await expect(page.locator('#detail .dt')).toHaveText('First Episode'); await expect(page.locator('#files audio')).toHaveCount(0); }); test('a downloaded file that is not audio gets no player', async ({ page }) => { // Regression: anything with a file got an