- Simplified loadPersistedVocabularyData() to use only VocabularyProgressManager - Updated calculateVocabularyProgress() to use unified data structure - Removed old system references from knowledge panel data loading - Fixed field names (drsDiscovered, drsMastered) for unified system - Knowledge panel now displays vocabulary progress correctly ✅ TESTED: Vocabulary Knowledge panel working with unified system 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
618 lines
21 KiB
JavaScript
618 lines
21 KiB
JavaScript
/**
|
|
* End-to-End Scenario Testing
|
|
* Tests complete user journeys from start to finish
|
|
*/
|
|
|
|
class E2EScenarioTester {
|
|
constructor() {
|
|
this.results = [];
|
|
this.currentScenario = null;
|
|
this.testContainer = null;
|
|
}
|
|
|
|
/**
|
|
* Initialize E2E testing environment
|
|
*/
|
|
async initialize() {
|
|
console.log('🎬 Initializing E2E Scenario Tests...');
|
|
|
|
// Create results container
|
|
this.testContainer = document.createElement('div');
|
|
this.testContainer.id = 'e2e-test-container';
|
|
this.testContainer.style.cssText = `
|
|
position: fixed;
|
|
bottom: 10px;
|
|
right: 10px;
|
|
width: 400px;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
background: linear-gradient(135deg, #ff9a56 0%, #ff6b9d 100%);
|
|
color: white;
|
|
border-radius: 12px;
|
|
padding: 15px;
|
|
z-index: 10002;
|
|
font-family: system-ui, sans-serif;
|
|
font-size: 12px;
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
|
`;
|
|
document.body.appendChild(this.testContainer);
|
|
|
|
this.log('✅ E2E test environment ready');
|
|
}
|
|
|
|
/**
|
|
* Run all E2E scenarios
|
|
*/
|
|
async runAllScenarios() {
|
|
await this.initialize();
|
|
|
|
this.log('🎬 Starting End-to-End scenario testing...');
|
|
|
|
// Scenario 1: Complete Learning Session
|
|
await this.runScenario('Complete Learning Session', async () => {
|
|
return await this.testCompleteLearningSession();
|
|
});
|
|
|
|
// Scenario 2: Vocabulary Discovery Journey
|
|
await this.runScenario('Vocabulary Discovery Journey', async () => {
|
|
return await this.testVocabularyDiscoveryJourney();
|
|
});
|
|
|
|
// Scenario 3: Smart Guide Full Flow
|
|
await this.runScenario('Smart Guide Full Flow', async () => {
|
|
return await this.testSmartGuideFullFlow();
|
|
});
|
|
|
|
// Scenario 4: Progress Tracking Flow
|
|
await this.runScenario('Progress Tracking Flow', async () => {
|
|
return await this.testProgressTrackingFlow();
|
|
});
|
|
|
|
// Scenario 5: Error Recovery Flow
|
|
await this.runScenario('Error Recovery Flow', async () => {
|
|
return await this.testErrorRecoveryFlow();
|
|
});
|
|
|
|
this.generateE2EReport();
|
|
}
|
|
|
|
/**
|
|
* Test complete learning session flow
|
|
*/
|
|
async testCompleteLearningSession() {
|
|
this.log('📚 Testing complete learning session...');
|
|
|
|
const steps = [
|
|
{
|
|
name: 'Load application',
|
|
test: () => window.app && window.app.getStatus().status === 'running'
|
|
},
|
|
{
|
|
name: 'Access flashcard learning',
|
|
test: async () => {
|
|
// Try to navigate to flashcards
|
|
const router = window.app?.getCore()?.router;
|
|
if (router) {
|
|
router.navigate('/games/flashcard');
|
|
await this.wait(1000);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
name: 'Start flashcard session',
|
|
test: async () => {
|
|
const flashcard = window.app?.getModule?.('flashcardLearning');
|
|
if (!flashcard) return false;
|
|
|
|
try {
|
|
await flashcard.start();
|
|
await this.wait(500);
|
|
return flashcard._isActive;
|
|
} catch (error) {
|
|
this.log(`⚠️ Flashcard start error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Interact with flashcard',
|
|
test: async () => {
|
|
// Simulate rating a card
|
|
const ratingBtn = document.querySelector('[data-rating="good"], .confidence-btn');
|
|
if (ratingBtn) {
|
|
this.simulateClick(ratingBtn);
|
|
await this.wait(300);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
name: 'Complete session',
|
|
test: async () => {
|
|
// Try to end the session
|
|
const flashcard = window.app?.getModule?.('flashcardLearning');
|
|
if (flashcard && flashcard._isActive) {
|
|
await flashcard.stop();
|
|
return !flashcard._isActive;
|
|
}
|
|
return true; // Already stopped
|
|
}
|
|
}
|
|
];
|
|
|
|
return await this.runSteps(steps);
|
|
}
|
|
|
|
/**
|
|
* Test vocabulary discovery journey
|
|
*/
|
|
async testVocabularyDiscoveryJourney() {
|
|
this.log('📖 Testing vocabulary discovery journey...');
|
|
|
|
const steps = [
|
|
{
|
|
name: 'Check prerequisite engine',
|
|
test: () => window.prerequisiteEngine && typeof window.prerequisiteEngine.markWordDiscovered === 'function'
|
|
},
|
|
{
|
|
name: 'Open vocabulary modal',
|
|
test: async () => {
|
|
if (typeof window.showVocabularyKnowledge === 'function') {
|
|
try {
|
|
await window.showVocabularyKnowledge();
|
|
await this.wait(500);
|
|
const modal = document.getElementById('vocabularyModal');
|
|
return modal && modal.style.display !== 'none';
|
|
} catch (error) {
|
|
this.log(`⚠️ Vocabulary modal error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
name: 'Verify progress bars',
|
|
test: () => {
|
|
const modal = document.getElementById('vocabularyModal');
|
|
if (!modal) return false;
|
|
const progressBars = modal.querySelectorAll('.progress-bar');
|
|
return progressBars.length >= 2;
|
|
}
|
|
},
|
|
{
|
|
name: 'Test word discovery',
|
|
test: () => {
|
|
const testWord = 'journey_test_word';
|
|
window.prerequisiteEngine.markWordDiscovered(testWord);
|
|
return window.prerequisiteEngine.isDiscovered(testWord);
|
|
}
|
|
},
|
|
{
|
|
name: 'Test word mastery',
|
|
test: () => {
|
|
const testWord = 'journey_test_word';
|
|
window.prerequisiteEngine.markWordMastered(testWord);
|
|
return window.prerequisiteEngine.isMastered(testWord);
|
|
}
|
|
},
|
|
{
|
|
name: 'Close modal',
|
|
test: async () => {
|
|
const modal = document.getElementById('vocabularyModal');
|
|
if (!modal) return true;
|
|
|
|
const closeBtn = modal.querySelector('.close');
|
|
if (closeBtn) {
|
|
this.simulateClick(closeBtn);
|
|
await this.wait(300);
|
|
return modal.style.display === 'none' || !document.body.contains(modal);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
];
|
|
|
|
return await this.runSteps(steps);
|
|
}
|
|
|
|
/**
|
|
* Test smart guide full flow
|
|
*/
|
|
async testSmartGuideFullFlow() {
|
|
this.log('🧠 Testing smart guide full flow...');
|
|
|
|
const steps = [
|
|
{
|
|
name: 'Check intelligent sequencer',
|
|
test: () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
return sequencer && typeof sequencer.startGuidedSession === 'function';
|
|
}
|
|
},
|
|
{
|
|
name: 'Start guided session',
|
|
test: () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
if (!sequencer) return false;
|
|
|
|
try {
|
|
const session = sequencer.startGuidedSession({
|
|
bookId: 'sbs',
|
|
chapterId: 'sbs-7-8',
|
|
targetLength: 3
|
|
});
|
|
return session && session.id;
|
|
} catch (error) {
|
|
this.log(`⚠️ Session start error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Get exercise recommendation',
|
|
test: async () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
if (!sequencer) return false;
|
|
|
|
try {
|
|
const recommendation = await sequencer.getNextExercise();
|
|
return recommendation && recommendation.type;
|
|
} catch (error) {
|
|
this.log(`⚠️ Recommendation error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Record exercise completion',
|
|
test: () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
if (!sequencer) return false;
|
|
|
|
try {
|
|
sequencer.recordExerciseCompletion(
|
|
{ type: 'text', difficulty: 'medium', bookId: 'sbs', chapterId: 'sbs-7-8' },
|
|
{ timeSpent: 30000, accuracy: 0.8, completed: true }
|
|
);
|
|
return true;
|
|
} catch (error) {
|
|
this.log(`⚠️ Recording error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Get performance insights',
|
|
test: () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
if (!sequencer) return false;
|
|
|
|
try {
|
|
const insights = sequencer.getPerformanceInsights();
|
|
return insights && insights.overallStats;
|
|
} catch (error) {
|
|
this.log(`⚠️ Insights error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Stop guided session',
|
|
test: () => {
|
|
const sequencer = window.app?.getCore()?.intelligentSequencer;
|
|
if (!sequencer) return false;
|
|
|
|
try {
|
|
sequencer.stopGuidedSession();
|
|
return !sequencer.isGuiding();
|
|
} catch (error) {
|
|
this.log(`⚠️ Stop session error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
return await this.runSteps(steps);
|
|
}
|
|
|
|
/**
|
|
* Test progress tracking flow
|
|
*/
|
|
async testProgressTrackingFlow() {
|
|
this.log('📊 Testing progress tracking flow...');
|
|
|
|
const steps = [
|
|
{
|
|
name: 'Load persisted vocabulary data',
|
|
test: async () => {
|
|
if (typeof window.loadPersistedVocabularyData === 'function') {
|
|
try {
|
|
const data = await window.loadPersistedVocabularyData('sbs-7-8');
|
|
return data !== null;
|
|
} catch (error) {
|
|
this.log(`⚠️ Data loading error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
name: 'Calculate vocabulary progress',
|
|
test: async () => {
|
|
if (typeof window.calculateVocabularyProgress === 'function') {
|
|
try {
|
|
const progress = await window.calculateVocabularyProgress(
|
|
'sbs-7-8',
|
|
{},
|
|
window.prerequisiteEngine
|
|
);
|
|
return progress && typeof progress.discoveredCount === 'number';
|
|
} catch (error) {
|
|
this.log(`⚠️ Progress calculation error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
},
|
|
{
|
|
name: 'Export mastery state',
|
|
test: () => {
|
|
if (!window.prerequisiteEngine) return false;
|
|
try {
|
|
const state = window.prerequisiteEngine.exportMasteryState();
|
|
return state && state.masteredWords && state.timestamp;
|
|
} catch (error) {
|
|
this.log(`⚠️ Export error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Import mastery state',
|
|
test: () => {
|
|
if (!window.prerequisiteEngine) return false;
|
|
try {
|
|
const testState = {
|
|
masteredWords: ['test1', 'test2'],
|
|
masteredPhrases: [],
|
|
masteredGrammar: [],
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
window.prerequisiteEngine.importMasteryState(testState);
|
|
return window.prerequisiteEngine.isMastered('test1');
|
|
} catch (error) {
|
|
this.log(`⚠️ Import error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
return await this.runSteps(steps);
|
|
}
|
|
|
|
/**
|
|
* Test error recovery flow
|
|
*/
|
|
async testErrorRecoveryFlow() {
|
|
this.log('🔧 Testing error recovery flow...');
|
|
|
|
const steps = [
|
|
{
|
|
name: 'Test invalid content loading',
|
|
test: async () => {
|
|
if (!window.contentLoader) return false;
|
|
try {
|
|
await window.contentLoader.getChapterContent('invalid', 'invalid-chapter');
|
|
return false; // Should have thrown an error
|
|
} catch (error) {
|
|
// Expected error
|
|
return true;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test AI validation without API',
|
|
test: async () => {
|
|
if (!window.llmValidator) return false;
|
|
try {
|
|
await window.llmValidator.validateAnswer('test', 'test', { timeout: 1000 });
|
|
return false; // Should have thrown an error
|
|
} catch (error) {
|
|
// Expected error without API keys
|
|
return error.message.includes('provider') || error.message.includes('API');
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test invalid module access',
|
|
test: () => {
|
|
try {
|
|
const invalidModule = window.app?.getModule?.('nonexistent-module');
|
|
return invalidModule === null || invalidModule === undefined;
|
|
} catch (error) {
|
|
// Expected error
|
|
return true;
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test graceful UI failures',
|
|
test: () => {
|
|
try {
|
|
// Try to access non-existent UI element
|
|
const element = document.getElementById('nonexistent-element');
|
|
return element === null;
|
|
} catch (error) {
|
|
// Should not throw error
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
return await this.runSteps(steps);
|
|
}
|
|
|
|
/**
|
|
* Run a series of test steps
|
|
*/
|
|
async runSteps(steps) {
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
for (const step of steps) {
|
|
try {
|
|
const result = await step.test();
|
|
if (result) {
|
|
passed++;
|
|
this.log(` ✅ ${step.name}`);
|
|
} else {
|
|
failed++;
|
|
this.log(` ❌ ${step.name}`);
|
|
}
|
|
} catch (error) {
|
|
failed++;
|
|
this.log(` ❌ ${step.name}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
const success = failed === 0;
|
|
const percentage = Math.round((passed / (passed + failed)) * 100);
|
|
|
|
return {
|
|
success,
|
|
passed,
|
|
failed,
|
|
percentage,
|
|
total: passed + failed
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Run a scenario
|
|
*/
|
|
async runScenario(name, testFn) {
|
|
this.currentScenario = name;
|
|
this.log(`\n🎬 Scenario: ${name}`);
|
|
|
|
try {
|
|
const result = await testFn();
|
|
this.results.push({
|
|
name,
|
|
...result,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
|
|
if (result.success) {
|
|
this.log(`✅ Scenario completed: ${result.percentage}% success`);
|
|
} else {
|
|
this.log(`❌ Scenario failed: ${result.percentage}% success`);
|
|
}
|
|
|
|
} catch (error) {
|
|
this.results.push({
|
|
name,
|
|
success: false,
|
|
passed: 0,
|
|
failed: 1,
|
|
percentage: 0,
|
|
total: 1,
|
|
error: error.message,
|
|
timestamp: new Date().toISOString()
|
|
});
|
|
this.log(`❌ Scenario error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate E2E test report
|
|
*/
|
|
generateE2EReport() {
|
|
const totalScenarios = this.results.length;
|
|
const successfulScenarios = this.results.filter(r => r.success).length;
|
|
const overallSuccess = Math.round((successfulScenarios / totalScenarios) * 100);
|
|
|
|
this.log('\n🎬 E2E SCENARIO REPORT');
|
|
this.log('=====================');
|
|
this.log(`Total Scenarios: ${totalScenarios}`);
|
|
this.log(`Successful: ${successfulScenarios} ✅`);
|
|
this.log(`Failed: ${totalScenarios - successfulScenarios} ❌`);
|
|
this.log(`Overall Success: ${overallSuccess}%`);
|
|
|
|
if (overallSuccess >= 80) {
|
|
this.log('🎉 EXCELLENT - Complete user flows work well!');
|
|
} else if (overallSuccess >= 60) {
|
|
this.log('👍 GOOD - Most user flows work');
|
|
} else {
|
|
this.log('⚠️ NEEDS IMPROVEMENT - Major user flow issues');
|
|
}
|
|
|
|
// Add close button
|
|
if (this.testContainer) {
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.textContent = '✖ Close';
|
|
closeBtn.style.cssText = `
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 8px;
|
|
background: rgba(255,255,255,0.2);
|
|
color: white;
|
|
border: 1px solid rgba(255,255,255,0.3);
|
|
border-radius: 4px;
|
|
padding: 4px 8px;
|
|
cursor: pointer;
|
|
font-size: 10px;
|
|
`;
|
|
closeBtn.onclick = () => this.testContainer.remove();
|
|
this.testContainer.appendChild(closeBtn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility methods
|
|
*/
|
|
wait(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
simulateClick(element) {
|
|
if (!element) return;
|
|
const event = new MouseEvent('click', { bubbles: true, cancelable: true });
|
|
element.dispatchEvent(event);
|
|
if (element.onclick) {
|
|
element.onclick.call(element, event);
|
|
}
|
|
}
|
|
|
|
log(message) {
|
|
console.log(message);
|
|
if (this.testContainer) {
|
|
const div = document.createElement('div');
|
|
div.textContent = message;
|
|
div.style.marginBottom = '2px';
|
|
div.style.fontSize = '11px';
|
|
div.style.lineHeight = '1.3';
|
|
this.testContainer.appendChild(div);
|
|
this.testContainer.scrollTop = this.testContainer.scrollHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make available globally
|
|
window.E2EScenarioTester = E2EScenarioTester;
|
|
|
|
window.runE2ETests = async () => {
|
|
const tester = new E2EScenarioTester();
|
|
await tester.runAllScenarios();
|
|
return tester.results;
|
|
};
|
|
|
|
console.log('🎬 E2E Scenario Test Suite loaded. Run with: runE2ETests()'); |