Claude
Skills
Sign in
Back

e2e-streaming

Included with Lifetime
$97 forever

# E2E Livestream Tests

General

What this skill does


# E2E Livestream Tests

Playwright test suite for Constellation v3 livestreaming features. Tests the go-live broadcast flow (stream creation, status transitions, stop), viewer experience (MUX player, public access), in-stream chat, and recording management. Uses `page.route()` to mock MUX and LiveKit API responses so tests run without real streaming infrastructure.

## When to Use This Skill

Use this skill when the user says:
- "test streaming"
- "add streaming e2e tests"
- "e2e streaming"
- "test livestream"
- "playwright streaming tests"
- "test go live"
- "test recordings"

## Prerequisites

- Next.js app with App Router
- `e2e` skill installed (Playwright configured with `playwright.config.ts`)
- `livestream` skill installed (go-live page, viewer page, stream management)
- `stream-mux` skill installed (MUX integration for stream ingest and playback)
- `video-player` skill installed (MUX player component)
- `auth-dev` skill installed (seed users for authentication)
- Dev server running on `localhost:3000`

## Installation

No additional packages required. The `e2e` skill provides `@playwright/test` and the Playwright configuration.

## What Gets Created

```
e2e/
├── fixtures/
│   └── mock-streaming.ts           # MUX & livestream API mock helpers
├── helpers/
│   └── streaming-auth.ts           # Reusable sign-in helper for streaming tests
├── livestream-go-live.spec.ts      # Go-live flow: create, start, stop stream
├── livestream-viewer.spec.ts       # Viewer page: player, title, public access
├── livestream-chat.spec.ts         # In-stream chat: send messages during stream
└── livestream-recording.spec.ts    # Recording: start, status, metadata
```

## Setup Steps

### Step 1: Create `e2e/fixtures/mock-streaming.ts`

Comprehensive mock helpers for MUX API responses, livestream endpoints, and the MUX player script. Provides consistent test data for all streaming tests.

