- 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
154 lines
5.8 KiB
TypeScript
154 lines
5.8 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
test.describe('Chat Roleplay Simulation & Supervision Suite', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.addInitScript(() => {
|
|
window.localStorage.clear();
|
|
window.localStorage.setItem('cognitive-clarity-settings', JSON.stringify({
|
|
voiceName: 'Zephyr',
|
|
vocalStyle: 'Helpful',
|
|
genderPreference: 'Neutral',
|
|
openRouterApiKey: 'sk-or-v1-playwright-test-key',
|
|
roleplayModel: 'google/gemini-3.8-flash',
|
|
supervisorModel: 'google/gemini-3.8-flash'
|
|
}));
|
|
});
|
|
|
|
// Mock OpenRouter API requests deterministically
|
|
await page.route('https://openrouter.ai/api/v1/chat/completions', async (route) => {
|
|
const request = route.request();
|
|
const postData = JSON.parse(request.postData() || '{}');
|
|
|
|
// 1. Streaming Roleplay Chat
|
|
if (postData.stream) {
|
|
const sseBody = [
|
|
'data: {"choices":[{"delta":{"content":"Hey, sorry I didn\'t "}}]}\n\n',
|
|
'data: {"choices":[{"delta":{"content":"get back to you right away. Work was totally overwhelming yesterday."}}]}\n\n',
|
|
'data: [DONE]\n\n'
|
|
].join('');
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'text/event-stream',
|
|
body: sseBody
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 2. Turn Supervisor or Session Report Analysis
|
|
const isSessionReport = postData.messages?.some((m: any) =>
|
|
typeof m.content === 'string' && m.content.includes('SESSION EVALUATION PROMPT')
|
|
);
|
|
|
|
if (isSessionReport) {
|
|
const mockSummaryReport = {
|
|
analysis: {
|
|
distortions: ['Mind Reading'],
|
|
isAdaptive: true,
|
|
evidence: 'User clearly asked for context before jumping to catastrophic conclusions.'
|
|
},
|
|
rating: {
|
|
score: 4,
|
|
reason: 'Demonstrated measured emotional self-regulation and clear communication.'
|
|
},
|
|
suggestions: {
|
|
alternativeThought: 'A delayed message is not proof of abandonment or malice.',
|
|
behavioralStep: 'Practice pausing for 2 minutes before assuming negative intentions.'
|
|
}
|
|
};
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
choices: [{ message: { content: JSON.stringify(mockSummaryReport) } }]
|
|
})
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Turn supervisor analysis
|
|
const mockTurnInsight = {
|
|
analysis: {
|
|
distortions: [],
|
|
isAdaptive: true,
|
|
evidence: 'Checked in calmly without accusation.'
|
|
},
|
|
rating: {
|
|
score: 5,
|
|
reason: 'Balanced and open inquiry.'
|
|
},
|
|
suggestions: {
|
|
alternativeThought: 'Waiting for factual data is adaptive.',
|
|
behavioralStep: 'Continue listening to the friend\'s explanation.'
|
|
}
|
|
};
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
choices: [{ message: { content: JSON.stringify(mockTurnInsight) } }]
|
|
})
|
|
});
|
|
});
|
|
});
|
|
|
|
test('01 - Launch Text Chat, Stream Response, Live Insights, and Export Report', async ({ page }, testInfo) => {
|
|
await page.goto('/');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Click 'Chat' on the first scenario (The Silent Friend)
|
|
const chatBtn = page.getByRole('button', { name: 'Chat' }).first();
|
|
await chatBtn.click();
|
|
|
|
// Verify initializing transition screen
|
|
await expect(page.getByText('Initializing Session')).toBeVisible();
|
|
|
|
// Wait for Chat Session view
|
|
await expect(page.getByText('The Silent Friend')).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText('Complete Interaction')).toBeVisible();
|
|
|
|
const shotDir = path.join(process.cwd(), 'docs/screenshots', testInfo.project.name);
|
|
fs.mkdirSync(shotDir, { recursive: true });
|
|
await page.screenshot({ path: path.join(shotDir, '10-chat-session-initial.png'), fullPage: true });
|
|
|
|
// Send a message
|
|
const chatInput = page.getByPlaceholder('Type your response in character...');
|
|
await chatInput.fill('Hey Sam, no worries at all! Just wanted to check if you were doing okay.');
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Verify bot streamed message appears
|
|
await expect(
|
|
page.getByText('Work was totally overwhelming yesterday')
|
|
).toBeVisible({ timeout: 5000 });
|
|
|
|
// Verify Live Insight Panel updates
|
|
await expect(page.getByText('Live Clinical Insight')).toBeVisible();
|
|
await expect(page.getByText('Waiting for factual data is adaptive')).toBeVisible();
|
|
|
|
await page.screenshot({ path: path.join(shotDir, '11-chat-interaction-streamed.png'), fullPage: true });
|
|
|
|
// Complete the interaction
|
|
await page.getByRole('button', { name: 'Complete Interaction' }).click();
|
|
|
|
// Verify Session Summary Report renders
|
|
await expect(page.getByRole('heading', { name: 'Session Summary Report' })).toBeVisible({ timeout: 6000 });
|
|
await expect(page.getByText('Adaptive Growth Observed')).toBeVisible();
|
|
await expect(page.getByText('Master Reframe')).toBeVisible();
|
|
await expect(page.getByText('Waiting for factual data is adaptive')).toBeVisible();
|
|
|
|
// Verify Export actions are present
|
|
await expect(page.getByTitle('Download Markdown Report')).toBeVisible();
|
|
await expect(page.getByTitle('Download JSON Report')).toBeVisible();
|
|
|
|
await page.screenshot({ path: path.join(shotDir, '12-session-report-summary.png'), fullPage: true });
|
|
|
|
// Close Report and return to home
|
|
await page.getByRole('button', { name: 'Close Report & Return' }).click();
|
|
await expect(page.getByRole('heading', { name: 'Training Scenarios' })).toBeVisible();
|
|
});
|
|
});
|