chrome-extension-test-framework
Version:
Universal testing framework for Chrome extensions - Fast static analysis without browser dependencies
425 lines (363 loc) • 14.2 kB
JavaScript
/**
* Chrome Extension Test Framework
* 汎用的なChrome拡張機能テストフレームワーク
*/
const TestRunner = require('./lib/TestRunner');
const TestSuite = require('./lib/TestSuite');
const TestCase = require('./lib/TestCase');
const Validator = require('./lib/Validator');
const Reporter = require('./lib/Reporter');
const ConfigLoader = require('./lib/ConfigLoader');
const ParallelRunner = require('./lib/ParallelRunner');
const ExcludeManager = require('./lib/ExcludeManager');
const WarningManager = require('./lib/WarningManager');
const ProfileManager = require('./lib/ProfileManager');
const IncrementalTester = require('./lib/IncrementalTester');
const VersionChecker = require('./lib/VersionChecker');
const PerformanceMonitor = require('./lib/PerformanceMonitor');
// フレームワークのバージョン(package.jsonから動的に取得)
const VERSION = require('./package.json').version;
// デフォルト設定
const DEFAULT_CONFIG = {
// テスト対象のディレクトリ
extensionPath: process.cwd(),
// 出力設定
output: {
format: ['console', 'json', 'html'],
directory: './test-results',
filename: 'test-report'
},
// バリデーション設定
validation: {
manifest: true,
permissions: true,
csp: true,
icons: true,
locales: true
},
// カスタムルール
rules: [],
// プラグイン
plugins: [],
// 並列実行
parallel: false,
// タイムアウト
timeout: 30000
};
/**
* メインのフレームワーククラス
*/
class ChromeExtensionTestFramework {
constructor(config = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.configLoader = new ConfigLoader();
this.testRunner = new TestRunner(this.config);
this.reporter = new Reporter(this.config);
this.suites = [];
// 新しいマネージャーを初期化
this.excludeManager = new ExcludeManager(this.config);
this.warningManager = new WarningManager(this.config);
this.profileManager = new ProfileManager(this.config);
this.incrementalTester = new IncrementalTester({
extensionPath: this.config.extensionPath,
excludeManager: this.excludeManager
});
// プロファイルが指定されている場合は適用
if (this.config.profile) {
this.applyProfile(this.config.profile);
}
}
/**
* 設定ファイルから読み込み
*/
async loadConfig(configPath) {
const loadedConfig = await this.configLoader.load(configPath);
this.config = { ...this.config, ...loadedConfig };
this.testRunner.updateConfig(this.config);
// マネージャーを更新
this.excludeManager = new ExcludeManager(this.config);
this.warningManager.updateConfig(this.config);
return this;
}
/**
* プロファイルを適用
*/
applyProfile(profileName) {
this.config = this.profileManager.applyProfile(profileName, this.config);
// マネージャーを更新
this.excludeManager = new ExcludeManager(this.config);
this.warningManager.updateConfig(this.config);
this.testRunner.updateConfig(this.config);
console.log(`📋 Using profile: ${profileName}`);
const profile = this.profileManager.getProfile(profileName);
if (profile.description) {
console.log(` ${profile.description}`);
}
// プロファイルの詳細設定を表示
if (this.config.verbose || profile.name === 'development' || profile.name === 'production') {
console.log(' Profile settings:');
if (profile.skipTests && profile.skipTests.length > 0) {
console.log(` - Skipping tests: ${profile.skipTests.length} tests`);
}
if (profile.additionalTests && profile.additionalTests.length > 0) {
console.log(` - Additional tests: ${profile.additionalTests.join(', ')}`);
}
if (profile.warningLevels) {
const errorLevel = Object.values(profile.warningLevels).filter(v => v === 'error').length;
const warnLevel = Object.values(profile.warningLevels).filter(v => v === 'warn').length;
const ignoreLevel = Object.values(profile.warningLevels).filter(v => v === 'ignore' || v === 'info').length;
console.log(` - Warning levels: ${errorLevel} errors, ${warnLevel} warnings, ${ignoreLevel} ignored`);
}
console.log(` - Fail on warning: ${profile.failOnWarning || false}`);
console.log(` - Fail on error: ${profile.failOnError || false}`);
if (profile.maxFileSize) {
console.log(` - Max file size: ${(profile.maxFileSize / 1000000).toFixed(1)}MB`);
}
}
return this;
}
/**
* テストスイートを追加
*/
addSuite(suite) {
if (suite instanceof TestSuite) {
this.suites.push(suite);
} else if (typeof suite === 'object') {
// オブジェクトからTestSuiteを作成
const suiteOptions = {
...suite,
config: {
...suite.config,
excludeManager: this.excludeManager
}
};
this.suites.push(new TestSuite(suiteOptions));
}
return this;
}
/**
* ビルトインテストを追加
*/
useBuiltinTests() {
const builtinSuites = [
require('./suites/ManifestTestSuite'),
require('./suites/SecurityTestSuite'),
require('./suites/PerformanceTestSuite'),
require('./suites/StructureTestSuite'),
require('./suites/LocalizationTestSuite')
];
builtinSuites.forEach(Suite => {
const suiteConfig = {
...this.config,
excludeManager: this.excludeManager
};
this.addSuite(new Suite(suiteConfig));
});
return this;
}
/**
* プラグインを使用
*/
use(plugin) {
if (typeof plugin === 'function') {
plugin(this);
} else if (plugin.install) {
plugin.install(this);
}
return this;
}
/**
* カスタムバリデーターを追加
*/
addValidator(name, validator) {
Validator.register(name, validator);
return this;
}
/**
* カスタムレポーターを追加
*/
addReporter(name, reporter) {
this.reporter.register(name, reporter);
return this;
}
/**
* テストを実行
*/
async run() {
const startTime = Date.now();
// バージョンチェック(--no-version-checkで無効化可能)
if (!this.config.noVersionCheck) {
const versionChecker = new VersionChecker();
await versionChecker.checkAndNotify();
}
// 並列実行の判定
if (this.config.parallel && this.suites.length > 1) {
return this.runParallel(startTime);
} else {
return this.runSequential(startTime);
}
}
/**
* 順次実行
*/
async runSequential(startTime) {
// Quietモードをグローバルに設定
if (this.config.quiet) {
global.__QUIET_MODE__ = true;
}
// パフォーマンス測定開始
const performanceMonitor = this.testRunner.getPerformanceMonitor();
performanceMonitor.start('total-execution');
// プログレス表示の開始
const totalTests = this.suites.reduce((sum, suite) => sum + suite.tests.length, 0);
this.testRunner.progressReporter.start(this.suites.length, totalTests, this.config.extensionPath);
const results = {
framework: VERSION,
timestamp: new Date().toISOString(),
config: this.config,
suites: []
};
try {
// 各テストスイートを実行
for (const suite of this.suites) {
const suiteResult = await this.testRunner.runSuite(suite);
results.suites.push(suiteResult);
}
// 結果を集計
results.summary = this.calculateSummary(results.suites);
results.duration = Date.now() - startTime;
// すべての警告を集約
results.warnings = [];
results.suites.forEach(suite => {
if (suite.warnings && suite.warnings.length > 0) {
results.warnings.push(...suite.warnings);
}
});
// プログレス表示の完了
this.testRunner.progressReporter.complete(results.summary);
// パフォーマンス測定終了
performanceMonitor.end('total-execution', {
totalTests,
suiteCount: this.suites.length
});
// パフォーマンスレポートを表示
performanceMonitor.printSummary();
// レポートを生成
await this.reporter.generate(results);
// 結果を返す
return results;
} catch (error) {
console.error('❌ Test execution failed:', error);
throw error;
} finally {
// Quietモードのクリーンアップ
if (this.config.quiet) {
global.__QUIET_MODE__ = false;
}
}
}
/**
* 並列実行
*/
async runParallel(startTime) {
console.log('\n🚀 Running tests in parallel mode...\n');
// 並列実行がサポートされているかチェック
if (!ParallelRunner.isSupported()) {
console.warn('⚠️ Parallel execution not supported, falling back to sequential mode');
return this.runSequential(startTime);
}
const parallelRunner = new ParallelRunner({
maxWorkers: ParallelRunner.getOptimalWorkerCount(this.suites.length)
});
// プログレスレポートのイベントリスナー
parallelRunner.on('suite-start', (info) => {
console.log(`🔄 Worker ${info.workerId}: Starting ${info.suite}`);
});
parallelRunner.on('progress', (info) => {
if (info.status === 'passed') {
console.log(` ✅ ${info.test}`);
} else if (info.status === 'failed') {
console.log(` ❌ ${info.test}`);
}
});
parallelRunner.on('suite-complete', (info) => {
console.log(`✅ Worker ${info.workerId}: Completed ${info.suite} (${info.passed} passed, ${info.failed} failed)`);
});
try {
// 並列実行
const parallelResults = await parallelRunner.runSuites(this.suites, this.config);
// 結果を整形
const results = {
framework: VERSION,
timestamp: new Date().toISOString(),
config: this.config,
suites: parallelResults.suites,
summary: parallelResults.summary,
duration: parallelResults.execution.duration,
execution: parallelResults.execution
};
// レポートを生成
await this.reporter.generate(results);
return results;
} catch (error) {
console.error('❌ Parallel test execution failed:', error);
throw error;
}
}
/**
* 結果を集計
*/
calculateSummary(suites) {
const summary = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
errors: [],
warningCount: 0
};
// 警告数をカウント
suites.forEach(suite => {
if (suite.warnings) {
summary.warningCount += suite.warnings.length;
}
});
suites.forEach(suite => {
suite.tests.forEach(test => {
summary.total++;
if (test.status === 'passed') summary.passed++;
else if (test.status === 'failed') summary.failed++;
else if (test.status === 'skipped') summary.skipped++;
if (test.error) {
summary.errors.push({
suite: suite.name,
test: test.name,
error: test.error
});
}
});
});
summary.successRate = summary.total > 0
? Math.round((summary.passed / summary.total) * 100)
: 0;
return summary;
}
/**
* 静的メソッド: クイックテスト
*/
static async test(extensionPath, options = {}) {
const framework = new ChromeExtensionTestFramework({
extensionPath,
...options
});
return framework
.useBuiltinTests()
.run();
}
}
// エクスポート
module.exports = ChromeExtensionTestFramework;
module.exports.TestSuite = TestSuite;
module.exports.TestCase = TestCase;
module.exports.Validator = Validator;
module.exports.VERSION = VERSION;