```typescript
import type { Page } from "@playwright/test";

/**
 * Shape of a livestream returned by the API.
 */
type MockLivestream = {
  id: string;
  title: string;
  status: "idle" | "live" | "ended";
  streamKey: string;
  playbackId: string;
  muxLiveStreamId: string;
  livekitRoomName: string;
  createdAt: string;
  startedAt: string | null;
  endedAt: string | null;
};

/**
 * Shape of a MUX live stream from the MUX API.
 */
type MockMuxLiveStream = {
  id: string;
  stream_key: string;
  status: string;
  playback_ids: Array<{ id: string; policy: string }>;
  reconnect_window: number;
  max_continuous_duration: number;
  created_at: string;
};

/**
 * Shape of a MUX asset (recording).
 */
type MockMuxAsset = {
  id: string;
  status: string;
  playback_ids: Array<{ id: string; policy: string }>;
  duration: number;
  created_at: string;
};

/**
 * Shape of a recording returned by the app API.
 */
type MockRecording = {
  id: string;
  livestreamId: string;
  muxAssetId: string;
  playbackId: string;
  status: "preparing" | "ready" | "errored";
  duration: number;
  createdAt: string;
};

const MOCK_LIVESTREAM: MockLivestream = {
  id: "ls-test-001",
  title: "E2E Test Stream",
  status: "idle",
  streamKey: "sk-test-stream-key-abc123",
  playbackId: "pb-test-playback-id",
  muxLiveStreamId: "mux-ls-test-001",
  livekitRoomName: "livestream-ls-test-001",
  createdAt: "2026-02-18T00:00:00.000Z",
  startedAt: null,
  endedAt: null,
};

const MOCK_MUX_LIVE_STREAM: MockMuxLiveStream = {
  id: "mux-ls-test-001",
  stream_key: "sk-test-stream-key-abc123",
  status: "idle",
  playback_ids: [{ id: "pb-test-playback-id", policy: "public" }],
  reconnect_window: 60,
  max_continuous_duration: 43200,
  created_at: "2026-02-18T00:00:00.000Z",
};

const MOCK_MUX_ASSET: MockMuxAsset = {
  id: "mux-asset-test-001",
  status: "ready",
  playback_ids: [{ id: "pb-recording-test", policy: "public" }],
  duration: 3600,
  created_at: "2026-02-18T01:00:00.000Z",
};

const MOCK_RECORDING: MockRecording = {
  id: "rec-test-001",
  livestreamId: MOCK_LIVESTREAM.id,
  muxAssetId: MOCK_MUX_ASSET.id,
  playbackId: "pb-recording-test",
  status: "ready",
  duration: 3600,
  createdAt: "2026-02-18T01:00:00.000Z",
};

/**
 * Intercepts outgoing requests to the MUX API.
 *
 * Mocks:
 * - POST to MUX live streams endpoint -> create live stream
 * - GET MUX live stream -> stream details
 * - POST to MUX assets endpoint -> create asset
 * - MUX webhook events are not intercepted (they're server-to-server)
 */
export async function interceptMuxApi(page: Page): Promise<void> {
  // Intercept MUX REST API calls (these go from the Next.js server to MUX,
  // but if the client makes them directly, catch them here)
  await page.route("**/api.mux.com/**", async (route) => {
    const url = route.request().url();
    const method = route.request().method();

    // Create live stream
    if (url.includes("/video/v1/live-streams") && method === "POST") {
      await route.fulfill({
        status: 201,
        contentType: "application/json",
        body: JSON.stringify({ data: MOCK_MUX_LIVE_STREAM }),
      });
      return;
    }

    // Get live stream
    if (url.includes("/video/v1/live-streams/") && method === "GET") {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({ data: MOCK_MUX_LIVE_STREAM }),
      });
      return;
    }

    // Create asset
    if (url.includes("/video/v1/assets") && method === "POST") {
      await route.fulfill({
        status: 201,
        contentType: "application/json",
        body: JSON.stringify({ data: MOCK_MUX_ASSET }),
      });
      return;
    }

    await route.continue();
  });
}

/**
 * Intercepts the app's livestream API routes.
 *
 * Mocks:
 * - POST /api/video/livestream          -> create a new livestream
 * - GET  /api/video/livestream/:id      -> get livestream details
 * - POST /api/video/livestream/:id/start -> start the stream (status -> live)
 * - POST /api/video/livestream/:id/stop  -> stop the stream (status -> ended)
 * - GET  /api/video/livestream          -> list livestreams
 */
export async function interceptLivestreamApi(page: Page): Promise<void> {
  // Track stream state so start/stop transitions work within a test
  let currentStatus: MockLivestream["status"] = "idle";
  let startedAt: string | null = null;
  let endedAt: string | null = null;

  // Create livestream
  await page.route("**/api/video/livestream", async (route) => {
    const method = route.request().method();

    if (method === "POST") {
      const body = route.request().postDataJSON() as { title?: string };
      currentStatus = "idle";
      startedAt = null;
      endedAt = null;
      await route.fulfill({
        status: 201,
        contentType: "application/json",
        body: JSON.stringify({
          livestream: {
            ...MOCK_LIVESTREAM,
            title: body?.title ?? MOCK_LIVESTREAM.title,
            status: currentStatus,
            startedAt,
            endedAt,
          },
        }),
      });
      return;
    }

    if (method === "GET") {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({
          livestreams: [
            {
              ...MOCK_LIVESTREAM,
              status: currentStatus,
              startedAt,
              endedAt,
            },
          ],
        }),
      });
      return;
    }

    await route.continue();
  });

  // Single livestream operations
  await page.route(/\/api\/video\/livestream\/[^/]+$/, async (route) => {
    if (route.request().method() === "GET") {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify({
          livestream: {
            ...MOCK_LIVESTREAM,
            status: currentStatus,
            startedAt,
            endedAt,
          },
        }),
      });
      return;
    }
    await route.continue();
  });

  // Start livestream
  await page.route(/\/api\/video\/livest
Files: 1
Size: 41.4 KB
Complexity: 36/100
Category: General

Related in General