tts-voice-creator-clone-and.../tests/constrained.spec.js

104 lines
5.1 KiB
JavaScript

// @ts-check
// Lightweight constrained-device regression: the browser's 4 GB profile must
// reduce shared batch concurrency before multiple audio/image operations run.
const { test, expect } = require('@playwright/test');
test('4 GB browser profile caps shared batch concurrency at two', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.runPool === 'function', null, { timeout: 15_000 });
const maxActive = await page.evaluate(async () => {
Object.defineProperty(navigator, 'deviceMemory', { configurable: true, value: 4 });
let active = 0;
let max = 0;
await window.runPool(Array.from({ length: 8 }, (_, i) => i), async () => {
active += 1;
max = Math.max(max, active);
await new Promise(resolve => setTimeout(resolve, 15));
active -= 1;
}, 4);
return max;
});
expect(maxActive).toBeLessThanOrEqual(2);
});
test('large character productions keep the rendered DOM paginated', async ({ page }) => {
const characters = Array.from({ length: 250 }, (_, index) => ({
id: 'large-' + index, book: 'Large Smoke Book', name: 'Character ' + index,
tags: '', voice: null, sheet: { gender: index % 2 ? 'F' : 'M' },
}));
await page.route('**/api/characters*', route => route.fulfill({ json: { characters } }));
await page.route('**/api/voices*', route => route.fulfill({ json: [] }));
await page.route('**/api/settings*', route => route.fulfill({ json: {} }));
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#lib-chars-list', { state: 'attached', timeout: 15_000 });
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
await page.evaluate(() => window.navTo('s-library'));
await page.waitForFunction(() => typeof window.libraryRenderCharacters === 'function', null, { timeout: 15_000 });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
await page.evaluate(() => window.navTo('s-library'));
await page.locator('.lib-tab[data-library-view="characters"]').click();
await expect.poll(() => page.locator('#lib-chars-list .lib-char-card').count()).toBeLessThanOrEqual(50);
await page.locator('#lib-chars-list .lib-chars-load-more').click();
await expect.poll(() => page.locator('#lib-chars-list .lib-char-card').count()).toBeLessThanOrEqual(100);
});
test('desktop navigation remains usable under Chrome CPU throttling', async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== 'desktop', 'Chromium CDP throttling is a desktop measurement');
const cdp = await page.context().newCDPSession(page);
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 4 });
try {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 20_000 });
await page.evaluate(() => window.navTo('s-performance'));
await expect(page.locator('#background-jobs-card')).toBeVisible({ timeout: 15_000 });
const metrics = await page.evaluate(() => ({
domNodes: document.getElementsByTagName('*').length,
audioElements: document.querySelectorAll('audio').length,
jsHeapBytes: performance.memory?.usedJSHeapSize || null,
}));
expect(metrics.domNodes).toBeLessThan(20_000);
expect(metrics.audioElements).toBeLessThan(64);
} finally {
await cdp.send('Emulation.setCPUThrottlingRate', { rate: 1 }).catch(() => {});
await cdp.detach().catch(() => {});
}
});
test('voice library cache reuses fresh data and honors explicit refresh', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.fetchVoiceLibrary === 'function', null, { timeout: 15_000 });
const result = await page.evaluate(async () => {
sessionStorage.removeItem('ttsvc_voice_library_cache_v2');
let calls = 0;
const originalFetch = window.fetch;
window.fetch = async (url, options) => {
if (String(url).startsWith('/api/voices')) {
calls += 1;
return new Response(JSON.stringify([{ id: 'cached-voice' }]), { status: 200, headers: { 'Content-Type': 'application/json' } });
}
return originalFetch(url, options);
};
try {
await window.fetchVoiceLibrary({ refresh: true });
await window.fetchVoiceLibrary();
await window.fetchVoiceLibrary({ refresh: true });
return calls;
} finally { window.fetch = originalFetch; }
});
expect(result).toBe(2);
});
test('Rehearser audio cache stays bounded with LRU eviction', async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
await page.evaluate(() => window.navTo('s-rehearser'));
await page.waitForFunction(() => window.rehState?.synthCache, null, { timeout: 15_000 });
const size = await page.evaluate(() => {
const cache = window.rehState.synthCache;
for (let index = 0; index < 180; index += 1) cache.set(index, new Blob(['audio-' + index]));
cache.get(179);
return cache.size;
});
expect(size).toBeLessThanOrEqual(128);
});