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 }]); const calls = []; const errors = []; await context.route('**/api/**', async route => { const request = route.request(); const path = new URL(request.url()).pathname; const reply = json => route.fulfill({ json }); if (path === '/api/auth/me') return reply({ username: 'Viewer', role: 'user', features: { requests: true, new_requests: true, stats: true, issues: true, invites: true } }); if (path === '/api/site/info') return reply({ mediaServerUrl: 'https://watch.example.test/' }); if (path === '/api/requests/search') return reply({ results: [ { title: 'Drive', type: 'movie', tmdbId: 64690, year: 2011, requestId: 42, statusLabel: 'Ready to watch' }, { title: 'Drive Away Dolls', type: 'movie', tmdbId: 957304, year: 2024 }, ] }); if (path === '/api/requests/42/snapshot') return reply({ request_id: '42', title: 'Drive', year: 2011, request_type: 'movie', state: 'AVAILABLE', timeline: [], actions: [], presentation: { status: { label: 'Available to watch', meaning: 'Collection is complete and the title is available on the media server.' }, nextStep: { title: 'Ready to watch', description: 'Open the title when you are ready.', actionIds: [] }, pipeline: [{ id: 'available', label: 'Available to watch', state: 'complete', summary: 'Ready', link: 'https://watch.example.test/movie/drive' }], }, }); if (path === '/api/requests/42/language') return reply({ language: null }); if (path === '/api/requests/42/issue-options') return reply({ request_id: '42', request_type: 'movie', title: 'Drive', can_act: true, movie: { selected_label: 'Drive', has_file: true, missing: false, best_fit: true, file_id: 77 }, seasons: [], episodes: [], }); if (path === '/api/portal/items' && request.method() === 'POST') { calls.push({ path, body: request.postDataJSON() }); return reply({ item: { id: 73 } }); } if (path === '/api/requests/42/actions/replace' && request.method() === 'POST') { calls.push({ path, body: request.postDataJSON() }); return reply({ status: 'ok', message: 'Replacement queued.' }); } if (path.includes('/events/stream')) return route.fulfill({ contentType: 'text/event-stream', body: ': fixture\n\n' }); if (path.includes('/branding/')) return route.fulfill({ status: 404 }); return reply({ navigation: { showRequests: true }, items: [], total: 0, services: [] }); }); const page = await context.newPage(); page.on('pageerror', error => errors.push(error.message)); const active = () => page.locator('.issue-procedure-step.is-current'); const visibleStep = async title => { await active().getByRole('heading', { name: title, exact: true }).waitFor(); assert.equal(await page.locator('.issue-procedure-step.is-current').count(), 1); }; for (const width of [1440, 390]) { await page.setViewportSize({ width, height: 950 }); await page.goto(base + '/requests/42'); await page.getByText('Watch this now!', { exact: true }).waitFor(); assert.equal(await page.getByRole('heading', { name: 'Where your request is now' }).count(), 0); assert.equal(await page.getByRole('link', { name: /Watch on Grizzlyflix/ }).getAttribute('href'), 'https://watch.example.test/movie/drive'); const search = page.getByRole('combobox', { name: 'Search titles or requests' }); assert(await search.isVisible()); await search.fill('Drive'); await page.getByRole('option', { name: /Drive.*Ready to watch/ }).waitFor(); await page.keyboard.press('Escape'); await page.getByRole('link', { name: /Start issue report/ }).click(); await page.waitForURL('**/portal/issues?*'); assert.equal(new URL(page.url()).searchParams.get('reportRequest'), '42'); await visibleStep('What is wrong?'); await active().locator('.issue-prefilled-request').getByText('Drive (2011)', { exact: true }).waitFor(); await active().locator('.issue-prefilled-request').getByText(/Request #42 is already selected/).waitFor(); await active().getByRole('button').filter({ hasText: 'Picture or file is broken' }).click(); await visibleStep('What needs to be corrected?'); await active().getByRole('button', { name: 'Visual artefacts or corruption', exact: true }).click(); await active().getByRole('button', { name: 'Continue', exact: true }).click(); await visibleStep('Review and submit'); await active().getByRole('button', { name: /Submit/ }).click(); await visibleStep('What is wrong?'); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)); if (width === 1440) { const labels = await page.locator('.header-actions a').allTextContents(); assert(labels.indexOf('New Requests') < labels.indexOf('My Requests')); assert(labels.indexOf('My Requests') < labels.indexOf('My Stats')); } } await page.goto(base + '/welcome'); await page.getByRole('heading', { name: 'View stats & requests' }).waitFor(); await page.getByText('Check your viewing stats, follow your requests, or make a new one.', { exact: true }).waitFor(); const issueCalls = calls.filter(call => call.path === '/api/portal/items'); const replaceCalls = calls.filter(call => call.path === '/api/requests/42/actions/replace'); assert.equal(issueCalls.length, 2); assert.equal(replaceCalls.length, 2); assert(issueCalls.every(({ body }) => body.kind === 'issue' && body.title === 'Replace media: Drive' && body.issue_type === 'broken_media' && body.external_ref === '/requests/42' && body.description.includes('Problem: Picture or file is broken') && body.description.includes('What needs correction: Visual artefacts or corruption') && body.description.includes('Magent request: #42'))); assert(replaceCalls.every(({ body }) => JSON.stringify(body) === JSON.stringify({ issue_id: 73, file_ids: [77], confirmed: true }))); assert.deepEqual(errors, []); console.log('Passed: completed requests hand off to the guided issue pipeline, create linked issues, start repairs, and preserve global navigation on desktop/mobile. APIs intercepted.'); } finally { await browser.close(); } })().catch(error => { console.error(error); process.exitCode = 1; });