const { test, expect } = require('@playwright/test'); const { openClassic } = require('./helpers/classic-fixture'); const capture = process.env.YTP_OFFLINE_CAPTURE; async function fixture(page, layout, state) { await openClassic(page); await page.evaluate(({layout,state}) => { data.playlists = [{id:'studio', name:'Studio', videos:Array.from({length:4},(_,i)=>({id:'preview'+String(i).padStart(4,'0'),title:i===2?'An extended conversation about making music and finding a little room to listen':'Studio session '+i,channel:'Preview',thumbnail:'/fixture-cover.svg'}))}]; cachedIds.clear(); downloading.clear(); downloadMeta.clear(); SaveQueue.resumeAll=async()=>{}; data.settings.layout = layout; applyAppearance(); API.cacheList = async () => ({ ok:true, items: state==='downloads-empty' ? [] : data.playlists[0].videos.slice(0,3).map((v,i)=>({id:v.id,size:104857600*(i+1)})), total:state==='downloads-empty'?0:629145600 }); if(state==='downloads-populated') { const videos=data.playlists[0].videos.slice(0,4); const statuses=['active','queued','paused','failed']; localStorage.setItem('ytpSaveQueue',JSON.stringify(Object.fromEntries(videos.map((v,i)=>[v.id,{...v,at:Date.now(),status:statuses[i],error:i===3?'Connection refused':''}])))); videos.slice(0,2).forEach((v,i)=>{downloading.add(v.id);downloadMeta.set(v.id,v);SaveQueue.progress(v.id,i?{phase:'waiting'}:{phase:'downloading',received:10485760,total:104857600});}); } view={type:state.startsWith('downloads')?'downloads':'saved'};render(); },{layout,state}); } for(const layout of ['classic','glass-stage'])for(const width of [390,1440])for(const state of ['downloads-empty','downloads-populated','saved-populated'])test(`${layout} ${state} at ${width}px`,async({page})=>{ await page.setViewportSize({width,height:900});await fixture(page,layout,state); if(state==='saved-populated')await expect(page.locator('.saved-card')).toHaveCount(3); if(state==='downloads-populated')await expect(page.locator('.dl-row')).toHaveCount(4); await page.locator('#listPane').scrollIntoViewIfNeeded(); if(state==='downloads-empty') {await expect(page.locator('#listFilterBar')).toBeHidden();await expect(page.getByRole('button',{name:'View saved videos',exact:true})).toBeVisible();} if(state==='downloads-populated') {await expect(page.getByRole('button',{name:/^Pause /})).toHaveCount(2);await expect(page.getByRole('button',{name:/^Retry /})).toHaveCount(1);} if(capture)await page.screenshot({path:`/tmp/ytplayer-offline-${capture}-${layout}-${width}-${state}.png`}); }); test('download cancellation waits for confirmation and filtering keeps queue intact', async({page})=>{ await fixture(page,'classic','downloads-populated'); await page.locator('.dl-row').first().getByRole('button',{name:/^Cancel /}).click(); await expect(page.getByRole('button',{name:'Cancel save',exact:true})).toBeVisible(); await page.getByRole('button',{name:'Keep',exact:true}).click(); await expect(page.locator('.dl-row')).toHaveCount(4); await page.locator('#listFilterInput').fill('extended'); await expect(page.locator('.dl-row')).toHaveCount(1); expect(await page.evaluate(()=>SaveQueue.pending().length)).toBe(4); });