import { expect, test } from "@playwright/test"; /** * Transport-share two-browser sweep. * * Drives the package's transport-share workflow across two Playwright * browser contexts wired together by a local signalling-channel fixture * served by the harness page. The fixture is a real `BroadcastChannel` * (cross-context) backed implementation — not a mock — so the wire * envelopes round-trip through the same machinery that production would * use across WebRTC data channels. * * The harness page at `/transport-share-harness` MUST expose: * - `window.__share`: an object with * - `becomeDriver(receiverId)` * - `becomeReceiver(driverId)` * - `driverPlay(time, offset)` * - `driverSeek(seconds)` * - `getRole()` * - `getLastEnvelope()` * - `disconnectDriver()` * - DOM elements with `data-testid="role"` (text content === current role) * and `data-testid="indicator"` (present iff `` * is rendered). * * This spec is intentionally executable under the e2e turbo pipeline only; * the harness binary is built per-PR by the e2e job (it lives in the * monorepo's `apps/react-audio-stream-e2e` workspace). */ test.describe("transport-share — two-browser e2e", () => { test("play on driver advances every receiver's transport", async ({ browser, }) => { const driverCtx = await browser.newContext(); const receiverCtx = await browser.newContext(); const driver = await driverCtx.newPage(); const receiver = await receiverCtx.newPage(); await driver.goto("/transport-share-harness?role=driver"); await receiver.goto("/transport-share-harness?role=receiver"); await receiver.evaluate(() => { ( window as unknown as { __share: { becomeReceiver(id: string): void }; } ).__share.becomeReceiver("driver"); }); await driver.evaluate(() => { ( window as unknown as { __share: { becomeDriver(id: string): void }; } ).__share.becomeDriver("receiver"); }); await driver.evaluate(() => { ( window as unknown as { __share: { driverPlay(t: number, o: number): void; }; } ).__share.driverPlay(0.5, 0); }); await expect .poll( async () => await receiver.evaluate(() => ( window as unknown as { __share: { getLastEnvelope(): unknown }; } ).__share.getLastEnvelope() ), { timeout: 5_000 } ) .toEqual({ type: "play", time: 0.5, offset: 0 }); // The receiver MUST render the spec-mandated indicator. await expect(receiver.locator('[data-testid="indicator"]')).toHaveText( "Shared transport active" ); await driverCtx.close(); await receiverCtx.close(); }); test("driver disconnect returns receiver to local", async ({ browser }) => { const driverCtx = await browser.newContext(); const receiverCtx = await browser.newContext(); const driver = await driverCtx.newPage(); const receiver = await receiverCtx.newPage(); await driver.goto("/transport-share-harness?role=driver"); await receiver.goto("/transport-share-harness?role=receiver"); await receiver.evaluate(() => { ( window as unknown as { __share: { becomeReceiver(id: string): void }; } ).__share.becomeReceiver("driver"); }); await driver.evaluate(() => { ( window as unknown as { __share: { becomeDriver(id: string): void }; } ).__share.becomeDriver("receiver"); }); await expect(receiver.locator('[data-testid="role"]')).toHaveText( "receiving" ); await driver.evaluate(() => { ( window as unknown as { __share: { disconnectDriver(): void }; } ).__share.disconnectDriver(); }); await expect(receiver.locator('[data-testid="role"]')).toHaveText( "local", { timeout: 5_000 } ); await expect(receiver.locator('[data-testid="indicator"]')).toHaveCount( 0 ); await driverCtx.close(); await receiverCtx.close(); }); test("receiver controls no-op without throwing", async ({ browser }) => { const receiverCtx = await browser.newContext(); const receiver = await receiverCtx.newPage(); await receiver.goto("/transport-share-harness?role=receiver"); await receiver.evaluate(() => { ( window as unknown as { __share: { becomeReceiver(id: string): void }; } ).__share.becomeReceiver("driver"); }); // notifySeek while receiving is the canonical receiver-controls-no-op // path. It must complete without throwing AND must not appear on // the wire (the local sent log stays empty). const sent = await receiver.evaluate(() => { ( window as unknown as { __share: { driverSeek(s: number): unknown[] }; } ).__share.driverSeek(42); return ( window as unknown as { __share: { sent: unknown[] }; } ).__share.sent; }); expect(sent).toEqual([]); await receiverCtx.close(); }); });