56 lines
3.5 KiB
JavaScript
56 lines
3.5 KiB
JavaScript
// @ts-check
|
|
// Source & consent panel in the voice inspector (all /api routes mocked; no live writes).
|
|
const { test, expect } = require('@playwright/test');
|
|
|
|
const VOICE = { id: 'EN_F_Test', path: '/voices/EN_F_Test.wav', file_type: 'wav', duration: 8, lang: 'EN', gender: 'F', enabled: true, has_ref: true, transcript: 'hi' };
|
|
|
|
test('unknown badge, validation error text and save payload', async ({ page }) => {
|
|
/** @type {any[]} */ const puts = [];
|
|
await page.route('**/api/**', route => route.request().method() === 'GET' ? route.fulfill({ json: {} }) : route.fulfill({ json: { ok: true } }));
|
|
await page.route('**/api/voices?*', route => route.fulfill({ json: [VOICE] }));
|
|
await page.route('**/api/voices', route => route.fulfill({ json: [VOICE] }));
|
|
await page.route('**/api/voices/EN_F_Test/provenance', route => {
|
|
puts.push(route.request().postDataJSON());
|
|
return puts.length === 1
|
|
? route.fulfill({ status: 400, json: { detail: 'source URL must be http(s) without credentials' } })
|
|
: route.fulfill({ json: { ok: true, provenance: { ...puts[1], set_at: 'now' } } });
|
|
});
|
|
await page.route('**/static/dist/main.min.js*', r => r.abort());
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-voices'));
|
|
const row = page.locator('[data-id="EN_F_Test"]').first();
|
|
await expect(row.locator('.vl-prov-badge')).toBeVisible();
|
|
await row.locator('.vl-name').click();
|
|
const panel = page.locator('.insp-prov-panel');
|
|
await expect(panel.getByRole('heading', { name: 'Source & consent' })).toBeVisible();
|
|
await panel.getByLabel('Basis').selectOption('licensed');
|
|
await panel.getByLabel(/^Source/).fill('ftp://bad');
|
|
const save = panel.getByRole('button', { name: /Save source/ });
|
|
const box = await save.boundingBox();
|
|
expect(box && box.height).toBeGreaterThanOrEqual(44);
|
|
await save.click();
|
|
await expect(panel.getByRole('status')).toContainText('Save failed: source URL must be http(s)');
|
|
await panel.getByLabel(/^Source/).fill('https://example.org/v');
|
|
await save.click();
|
|
await expect(panel.getByRole('status')).toHaveText('Saved.');
|
|
expect(puts[1]).toMatchObject({ basis: 'licensed', source: 'https://example.org/v' });
|
|
});
|
|
|
|
test('record button explains missing microphone permission', async ({ page }) => {
|
|
await page.route('**/api/**', route => route.request().method() === 'GET' ? route.fulfill({ json: {} }) : route.fulfill({ json: { ok: true } }));
|
|
await page.route('**/api/voices*', route => route.fulfill({ json: [VOICE] }));
|
|
await page.route('**/static/dist/main.min.js*', r => r.abort());
|
|
await page.addInitScript(() => {
|
|
navigator.mediaDevices.getUserMedia = () => Promise.reject(Object.assign(new Error('x'), { name: 'NotAllowedError' }));
|
|
});
|
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForFunction(() => typeof window.navTo === 'function', null, { timeout: 15_000 });
|
|
await page.waitForSelector('#app-skeleton', { state: 'detached', timeout: 15_000 });
|
|
await page.evaluate(() => window.navTo('s-voices'));
|
|
await page.locator('[data-id="EN_F_Test"]').first().locator('.vl-name').click();
|
|
await page.getByRole('button', { name: 'Record spoken consent' }).click();
|
|
await expect(page.locator('.insp-prov-panel').getByRole('status')).toContainText('Microphone permission was denied');
|
|
});
|