UNPKG

chrome-extension-test-framework

Version:

Universal testing framework for Chrome extensions - Fast static analysis without browser dependencies

278 lines (233 loc) 9.4 kB
/** * TestRunner - テストの実行を管理 */ const fs = require('fs'); const path = require('path'); const ProgressReporter = require('./ProgressReporter'); const ErrorHandler = require('./ErrorHandler'); const WarningCollector = require('./WarningCollector'); const EssentialTests = require('./EssentialTests'); const PerformanceMonitor = require('./PerformanceMonitor'); class TestRunner { constructor(config) { this.config = config; this.results = []; this.currentSuite = null; this.progressReporter = new ProgressReporter({ showProgress: config.progress !== false && !config.quiet, verbose: config.verbose || false, quiet: config.quiet || false }); this.errorHandler = new ErrorHandler(); this.warningCollector = new WarningCollector(); this.performanceMonitor = new PerformanceMonitor(); } updateConfig(config) { this.config = { ...this.config, ...config }; // ProgressReporterの設定も更新 this.progressReporter.showProgress = this.config.progress !== false && !this.config.quiet; this.progressReporter.verbose = this.config.verbose || false; } /** * テストスイートを実行 */ async runSuite(suite) { this.currentSuite = suite; const suiteResult = { name: suite.name, description: suite.description, startTime: Date.now(), tests: [], warnings: [] }; // パフォーマンス測定開始 this.performanceMonitor.start(`suite-${suite.name}`); // 警告の収集を開始 this.warningCollector.clear(); this.warningCollector.startCapture(); // quietモードの設定(開始時に設定) const wasQuietMode = global.__QUIET_MODE__; if (this.config.quiet) { global.__QUIET_MODE__ = true; } // プログレスレポーターでスイート開始を通知 this.progressReporter.startSuite(suite, suite.tests.length); // beforeAllフックを実行 if (suite.beforeAll) { try { await suite.beforeAll(this.config); } catch (error) { const enhanced = await this.errorHandler.handleError(error, { suite: suite.name, phase: 'beforeAll' }); console.error(this.errorHandler.formatError(enhanced)); } } // 各テストケースを実行 for (const testCase of suite.tests) { const testResult = await this.runTest(testCase, suite); suiteResult.tests.push(testResult); } // afterAllフックを実行 if (suite.afterAll) { try { await suite.afterAll(this.config); } catch (error) { const enhanced = await this.errorHandler.handleError(error, { suite: suite.name, phase: 'afterAll' }); console.error(this.errorHandler.formatError(enhanced)); } } suiteResult.endTime = Date.now(); suiteResult.duration = suiteResult.endTime - suiteResult.startTime; // 警告の収集を停止して結果に追加 this.warningCollector.stopCapture(); suiteResult.warnings = this.warningCollector.getWarnings(); // quietモードを元に戻す global.__QUIET_MODE__ = wasQuietMode; // スイート完了を通知 this.progressReporter.completeSuite(suite.name); // パフォーマンス測定終了 this.performanceMonitor.end(`suite-${suite.name}`, { testCount: suite.tests.length, duration: suiteResult.duration }); return suiteResult; } /** * 個別のテストを実行 */ async runTest(testCase, suite) { const testResult = { name: testCase.name, description: testCase.description, status: 'pending', error: null, startTime: Date.now() }; // パフォーマンス測定開始 this.performanceMonitor.start(`test-${testCase.name}`); // スキップ判定 if (testCase.skip || (testCase.condition && !testCase.condition(this.config))) { testResult.status = 'skipped'; this.progressReporter.completeTest(testCase.name, 'skipped'); return testResult; } // プロファイルによるスキップ判定 if (this.config.skipTests && this.config.skipTests.includes(testCase.name)) { testResult.status = 'skipped'; testResult.skipReason = 'Skipped by profile'; this.progressReporter.completeTest(testCase.name, 'skipped', 'Profile skip'); return testResult; } // クイックモードによるスキップ判定 if (this.config.quickMode || this.config.profile === 'quick') { const isEssential = EssentialTests.isEssential(this.currentSuite?.name, testCase.name); if (!isEssential) { testResult.status = 'skipped'; testResult.skipReason = 'Skipped in quick mode'; this.progressReporter.completeTest(testCase.name, 'skipped'); return testResult; } } // テスト開始を通知 this.progressReporter.startTest(testCase.name); try { // beforeEachフックを実行 if (suite.beforeEach) { await suite.beforeEach(this.config); } // タイムアウト設定 const timeout = testCase.timeout || this.config.timeout || 30000; const testPromise = testCase.test(this.config); // タイムアウトレースを実装 const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error(`Test timeout after ${timeout}ms`)), timeout); }); // テスト実行 await Promise.race([testPromise, timeoutPromise]); testResult.status = 'passed'; this.progressReporter.completeTest(testCase.name, 'passed'); // afterEachフックを実行 if (suite.afterEach) { await suite.afterEach(this.config); } } catch (error) { const enhanced = await this.errorHandler.handleError(error, { suite: this.currentSuite?.name, test: testCase.name, phase: 'test', file: testCase.file }); testResult.status = 'failed'; testResult.error = { message: error.message, stack: error.stack, enhanced: enhanced }; // 詳細エラーをプログレスレポーターに渡す this.progressReporter.completeTest(testCase.name, 'failed', enhanced); } testResult.endTime = Date.now(); testResult.duration = testResult.endTime - testResult.startTime; // パフォーマンス測定終了 this.performanceMonitor.end(`test-${testCase.name}`, { status: testResult.status, duration: testResult.duration }); return testResult; } /** * 拡張機能のファイルを読み込み */ async loadExtensionFile(filePath) { const fullPath = path.join(this.config.extensionPath, filePath); if (!fs.existsSync(fullPath)) { const error = new Error(`File not found: ${filePath}`); error.code = 'ENOENT'; error.path = fullPath; throw error; } return fs.readFileSync(fullPath, 'utf8'); } /** * 拡張機能のJSONファイルを読み込み */ async loadExtensionJSON(filePath) { const content = await this.loadExtensionFile(filePath); try { return JSON.parse(content); } catch (error) { error.file = filePath; error.content = content; throw error; } } /** * ファイルの存在確認 */ async fileExists(filePath) { const fullPath = path.join(this.config.extensionPath, filePath); return fs.existsSync(fullPath); } /** * ディレクトリの内容を取得 */ async readDirectory(dirPath) { const fullPath = path.join(this.config.extensionPath, dirPath); if (!fs.existsSync(fullPath)) { return []; } return fs.readdirSync(fullPath); } /** * パフォーマンスモニターを取得 */ getPerformanceMonitor() { return this.performanceMonitor; } } module.exports = TestRunner;