Show actual collector search activity in request pipeline
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Isolated UI checks: all API responses are fixtures. No searches or downloads run.
|
||||
const assert = require('node:assert/strict')
|
||||
const { chromium } = require(process.env.REVIEW_PLAYWRIGHT || 'playwright')
|
||||
const base = process.env.REVIEW_BASE || 'http://127.0.0.1:3101'
|
||||
const output = process.env.REVIEW_DIR
|
||||
|
||||
const states = {
|
||||
queued: ['active', 'Search queued', 'Search queued — waiting for Radarr to start.'],
|
||||
searching: ['active', 'Searching', 'Radarr is searching for a matching release.'],
|
||||
idle: ['waiting', 'Not searching', 'Not currently searching for this movie.'],
|
||||
unavailable: ['attention', 'Search unknown', 'Search status unavailable — unable to check Radarr.'],
|
||||
complete: ['complete', 'complete', 'Collection complete — no search needed'],
|
||||
}
|
||||
const fixture = (mode, partial = false) => {
|
||||
const [state, stateLabel, summary] = states[mode]
|
||||
const library = {
|
||||
id: 'library', label: 'Library collection', state: partial ? 'partial' : state, stateLabel,
|
||||
searchStatus: mode === 'complete' ? 'idle' : mode,
|
||||
summary: partial ? '22 of 24 episodes collected. ' + summary : summary,
|
||||
total: partial ? 24 : 1, available: partial ? 22 : mode === 'complete' ? 1 : 0, missing: partial ? 2 : mode === 'complete' ? 0 : 1,
|
||||
}
|
||||
return {
|
||||
request_id: '12', title: 'Search status review', request_type: partial ? 'tv' : 'movie',
|
||||
state: mode === 'complete' ? 'COMPLETED' : 'ADDED_TO_ARR', timeline: [], actions: [],
|
||||
presentation: {
|
||||
status: { label: 'In the library queue', meaning: 'Tracking collection.' },
|
||||
download: { visible: false },
|
||||
nextStep: { title: 'Tracking your request', description: 'The status updates automatically.', actionIds: [] },
|
||||
pipeline: [
|
||||
{ id: 'requested', label: 'Requested', state: 'complete', summary: 'Request received' },
|
||||
{ id: 'approved', label: 'Approved', state: 'complete', summary: 'Approved for collection' },
|
||||
library,
|
||||
{ id: 'search', label: 'Release search', state, summary },
|
||||
{ id: 'download', label: 'Download', state: 'waiting', summary: 'No download attempt yet' },
|
||||
{ id: 'available', label: 'Media server', state: 'waiting', summary: 'Not yet available' },
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
const browser = await chromium.launch({ headless: true })
|
||||
try {
|
||||
const context = await browser.newContext()
|
||||
await context.addCookies([{ name: 'magent_logged_in', value: '1', url: base }])
|
||||
let mode = 'queued'
|
||||
let partial = false
|
||||
let polls = 0
|
||||
const mutations = []
|
||||
const errors = []
|
||||
await context.route('**/api/**', (route) => {
|
||||
const request = route.request()
|
||||
const path = new URL(request.url()).pathname
|
||||
if (request.method() !== 'GET') mutations.push(path)
|
||||
const reply = (json) => route.fulfill({ json })
|
||||
if (path === '/api/auth/me') return reply({ username: 'Review member', role: 'user' })
|
||||
if (path.endsWith('/snapshot')) { polls++; return reply(fixture(mode, partial)) }
|
||||
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 }, requests: [], services: [] })
|
||||
})
|
||||
const page = await context.newPage()
|
||||
page.on('pageerror', (error) => errors.push(error.message))
|
||||
const card = page.locator('.request-stage').filter({ has: page.getByRole('heading', { name: 'Library collection', exact: true }) })
|
||||
const verify = async () => {
|
||||
await card.getByText(states[mode][1], { exact: true }).waitFor()
|
||||
assert.equal(await card.getByRole('progressbar').getAttribute('aria-valuenow'), partial ? '22' : mode === 'complete' ? '1' : '0')
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth), 0)
|
||||
await card.evaluate((element) => element.scrollIntoView({ block: 'center', behavior: 'instant' }))
|
||||
if (output) await card.screenshot({ path: output + `/search-${page.viewportSize().width}-${partial ? 'partial-' : ''}${mode}.png` })
|
||||
}
|
||||
for (const width of [1440, 390]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
for (mode of Object.keys(states)) {
|
||||
await page.goto(base + '/requests/12')
|
||||
await verify()
|
||||
}
|
||||
mode = 'idle'; partial = true
|
||||
await page.goto(base + '/requests/12')
|
||||
await verify()
|
||||
partial = false
|
||||
}
|
||||
// Confirm queued -> searching -> idle updates without a click or page reload.
|
||||
mode = 'queued'
|
||||
await page.goto(base + '/requests/12')
|
||||
await verify()
|
||||
const initialPolls = polls
|
||||
mode = 'searching'
|
||||
await card.getByText('Searching', { exact: true }).waitFor({ timeout: 8000 })
|
||||
await verify()
|
||||
mode = 'idle'
|
||||
await card.getByText('Not searching', { exact: true }).waitFor({ timeout: 8000 })
|
||||
await verify()
|
||||
assert.ok(polls >= initialPolls + 2, 'Active search must refresh automatically')
|
||||
assert.deepEqual(errors, [])
|
||||
assert.deepEqual(mutations, [])
|
||||
console.log('PASS: desktop/mobile search cards, partial collection, and queued → searching → idle automatic updates; no API mutations.')
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
})().catch((error) => { console.error(error); process.exitCode = 1 })
|
||||
Reference in New Issue
Block a user