Files
ytplayer/tests/audio-watchdog.smoke.spec.js
Claude Worker ffeef5d520 feat: audio playback intermittent issue
Task #50 completed by ClaudeQueue

ClaudeQueue
2026-07-01 01:53:09 +00:00

73 lines
3.1 KiB
JavaScript

/**
* Smoke test for the background-audio-resume watchdog (Task #50).
*
* iOS Safari — most visibly in PWA/standalone mode on iPhone — can silently
* pause a background <audio> element (screen lock, app switch) without the
* app reacting, while a muted foreground <video> element keeps rolling. This
* doesn't try to simulate real OS audio suspension (Playwright/Chromium
* can't), but it does verify:
* 1. The new visibilitychange/pageshow/focus listeners don't throw and are
* wired up without breaking page load.
* 2. Player.resumeIfNeeded() actually resumes a sounding element that is
* paused while intent (_wantsPlaying) is true, and leaves it alone when
* intent is false (a real user pause).
*/
const { test, expect } = require('@playwright/test');
test.describe('Background audio resume watchdog', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('.app', { state: 'attached' });
});
test('page loads with no JS errors and the new listeners are wired', async ({ page }) => {
const errors = [];
page.on('pageerror', (e) => errors.push(e.message));
await page.evaluate(() => {
document.dispatchEvent(new Event('visibilitychange'));
window.dispatchEvent(new Event('pageshow'));
window.dispatchEvent(new Event('focus'));
});
expect(errors).toEqual([]);
});
test('resumeIfNeeded() resumes a silently-paused sounding element while intent is true', async ({ page }) => {
const result = await page.evaluate(async () => {
const audio = document.getElementById('audio');
// Simulate a loaded audio-only track without going through the native
// bridge (not available in this headless smoke context).
audio.src = 'data:audio/mpeg;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAACAAAJmwAeHh4eHh4eHh4eLi4uLi4uLi4uLi5AQEBAQEBAQEBAQFJSUlJSUlJSUlJSZGRkZGRkZGRkZGR2dnZ2dnZ2dnZ2dg==';
Player.mode = 'audio';
Player.master = audio;
Player.secondary = null;
Player._wantsPlaying = true;
audio.pause(); // simulate iOS silently pausing the sounding element
const pausedBefore = audio.paused;
Player.resumeIfNeeded();
// play() is async; give the browser a tick to apply it.
await new Promise((r) => setTimeout(r, 150));
return { pausedBefore, pausedAfter: audio.paused };
});
expect(result.pausedBefore).toBe(true);
expect(result.pausedAfter).toBe(false);
});
test('resumeIfNeeded() leaves a real user pause alone', async ({ page }) => {
const result = await page.evaluate(async () => {
const audio = document.getElementById('audio');
audio.src = 'data:audio/mpeg;base64,//uQxAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAACAAAJmwAeHh4eHh4eHh4eLi4uLi4uLi4uLi5AQEBAQEBAQEBAQFJSUlJSUlJSUlJSZGRkZGRkZGRkZGR2dnZ2dnZ2dnZ2dg==';
Player.mode = 'audio';
Player.master = audio;
Player.secondary = null;
Player._wantsPlaying = false; // user explicitly paused
audio.pause();
Player.resumeIfNeeded();
await new Promise((r) => setTimeout(r, 150));
return { paused: audio.paused };
});
expect(result.paused).toBe(true);
});
});