- Re-architect VoiceSession into full-bleed studio cockpit with overlay slide-over companion drawer - Eliminate width squishing defect on LiveInsightPanel and add native dark/light theme support - Integrate authentic Google Gemini 3.8 Flash TTS with Fenrir persona and fallback audio pipeline - Implement comprehensive Playwright E2E visual regression suite across Desktop, iPad, and iPhone (21 tests, 45 snapshots) - Migrate core application to Svelte 5 runes (, , ) with reactive stores - Maintain .NET 8 Azure Functions serverless endpoints and clean build hygiene
104 lines
3.9 KiB
TypeScript
104 lines
3.9 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
test.describe('Voice Roleplay Simulation & Visualizer Suite', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Stub getUserMedia and AudioContext before loading page
|
|
await page.addInitScript(() => {
|
|
window.localStorage.clear();
|
|
window.localStorage.setItem('cognitive-clarity-settings', JSON.stringify({
|
|
voiceName: 'Kore',
|
|
vocalStyle: 'Calm',
|
|
genderPreference: 'Female',
|
|
openRouterApiKey: 'sk-or-v1-playwright-test-key',
|
|
roleplayModel: 'google/gemini-3.8-flash',
|
|
supervisorModel: 'google/gemini-3.8-flash'
|
|
}));
|
|
|
|
// Mock MediaDevices with real native MediaStream from AudioContext
|
|
if (navigator.mediaDevices) {
|
|
navigator.mediaDevices.getUserMedia = async () => {
|
|
try {
|
|
const AudioCtx = window.AudioContext || (window as any).webkitAudioContext;
|
|
const ctx = new AudioCtx();
|
|
const dest = ctx.createMediaStreamDestination();
|
|
return dest.stream;
|
|
} catch (e) {
|
|
return new MediaStream();
|
|
}
|
|
};
|
|
navigator.mediaDevices.enumerateDevices = async () => [
|
|
{
|
|
deviceId: 'default',
|
|
kind: 'audioinput',
|
|
label: 'Studio Microphone Pro',
|
|
groupId: 'group-1',
|
|
toJSON: () => {}
|
|
} as MediaDeviceInfo
|
|
];
|
|
}
|
|
});
|
|
|
|
// Mock OpenRouter Speech synthesis API
|
|
await page.route('https://openrouter.ai/api/v1/audio/speech', async (route) => {
|
|
// Return empty audio buffer
|
|
const dummyPcm = new Uint8Array(2048);
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'audio/mpeg',
|
|
body: Buffer.from(dummyPcm)
|
|
});
|
|
});
|
|
});
|
|
|
|
test('01 - Launch Voice Session, Visualizer Orb, and Transcript Drawer', async ({ page }, testInfo) => {
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Click 'Voice' on 'Critical Feedback'
|
|
const voiceBtn = page.getByRole('button', { name: 'Voice' }).first();
|
|
await voiceBtn.click();
|
|
|
|
// Verify initializing transition screen
|
|
await expect(page.getByText('Initializing Session')).toBeVisible();
|
|
|
|
// Wait for Voice Session cockpit
|
|
await expect(page.getByText(/Active Roleplay Session/)).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('Begin Voice Simulation')).toBeVisible();
|
|
await expect(page.getByText('Kore Vocal Persona Active')).toBeVisible();
|
|
|
|
const shotDir = path.join(process.cwd(), 'docs/screenshots', testInfo.project.name);
|
|
fs.mkdirSync(shotDir, { recursive: true });
|
|
await page.screenshot({ path: path.join(shotDir, '13-voice-cockpit-initial.png'), fullPage: true });
|
|
|
|
// Click 'Begin Voice Simulation'
|
|
await page.getByRole('button', { name: 'Begin Voice Simulation' }).click();
|
|
|
|
// Toggle Transcript Drawer
|
|
const transcriptToggleBtn = page.getByTitle('Toggle Transcript Drawer');
|
|
await transcriptToggleBtn.click();
|
|
|
|
// Verify Transcript Drawer opens
|
|
await expect(page.getByText('Live Conversation')).toBeVisible();
|
|
|
|
await page.screenshot({ path: path.join(shotDir, '14-voice-transcript-drawer.png'), fullPage: true });
|
|
|
|
// Close Transcript Drawer by toggling it back off
|
|
await transcriptToggleBtn.click();
|
|
await expect(page.getByText('Live Conversation')).not.toBeVisible();
|
|
|
|
// Toggle Mute
|
|
const muteBtn = page.getByTitle('Mute Microphone');
|
|
await muteBtn.click();
|
|
await expect(page.getByTitle('Unmute Microphone')).toBeVisible();
|
|
|
|
// Open Audio Hardware Settings
|
|
const audioSettingsBtn = page.getByTitle('Audio Settings');
|
|
await audioSettingsBtn.click();
|
|
await expect(page.getByText('Select Audio Input Device')).toBeVisible();
|
|
|
|
await page.screenshot({ path: path.join(shotDir, '15-voice-audio-settings-modal.png'), fullPage: true });
|
|
});
|
|
});
|