// Actual packaged dashboard and API. Only signer is synthetic; never native UAT. import { chromium, expect } from '@playwright/test'; import { createRequire } from 'node:module'; // Reuse the exact Podsteadr image's public dependency bytes, not a new npm install. const { finalizeEvent, getPublicKey } = createRequire('/signer/package.json')('nostr-tools/pure'); import assert from 'node:assert/strict'; const base = 'http://127.0.0.1:8095'; const allowed = new Set([base, 'http://127.0.0.1:3000', 'http://127.0.0.1:8888']); const secret = new Uint8Array(32).fill(1); let signatures = 0; const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_EXECUTABLE, headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); try { const context = await browser.newContext({ serviceWorkers: 'block', viewport: { width: 390, height: 844 } }); await context.route('**/*', route => allowed.has(new URL(route.request().url()).origin) ? route.continue() : route.abort()); await context.routeWebSocket('**/*', socket => socket.close()); await context.exposeFunction('fixtureSign', event => { assert.equal(event.kind, 27235, 'Fixture must never sign a publication/payment'); assert.ok(event.tags.some(t => t[0] === 'u' && t[1] === base+'/api/auth/login')); assert.ok(event.tags.some(t => t[0] === 'method' && t[1] === 'POST')); signatures++; return finalizeEvent(event, secret); }); await context.addInitScript(pubkey => { // This scenario substitutes only the signer, explicitly. Canonical bridge // bytes/inclusion are checked separately; no native-broker credit is claimed. window.__archipelagoNostr = true; window.nostr = { getPublicKey: async () => pubkey, signEvent: event => window.fixtureSign(event) }; }, getPublicKey(secret)); const page = await context.newPage(); const errors = []; page.on('pageerror', error => errors.push(error.message)); await page.goto(base+'/login'); assert.equal(await page.locator('script[src="/nostr-provider.js"][data-app-id="podsteadr"][data-no-nip98]').count(), 1); await page.getByRole('button', { name: 'Log in with nostr', exact: true }).click(); await page.waitForURL(base+'/'); assert.equal(signatures, 1); const streams = await (await context.request.get(base+'/api/streams')).json(); assert.equal(streams.length, 1); const stream = streams[0]; await page.goto(base+'/streams/'+stream.id); await expect(page.getByRole('heading', { name: stream.title, exact: true })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Recordings', exact: true })).toBeVisible(); const target = page.getByLabel('Publish to podcast', { exact: true }); await target.selectOption({ label: 'Synthetic recording qualification' }); const podcastId = await target.inputValue(); const pending = page.waitForResponse(r => r.url() === base+'/api/streams/'+stream.id+'/recordings/publish' && r.request().method() === 'POST'); await page.getByRole('button', { name: 'Publish as episode', exact: true }).first().click(); const response = await pending; assert.equal(response.status(), 201); const episode = await response.json(); assert.equal(episode.price_sats, null); await expect(page.getByText('Published! Feed:', { exact: false })).toBeVisible(); const feed = page.getByRole('link', { name: base+'/feeds/'+podcastId+'/feed.xml', exact: true }); await expect(feed).toBeVisible(); assert.equal((await context.request.get(episode.enclosure_url)).status(), 200); for (const width of [390, 1440]) { await page.setViewportSize({ width, height: 900 }); await page.reload(); await expect(page.getByRole('heading', { name: 'Recordings', exact: true })).toBeVisible(); assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth+1), false, `horizontal overflow at ${width}px`); } const podcast = await (await context.request.get(base+'/api/podcasts/'+podcastId)).json(); assert.equal(podcast.episodes.length, 2); assert.ok(podcast.episodes.some(e => e.id === episode.id)); assert.equal(signatures, 1, 'Reload should retain the actual cookie session'); assert.deepEqual(errors, []); await context.close(); console.log('PASS actual packaged dashboard synthetic-signer login→recording selection→free episode publication→feed, mobile/desktop reload, real cookie persistence; no native-identity/physical acceptance'); } finally { await browser.close(); }