domain project / streaming
Video Streaming
Test strategies for content catalogues, video playback, DRM, subscription management, and adaptive bitrate streaming.
Content catalogue & searchVideo playbackSubscription & billingDRM & access controlAdaptive bitrateWatch history & recommendations
Domain Overview
Video streaming platforms must guarantee playback continuity, enforce content licensing through DRM, and gate premium content behind subscriptions. Testing spans CDN-delivered manifests, browser-native media APIs, licence server integrations, and billing lifecycle edge cases.
Typical stack: React / Web Components player, HLS/DASH manifest via CDN, Widevine/FairPlay DRM licence server, Stripe subscription billing, recommendation microservice.
Key Test Areas
| Area | What to test | Risk |
|---|---|---|
| Catalogue | Search, browse, genre filters, series/episode structure, metadata accuracy | User can’t find content |
| Playback | Play/pause/seek, quality switching, captions/subtitles, autoplay next episode | Black screen, spinner loop |
| Auth & access | Subscription gates, free vs premium content, concurrent device limits | Unauthorised access, paying user blocked |
| DRM | Valid licence loads, error on expired/invalid licence, offline download licence | Content pirates, legitimate user errors |
| Billing | Free trial, subscription start, cancellation, invoice generation, card update | Surprise charges, failed renewals |
| Recommendations | Personalised row appears, no crash when watch history is empty | Broken homepage for new users |
Test Pyramid for Video Streaming
Unit (60%): Manifest URL builder, DRM key-system selection, billing proration logic
Integration (30%): Licence server handshake, billing webhook handler, catalogue search API
E2E (10%): Login → browse catalogue → play video → verify captions → autoplay next
Critical E2E tests (must pass before every deploy):
- Premium content is accessible to subscribed user and blocked for free user
- Video player plays and seek to 30s works without buffering loop
- Captions toggle on/off and track language can be changed
- Subscription cancellation stops renewal charge
- New user homepage renders without watch history (no crash)
Common Failure Scenarios
1. Video player shows spinner forever when stream URL returns 403
Test: intercept manifest URL with page.route(), return 403, assert error message visible not infinite spinner
2. Subtitle timing offset after seek
Test: seek to 00:01:30, wait for caption element, assert caption text matches expected line for that timestamp
3. Concurrent device limit not enforced
Test: open same account in two browser contexts, play in both, assert second context receives device-limit error
4. Trial cancellation still charges card at period end
Test: mock billing webhook for trial_end event after cancellation, assert no invoice created
5. Autoplay doesn't trigger for next episode when DRM licence not pre-fetched
Test: let episode end, intercept licence request timing, assert next episode starts within 3s
Playwright Patterns for Video Streaming
// Intercept manifest, return 403, verify player shows error not spinner
test('player shows error message on 403 manifest', async ({ page }) => {
await page.route('**/*.m3u8', route => route.fulfill({ status: 403 }));
await page.goto('/watch/episode-1');
await page.getByRole('button', { name: 'Play' }).click();
// Error message must appear; spinner must not persist
await expect(page.getByTestId('player-error')).toBeVisible({ timeout: 5000 });
await expect(page.getByTestId('player-spinner')).toBeHidden();
});
// Captions toggle via accessible role
test('captions can be toggled on and off', async ({ page }) => {
await page.goto('/watch/episode-1');
await page.getByRole('button', { name: 'Play' }).click();
const captionsBtn = page.getByRole('button', { name: /captions/i });
await captionsBtn.click();
await expect(page.getByTestId('caption-track')).toBeVisible();
await captionsBtn.click();
await expect(page.getByTestId('caption-track')).toBeHidden();
});