import { expect, test } from '@playwright/test'; import { observeRuntime, PRIMARY_FIXTURE, startNestedStaticServer, type NestedStaticServer, } from './browser-helpers'; test.describe('static application and Toolbox shell', () => { test('runs standalone, stays lazy and supports keyboard-operated shell controls', async ({ page, }) => { const runtime = observeRuntime(page); await page.goto('/'); await expect(page).toHaveTitle('Audio & Video Tools'); await expect( page.getByRole('heading', { level: 1, name: 'Audio & Video Tools', }) ).toBeVisible(); await expect(page.locator('[data-toolbox-context]')).toHaveAttribute( 'data-toolbox-context', 'standalone' ); await expect(page.getByRole('main')).toBeVisible(); await expect( page.getByRole('tab', { name: 'Quick Convert' }) ).toHaveAttribute('aria-selected', 'true'); await expect( page.getByRole('button', { name: 'Choose files' }) ).toBeEnabled(); expect( runtime.requests.some((url) => url.includes('/vendor/ffmpeg/')) ).toBe(false); expect( runtime.requests.some((url) => /\/assets\/worker-[^/]+\.js$/u.test(url)) ).toBe(false); const helpButton = page.getByRole('button', { name: 'Help' }); await helpButton.focus(); await page.keyboard.press('Enter'); const helpDialog = page.getByRole('dialog', { name: 'Audio & Video Tools', }); await expect(helpDialog).toBeVisible(); await expect( helpDialog.getByRole('heading', { level: 2, name: 'Audio & Video Tools', }) ).toBeVisible(); await page.keyboard.press('Escape'); await expect(helpDialog).toBeHidden(); const personalizeButton = page.getByRole('button', { name: 'Personalize', }); await personalizeButton.focus(); await page.keyboard.press('Enter'); await expect( page.getByRole('heading', { level: 2, name: 'Personalize your toolbox', }) ).toBeVisible(); await page.getByRole('button', { name: 'Dark' }).click(); await expect(page.locator('.toolbox-shell').first()).toHaveAttribute( 'data-toolbox-theme', 'dark' ); await page.keyboard.press('Escape'); await expect(personalizeButton).toBeFocused(); const networkUrls = runtime.requests.filter((url) => /^https?:/u.test(url)); expect( networkUrls.every( (url) => new URL(url).origin === 'http://127.0.0.1:4173' ) ).toBe(true); expect(runtime.failedRequests).toEqual([]); expect(runtime.pageErrors).toEqual([]); expect(runtime.consoleErrors).toEqual([]); }); test('keeps preview primary and exposes accessible Edit drawers on narrow screens', async ({ page, }) => { await page.setViewportSize({ width: 390, height: 844 }); await page.goto('/'); await page.getByRole('tab', { name: 'Edit' }).click(); await expect(page.getByRole('heading', { name: 'Preview' })).toBeVisible(); const mediaDrawer = page.locator('#edit-media-drawer'); const inspectorDrawer = page.locator('#edit-inspector-drawer'); await expect(mediaDrawer).toBeHidden(); await expect(inspectorDrawer).toBeHidden(); const sources = page.getByRole('button', { name: 'Sources' }); await sources.click(); await expect(mediaDrawer).toBeVisible(); await expect( mediaDrawer.getByRole('button', { name: 'Close media bin' }) ).toBeFocused(); await page.keyboard.press('Escape'); await expect(mediaDrawer).toBeHidden(); await expect(sources).toBeFocused(); const inspector = page.getByRole('button', { name: 'Inspector' }); await inspector.click(); await expect(inspectorDrawer).toBeVisible(); const closeInspector = inspectorDrawer.getByRole('button', { name: 'Close inspector', }); await expect(closeInspector).toBeFocused(); await closeInspector.click(); await expect(inspectorDrawer).toBeHidden(); await expect(inspector).toBeFocused(); }); test('shows native preview metadata without loading the FFmpeg runtime', async ({ page, }) => { const runtime = observeRuntime(page); await page.goto('/'); await page .locator('input[type="file"][accept*="audio"]') .first() .setInputFiles(PRIMARY_FIXTURE); await expect(page.locator('.quick-preview video')).toBeVisible(); const card = page .locator('.media-card') .filter({ hasText: 'pattern-av.mp4' }); await expect(card.locator('.phase--idle')).toContainText( 'Ready to play · Details on demand' ); await expect(card).toContainText(/\d+:\d{2}\.\d{2}/u); expect( runtime.requests.some((url) => url.includes('/vendor/ffmpeg/')) ).toBe(false); await page.getByRole('tab', { name: 'Edit' }).click(); await expect( page.getByRole('button', { name: 'Inspect & render timeline' }) ).toBeEnabled(); expect( runtime.requests.some((url) => url.includes('/vendor/ffmpeg/')) ).toBe(false); }); }); test.describe('nested static deployment and Toolbox context', () => { let nestedServer: NestedStaticServer; test.beforeAll(async () => { nestedServer = await startNestedStaticServer(); }); test.afterAll(async () => { await nestedServer.close(); }); test('loads a valid same-origin catalog and all runtime assets below the nested path', async ({ page, }) => { const runtime = observeRuntime(page); await page.goto(`${nestedServer.appUrl}?toolbox=../toolbox.catalog.json`); await expect(page.locator('[data-toolbox-context]')).toHaveAttribute( 'data-toolbox-context', 'connected' ); await expect( page.getByRole('link', { name: 'add·ideas Toolbox' }) ).toHaveAttribute('href', `${nestedServer.origin}/deep/nested/`); await page.getByRole('button', { name: 'Apps' }).click(); const appSwitcher = page.getByRole('navigation', { name: 'Toolbox applications', }); await expect( appSwitcher.getByRole('link', { name: 'Browser Test Tool' }) ).toBeVisible(); await expect( appSwitcher.getByRole('link', { name: 'Audio & Video Tools' }) ).toBeVisible(); await page.keyboard.press('Escape'); expect( runtime.requests.some((url) => /\/deep\/nested\/av\/assets\/index-[^/]+\.js$/u.test(url) ) ).toBe(true); expect( runtime.requests.some((url) => /\/deep\/nested\/av\/assets\/index-[^/]+\.css$/u.test(url) ) ).toBe(true); expect( runtime.requests.some((url) => url.includes('/vendor/ffmpeg/')) ).toBe(false); await page.getByText('Engine & browser').click(); await page.getByLabel('Engine mode').selectOption('force-single-thread'); await page.getByRole('button', { name: 'Initialize engine' }).click(); await expect( page .locator('.engine-pill') .filter({ hasText: 'Single-threaded compatibility mode' }) ).toBeVisible({ timeout: 120_000 }); await expect(page.getByText('0.12.15 / 0.12.10')).toBeVisible(); await expect( page.getByText('Encoders').locator('..').locator('dd') ).not.toHaveText('0'); await expect( page.getByText('Filters').locator('..').locator('dd') ).not.toHaveText('0'); const coreScript = runtime.responses.find((response) => response.url.endsWith( '/deep/nested/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/st/ffmpeg-core.js' ) ); const coreWasm = runtime.responses.find((response) => response.url.endsWith( '/deep/nested/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/st/ffmpeg-core.wasm' ) ); expect(coreScript).toMatchObject({ status: 200, contentType: 'text/javascript; charset=utf-8', }); expect(coreWasm).toMatchObject({ status: 200, contentType: 'application/wasm', }); expect( runtime.responses.some((response) => /\/deep\/nested\/av\/assets\/worker-[^/]+\.js$/u.test(response.url) ) ).toBe(true); const networkUrls = runtime.requests.filter((url) => /^https?:/u.test(url)); expect( networkUrls.every((url) => new URL(url).origin === nestedServer.origin) ).toBe(true); expect(runtime.failedRequests).toEqual([]); expect(runtime.pageErrors).toEqual([]); expect(runtime.consoleErrors).toEqual([]); }); test('rejects an invalid same-origin catalog and remains usable standalone', async ({ page, }) => { const runtime = observeRuntime(page); await page.goto(`${nestedServer.appUrl}?toolbox=../invalid-catalog.json`); await expect .poll(() => runtime.consoleWarnings.length, { timeout: 10_000 }) .toBeGreaterThan(0); await expect(page.locator('[data-toolbox-context]')).toHaveAttribute( 'data-toolbox-context', 'standalone' ); await expect( page.getByRole('button', { name: 'Choose files' }) ).toBeEnabled(); expect( runtime.consoleWarnings.some((warning) => warning.includes('Toolbox context unavailable; continuing standalone.') ) ).toBe(true); expect( runtime.requests.some( (url) => url === `${nestedServer.origin}/deep/nested/invalid-catalog.json` ) ).toBe(true); expect(runtime.failedRequests).toEqual([]); expect(runtime.pageErrors).toEqual([]); expect(runtime.consoleErrors).toEqual([]); }); });