const { test, expect } = require('@playwright/test'); test('library selection queues LRCLIB, Whisper, LRC export and revision-backed clear jobs', async ({ page }) => { const calls = []; let latestJob = null; await page.route('**/api/**', async route => { const request = route.request(), url = new URL(request.url()); let body = { ok: true }; if (url.pathname === '/api/admin/me') body = { ok: true, enabled: true, admin: true }; if (url.pathname === '/api/uploads') body = { ok: true, uploads: [] }; if (url.pathname === '/api/admin/lyrics/bulk/library') body = { ok: true, total: 2, limit: 50, offset: 0, songs: [ { id: 'dQw4w9WgXcQ', title: 'Amazing grace', channel: 'Choir', lines: 4, cacheStatus: 'ready' }, { id: 'jNQXAC9IVRw', title: 'Saved song', channel: 'Band', lines: 0, cacheStatus: 'failed' }, ] }; if (url.pathname === '/api/admin/lyrics/jobs' && request.method() === 'GET') body = { ok: true, jobs: latestJob ? [latestJob] : [] }; if (url.pathname === '/api/admin/lyrics/jobs' && request.method() === 'POST') { const payload = request.postDataJSON(); calls.push(payload); latestJob = { id: `job-${calls.length}`, action: payload.action, status: 'complete', state: { done: payload.ids?.length || 2, total: payload.ids?.length || 2, results: [{ id: 'dQw4w9WgXcQ', ok: true, message: 'Done' }], fileReady: payload.action === 'export' } }; body = { ok: true, job: latestJob }; } if (url.pathname === '/api/admin/lyrics/jobs/job-1/pause' || url.pathname.endsWith('/resume') || url.pathname.endsWith('/cancel')) body = { ok: true, status: 'paused' }; await route.fulfill({ contentType: 'application/json', body: JSON.stringify(body) }); }); page.on('dialog', dialog => dialog.accept()); await page.goto('/admin.html#library'); await expect(page.locator('#bulkLibraryTable')).toContainText('Amazing grace'); await page.locator('#bulkSelectAll').check(); await page.locator('#bulkAction').selectOption('lrclib'); await page.locator('#bulkStart').click(); await expect.poll(() => calls.length).toBe(1); expect(calls[0]).toMatchObject({ action: 'lrclib', allFiltered: true, query: '' }); await page.locator('#bulkLibraryTable [data-bulk-song="dQw4w9WgXcQ"]').check(); await page.locator('#bulkAction').selectOption('whisper'); await page.locator('#bulkStart').click(); await expect.poll(() => calls.length).toBe(2); expect(calls[1].action).toBe('whisper'); await page.locator('#bulkLibraryTable [data-bulk-song="jNQXAC9IVRw"]').check(); await page.locator('#bulkAction').selectOption('export'); await page.locator('#bulkStart').click(); await expect.poll(() => calls.length).toBe(3); expect(calls[2].action).toBe('export'); await expect(page.locator('#bulkJobs a[href*="/export"]')).toBeVisible(); await page.locator('#bulkLibraryTable [data-bulk-song="dQw4w9WgXcQ"]').check(); await page.locator('#bulkAction').selectOption('clear'); await page.locator('#bulkStart').click(); await expect.poll(() => calls.length).toBe(4); expect(calls[3]).toMatchObject({ action: 'clear', confirmClear: true }); });