const assert = require('node:assert/strict'); const { chromium } = require(process.env.PLAYWRIGHT_PACKAGE || 'playwright'); const base = process.env.REVIEW_BASE || 'http://localhost:3114'; (async () => { const browser = await chromium.launch(); try { const context = await browser.newContext(); await context.addCookies([{ name: 'magent_logged_in', value: '1', url: base }]); let finish, original = false, outcome = 'attention'; const writes = [], errors = []; await context.route('**/api/**', async route => { const req = route.request(), path = new URL(req.url()).pathname; const reply = json => route.fulfill({ json }); if (path === '/api/auth/me') return reply({ username: 'Admin', role: 'admin' }); if (path.endsWith('/snapshot')) return reply({ request_id: '3976', title: 'Downfall', request_type: 'movie', state: 'ADDED_TO_ARR', timeline: [], actions: [{ id: 'search_auto', label: 'Search and auto-download', requires_confirmation: false }], presentation: { status: { label: 'Waiting', meaning: 'Waiting for a release.' }, nextStep: { title: 'Search', description: 'Search for a download.', actionIds: ['search_auto'] }, pipeline: [] } }); if (path.endsWith('/language') && req.method() === 'GET') return reply({ language: { code: 'de' }, originalEnabled: original, canChange: true, profileLanguage: original ? 'Original' : 'English' }); if (path.includes('/operations/')) return reply({ id: path.split('/').pop(), label: 'Working on request', status: 'running', events: [{ id: 'search', service: 'Radarr', state: 'active', message: 'Searching indexers for Downfall…' }] }); if (path.endsWith('/actions/search_auto') || path.endsWith('/actions/language')) { writes.push({ path, payload: req.postData() ? req.postDataJSON() : null }); await new Promise(resolve => { finish = resolve; }); if (path.endsWith('/language')) original = true; return reply({ status: outcome, message: outcome === 'attention' ? 'Search finished, but no download appeared. Review matching releases and rejection reasons.' : 'Radarr has a download queued for this movie.' }); } if (path.includes('/events/stream')) return route.fulfill({ contentType: 'text/event-stream', body: ': fixture\n\n' }); return reply({}); }); const page = await context.newPage(); page.on('pageerror', e => errors.push(e.message)); for (const width of [1440, 390]) { original = false; outcome = 'attention'; await page.setViewportSize({ width, height: 900 }); await page.goto(base + '/requests/3976'); await page.getByRole('heading', { name: 'German audio may need your approval' }).waitFor(); await page.getByRole('button', { name: 'Search and auto-download', exact: true }).first().click(); let dialog = page.getByRole('dialog'); await dialog.waitFor(); assert(await dialog.getByText(/Working on your request/).isVisible(), 'Progress opens automatically'); await dialog.getByText('Searching indexers for Downfall…', { exact: true }).waitFor(); finish(); await dialog.getByText('Search finished, but no download appeared. Review matching releases and rejection reasons.', { exact: true }).waitFor(); await dialog.getByRole('button', { name: 'Dismiss activity' }).click(); outcome = 'downloading'; await page.getByRole('button', { name: 'Use German audio & search', exact: true }).click(); dialog = page.getByRole('dialog'); await dialog.waitFor(); await dialog.getByText('Searching indexers for Downfall…', { exact: true }).waitFor(); finish(); await dialog.getByText('Radarr has a download queued for this movie.', { exact: true }).waitFor(); if (process.env.REVIEW_DIR) await page.screenshot({ path: `${process.env.REVIEW_DIR}/downfall-progress-${width}.png`, animations: 'disabled' }); await dialog.getByRole('button', { name: 'Dismiss activity' }).click(); await page.getByRole('heading', { name: 'German audio enabled' }).waitFor(); assert.notEqual(await page.evaluate(() => document.body.style.overflow), 'hidden'); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)); } assert(writes.filter(w => w.path.endsWith('/language')).every(w => w.payload.acceptOriginalLanguage === true && w.payload.languageCode === 'de')); assert.deepEqual(errors, []); console.log('Passed: desktop/mobile automatic repair overlay, live events, no-download outcome, prominent German audio choice, saved state, queue confirmation and scroll restoration. All APIs intercepted.'); } finally { await browser.close(); } })().catch(e => { console.error(e); process.exitCode = 1; });