/** * Smoke test for the "Edit & download" custom-video feature (Task #68). * * The editor lets a user cut parts out of a video and save the result as a * custom offline video that can be added to playlists like any other. This * spec runs against the static frontend (no backend), so it exercises the * pieces that don't need the yt-dlp/ffmpeg server round-trip: * • video-edit.js loads and exposes window.VideoEdit with correct maths. * • The now-playing "Edit & download" button (#editBtn) is present. * • The editor modal opens, accepts a cut, and reports the right final * length via the shared keep-segment maths. */ const { test, expect } = require('@playwright/test'); test.describe('Video editor — Edit & download', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForSelector('.app', { state: 'attached' }); }); test('exposes window.VideoEdit with correct cut/keep maths', async ({ page }) => { const ok = await page.evaluate(() => typeof window.VideoEdit === 'object' && window.VideoEdit !== null); expect(ok).toBe(true); const keep = await page.evaluate(() => window.VideoEdit.invertCuts([{ start: 10, end: 20 }], 60)); expect(keep).toEqual([{ start: 0, end: 10 }, { start: 20, end: 60 }]); const param = await page.evaluate(() => window.VideoEdit.keepToParam(window.VideoEdit.invertCuts([{ start: 10, end: 20 }], 60))); expect(param).toBe('0-10,20-60'); const finalLen = await page.evaluate(() => window.VideoEdit.keepDuration(window.VideoEdit.invertCuts([{ start: 10, end: 20 }], 60))); expect(finalLen).toBe(50); }); test('renders the now-playing Edit & download button', async ({ page }) => { const btn = page.locator('#editBtn'); await expect(btn).toHaveCount(1); await expect(btn).toContainText('Edit'); }); test('editor modal opens for a source video and computes the final length', async ({ page }) => { // Drive openVideoEditor directly with a fake source that has a known // duration, so the test doesn't depend on network playback. OPFS may be // absent in the test browser; if so the editor toasts and returns — assert // whichever path this browser takes so the test is deterministic. const supported = await page.evaluate(() => !!(window.OPFS && window.OPFS.isSupported && window.OPFS.isSupported())); const opened = await page.evaluate(() => { window.openVideoEditor({ id: 'testsource1', title: 'Sample', duration: 100, thumbnail: '' }); const modal = document.getElementById('modal'); return modal && !modal.classList.contains('hidden'); }); if (!supported) { // Without OPFS the editor declines to open — that's the correct guard. expect(opened).toBe(false); return; } expect(opened).toBe(true); // Add a cut 0:10–0:40 → final length should be 100 - 30 = 70s = "1:10". await page.fill('.video-editor .ve-from', '0:10'); await page.fill('.video-editor .ve-to', '0:40'); await page.click('.video-editor .ve-add'); await expect(page.locator('.video-editor .ve-cut')).toHaveCount(1); await expect(page.locator('.video-editor .ve-summary')).toContainText('1:10'); }); });