const { test, expect } = require('@playwright/test'); const { openClassic } = require('./helpers/classic-fixture'); const id = 'wZzRoXymOUU'; for (const layout of ['classic', 'glass-stage']) for (const screen of ['downloads', 'settings']) { test(`${layout} ${screen}: Retry starts a fresh server job before preparation`, async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await openClassic(page, { settings: { autoPreload: false } }); const requests = []; await page.route(`**/api/download/${id}/retry`, route => { requests.push('retry:' + route.request().method()); return route.fulfill({ status: 202, contentType: 'application/json', body: '{"ok":true,"state":"working"}' }); }); await page.route(`**/api/download/${id}/prepare*`, route => { requests.push('prepare'); return route.fulfill({ contentType: 'application/json', body: '{"ok":true,"state":"ready","etag":"fixture","size":4096,"ext":"mp4"}' }); }); await page.evaluate(({ layout, screen, id }) => { data.settings.layout = layout; applyAppearance(); window.OPFS.isSupported = () => true; window.OPFS.downloadVideo = async () => ({ ok: true, size: 4096 }); SaveQueue.add({ id, title: 'Rock Medley', channel: 'Petra - Topic' }); SaveQueue.state(id, 'failed', `ERROR: [youtube] ${id}: Video unavailable`); view = { type: screen }; render(); }, { layout, screen, id }); if (screen === 'settings') await page.locator('[data-section="downloads-storage"]').click(); await page.getByRole('button', { name: 'Retry Rock Medley', exact: true }).click(); await expect.poll(() => requests).toEqual(['retry:POST', 'prepare']); await expect.poll(() => page.evaluate(id => cachedIds.has(id), id)).toBe(true); expect(await page.evaluate(() => SaveQueue.pending().length)).toBe(0); }); } test('normal resumptions do not force the server to restart a cache job', async ({ page }) => { await openClassic(page, { settings: { autoPreload: false } }); let retries = 0; await page.route(`**/api/download/${id}/retry`, route => { retries++; return route.fulfill({ contentType: 'application/json', body: '{"ok":true}' }); }); await page.route(`**/api/download/${id}/prepare*`, route => route.fulfill({ contentType: 'application/json', body: '{"ok":true,"state":"ready","etag":"fixture","size":4096}' })); await page.evaluate(async id => { window.OPFS.isSupported = () => true; window.OPFS.downloadVideo = async () => ({ ok: true, size: 4096 }); await preload({ id, title: 'Rock Medley' }, { quiet: true }); }, id); expect(retries).toBe(0); expect(await page.evaluate(id => cachedIds.has(id), id)).toBe(true); }); for (const layout of ['classic', 'glass-stage']) for (const width of [390, 1440]) for (const screen of ['downloads', 'settings']) { test(`${layout} ${width} ${screen}: friendly failure retains safe technical details`, async ({ page }) => { await page.setViewportSize({ width, height: 844 }); await openClassic(page, { settings: { autoPreload: false } }); const raw = `ERROR: [youtube] ${id}: Video unavailable\nYouTube clients tried: default, web_embedded, web_safari\n`; await page.evaluate(({ layout, screen, id, raw }) => { data.settings.layout = layout; applyAppearance(); SaveQueue.add({ id, title: 'Rock Medley', channel: 'Petra - Topic' }); SaveQueue.state(id, 'failed', raw); view = { type: screen }; render(); }, { layout, screen, id, raw }); if (screen === 'settings') await page.locator('[data-section="downloads-storage"]').click(); const row = page.locator(screen === 'settings' ? '.download-job' : '.dl-failed').first(); await expect(row).toContainText('YouTube isn’t letting the server save this video.'); await expect(row.locator('pre')).toBeHidden(); const summary = row.locator('summary'); expect((await summary.boundingBox()).height).toBeGreaterThanOrEqual(44); await summary.click(); await expect(row.locator('pre')).toHaveText(raw); expect(await page.evaluate(() => window.unsafeDetail)).toBeUndefined(); const bounds = await row.evaluate(el => ({ width: el.clientWidth, content: el.scrollWidth, children: [...el.querySelectorAll('*')].filter(child => child.scrollWidth > child.clientWidth + 1).map(child => [child.tagName, child.className, child.clientWidth, child.scrollWidth]) })); expect(bounds.content <= bounds.width + 1, JSON.stringify(bounds)).toBe(true); await expect(row.getByRole('button', { name: 'Retry Rock Medley', exact: true })).toBeVisible(); }); }