Update TTSContext to track manual pause epochs, ensuring explicit user pauses are respected during async sentence splitting and playback state restoration. Adjust TTSPlayer button disabling logic to allow pausing while processing. Move and improve pauseTTSAndVerify helper for robust cross-browser TTS pause assertions, replacing local test implementations. Update expectDocumentListed to use polling for document link visibility.
149 lines
5.4 KiB
TypeScript
149 lines
5.4 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test';
|
|
import {
|
|
setupTest,
|
|
playTTSAndWaitForASecond,
|
|
pauseTTSAndVerify,
|
|
openVoicesMenu,
|
|
selectVoiceAndAssertPlayback,
|
|
expectMediaState,
|
|
expectProcessingTransition,
|
|
} from './helpers';
|
|
|
|
async function openSpeedPopover(page: Page) {
|
|
const ttsbar = page.locator('[data-app-ttsbar]');
|
|
const buttons = ttsbar.getByRole('button');
|
|
// Heuristic: the Speed control is the first button in the TTS bar and shows something like "1x"
|
|
const speedBtn = buttons.first();
|
|
await expect(speedBtn).toBeVisible({ timeout: 10000 });
|
|
await speedBtn.click();
|
|
// Popover panel should appear with sliders
|
|
await page.waitForSelector('input[type="range"]', { timeout: 10000 });
|
|
}
|
|
|
|
async function changeNativeSpeedAndAssert(page: Page, newSpeed: number) {
|
|
await openSpeedPopover(page);
|
|
const slider = page.locator('input[type="range"]').first();
|
|
|
|
// Set the slider value programmatically and dispatch events to trigger handlers
|
|
const valueStr = String(newSpeed);
|
|
await slider.evaluate((input: HTMLInputElement, v: string) => {
|
|
input.value = v;
|
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
input.dispatchEvent(new Event('mouseup', { bubbles: true }));
|
|
input.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: 'ArrowRight' }));
|
|
input.dispatchEvent(new Event('touchend', { bubbles: true }));
|
|
}, valueStr);
|
|
|
|
await expectProcessingTransition(page);
|
|
}
|
|
|
|
test.describe('Play/Pause Tests', () => {
|
|
test.beforeEach(async ({ page }, testInfo) => {
|
|
await setupTest(page, testInfo);
|
|
});
|
|
|
|
test.describe.configure({ mode: 'serial', timeout: 60000 });
|
|
|
|
test('plays and pauses TTS for a PDF document', async ({ page }) => {
|
|
// Play TTS for the PDF document
|
|
await playTTSAndWaitForASecond(page, 'sample.pdf');
|
|
|
|
// Pause TTS and verify paused state
|
|
await pauseTTSAndVerify(page);
|
|
});
|
|
|
|
test('plays and pauses TTS for an EPUB document', async ({ page }) => {
|
|
// Play TTS for the EPUB document
|
|
await playTTSAndWaitForASecond(page, 'sample.epub');
|
|
|
|
// Pause TTS and verify paused state
|
|
await pauseTTSAndVerify(page);
|
|
});
|
|
|
|
test('plays and pauses TTS for an DOCX document', async ({ page }) => {
|
|
// Play TTS for the DOCX document
|
|
await playTTSAndWaitForASecond(page, 'sample.docx');
|
|
|
|
// Pause TTS and verify paused state
|
|
await pauseTTSAndVerify(page);
|
|
});
|
|
|
|
test('plays and pauses TTS for a TXT document', async ({ page }) => {
|
|
// Play TTS for the TXT document
|
|
await playTTSAndWaitForASecond(page, 'sample.txt');
|
|
|
|
// Pause TTS and verify paused state
|
|
await pauseTTSAndVerify(page);
|
|
});
|
|
|
|
test('switches to a single voice and resumes playing', async ({ page }) => {
|
|
// Start playback
|
|
await playTTSAndWaitForASecond(page, 'sample.pdf');
|
|
|
|
// Ensure basic TTS controls are present
|
|
await expect(page.getByRole('button', { name: 'Skip backward' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Skip forward' })).toBeVisible();
|
|
|
|
// Open voices list and assert options render
|
|
await openVoicesMenu(page);
|
|
const options = page.getByRole('option');
|
|
expect(await options.count()).toBeGreaterThan(0);
|
|
|
|
await selectVoiceAndAssertPlayback(page, 'af_bella');
|
|
|
|
// Final state should be playing
|
|
await expectMediaState(page, 'playing');
|
|
});
|
|
|
|
test('keeps selected single voice instead of resetting to first option', async ({ page }) => {
|
|
await playTTSAndWaitForASecond(page, 'sample.pdf');
|
|
|
|
await openVoicesMenu(page);
|
|
const options = page.locator('[role="option"]:visible');
|
|
const optionCount = await options.count();
|
|
expect(optionCount).toBeGreaterThan(1);
|
|
|
|
const uniqueVoices: string[] = [];
|
|
for (let i = 0; i < optionCount; i++) {
|
|
const candidate = (await options.nth(i).innerText()).trim();
|
|
if (candidate && !uniqueVoices.includes(candidate)) uniqueVoices.push(candidate);
|
|
}
|
|
expect(uniqueVoices.length).toBeGreaterThan(1);
|
|
|
|
const selectedVoice = uniqueVoices[1] || '';
|
|
expect(selectedVoice).not.toBe('');
|
|
|
|
await selectVoiceAndAssertPlayback(page, selectedVoice);
|
|
|
|
const ttsbar = page.locator('[data-app-ttsbar]');
|
|
await expect(ttsbar.getByRole('button', { name: selectedVoice }).first()).toBeVisible();
|
|
await page.waitForTimeout(1000);
|
|
await expect(ttsbar.getByRole('button', { name: selectedVoice }).first()).toBeVisible();
|
|
});
|
|
|
|
if (!process.env.CI) test('selects multiple Kokoro voices and resumes playing', async ({ page }) => {
|
|
// Start playback
|
|
await playTTSAndWaitForASecond(page, 'sample.pdf');
|
|
|
|
// Ensure TTS controls are present
|
|
await expect(page.getByRole('button', { name: 'Skip backward' })).toBeVisible();
|
|
await expect(page.getByRole('button', { name: 'Skip forward' })).toBeVisible();
|
|
|
|
// Select first voice (e.g., bf_emma) and assert processing -> playing
|
|
await openVoicesMenu(page);
|
|
await selectVoiceAndAssertPlayback(page, 'bf_emma');
|
|
|
|
// Select second voice (e.g., af_heart) to create a multi-voice mix and assert again
|
|
await openVoicesMenu(page);
|
|
await selectVoiceAndAssertPlayback(page, 'af_heart');
|
|
|
|
// Final state should be playing
|
|
await expectMediaState(page, 'playing');
|
|
});
|
|
|
|
test('changing TTS native speed toggles processing and returns to playing', async ({ page }) => {
|
|
await playTTSAndWaitForASecond(page, 'sample.pdf');
|
|
await changeNativeSpeedAndAssert(page, 1.5);
|
|
await expectMediaState(page, 'playing');
|
|
});
|
|
});
|