import { expect, test } from '@playwright/test'; test('desktop files survive restart, isolate owners and clear only recent cache', async ({ page }) => { await page.goto('/login'); const saved = await page.evaluate(async () => { const source = '/src/features/desktop/offline.ts'; const { DesktopOffline } = await import(source); const track = { id: 'cached-track', title: 'Offline song', available: true, stream_url: 'data:audio/wav;base64,UklGRg==', duration_ms: 1000, artists: [], album: { id: 'album', title: 'Album', cover_url: null }, genres: [], reaction: null }; const session = { client: { POST: async () => ({ data: { items: [track] }, response: new Response('{}') }) } }; const offline = new DesktopOffline('test-server/alice', session); await offline.ready; await offline.save(track, true); await offline.save({ ...track, id: 'recent-track' }); const tracks = offline.tracks(); const result = { count: tracks.length, blob: tracks[0].stream_url.startsWith('blob:'), pinned: offline.pinned(track.id) }; offline.dispose(); return result; }); expect(saved).toEqual({ count: 2, blob: true, pinned: true }); await page.reload(); const restored = await page.evaluate(async () => { const source = '/src/features/desktop/offline.ts'; const { DesktopOffline } = await import(source); const session = { client: { POST: async () => { throw new TypeError('offline'); } } }; const alice = new DesktopOffline('test-server/alice', session); const bob = new DesktopOffline('test-server/bob', session); await Promise.all([alice.ready, bob.ready]); const before = alice.tracks().length; const body = await (await fetch(alice.tracks()[0].stream_url)).blob(); await alice.clearRecent(); const result = { before, bytes: body.size, after: alice.tracks().map((t: { id: string }) => t.id), other: bob.tracks().length }; alice.dispose(); bob.dispose(); return result; }); expect(restored).toEqual({ before: 2, bytes: 4, after: ['cached-track'], other: 0 }); }); test('desktop update dialog hides build reports and launches installer', async ({ page }) => { await page.addInitScript(() => { localStorage.setItem('pipu.locale', 'ru'); (window as Window & { __TAURI__?: unknown }).__TAURI__ = { core: { invoke: async (command: string) => { if (command === 'bootstrap') return { proxyBase: location.origin, server: 'https://music.example' }; if (command === 'update_check') return { build: { version: '1.8.0', size: 1048576 }, notes: '\n> APK SHA-256 abc\n\n## Изменения\n- **Офлайн-музыка**' }; if (command === 'update_install') document.documentElement.dataset.installer = 'opened'; return null; } }, event: { listen: async () => () => undefined }, }; }); await page.goto('/login'); await page.getByLabel('Логин').fill('alice'); await page.getByRole('textbox', { name: 'Пароль' }).fill('alice-password'); await page.getByRole('button', { name: 'Войти' }).click(); const dialog = page.getByRole('dialog'); await expect(dialog).toContainText('Доступна версия 1.8.0'); await expect(dialog).toContainText('• Офлайн-музыка'); await expect(dialog).not.toContainText('android-status'); await expect(dialog).not.toContainText('SHA-256'); await dialog.getByRole('button', { name: 'Скачать и установить' }).click(); await expect(dialog).toContainText('Установщик открыт'); await expect(page.locator('html')).toHaveAttribute('data-installer', 'opened'); });