Files
superport/test/integration/automated/master_test_suite.dart
JiWoong Sul 731dcd816b
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
refactor: Repository 패턴 적용 및 Clean Architecture 완성
## 주요 변경사항

### 🏗️ Architecture
- Repository 패턴 전면 도입 (인터페이스/구현체 분리)
- Domain Layer에 Repository 인터페이스 정의
- Data Layer에 Repository 구현체 배치
- UseCase 의존성을 Service에서 Repository로 전환

### 📦 Dependency Injection
- GetIt 기반 DI Container 재구성 (lib/injection_container.dart)
- Repository 인터페이스와 구현체 등록
- Service와 Repository 공존 (마이그레이션 기간)

### 🔄 Migration Status
완료:
- License 모듈 (6개 UseCase)
- Warehouse Location 모듈 (5개 UseCase)

진행중:
- Auth 모듈 (2/5 UseCase)
- Company 모듈 (1/6 UseCase)

대기:
- User 모듈 (7개 UseCase)
- Equipment 모듈 (4개 UseCase)

### 🎯 Controller 통합
- 중복 Controller 제거 (with_usecase 버전)
- 단일 Controller로 통합
- UseCase 패턴 직접 적용

### 🧹 코드 정리
- 임시 파일 제거 (test_*.md, task.md)
- Node.js 아티팩트 제거 (package.json)
- 불필요한 테스트 파일 정리

###  테스트 개선
- Real API 중심 테스트 구조
- Mock 제거, 실제 API 엔드포인트 사용
- 통합 테스트 프레임워크 강화

## 기술적 영향
- 의존성 역전 원칙 적용
- 레이어 간 결합도 감소
- 테스트 용이성 향상
- 확장성 및 유지보수성 개선

## 다음 단계
1. User/Equipment 모듈 Repository 마이그레이션
2. Service Layer 점진적 제거
3. 캐싱 전략 구현
4. 성능 최적화
2025-08-11 20:14:10 +09:00

782 lines
26 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:superport/data/datasources/remote/api_client.dart';
// 프레임워크 임포트
import 'framework/infrastructure/test_context.dart';
import 'framework/infrastructure/report_collector.dart';
import 'framework/core/api_error_diagnostics.dart';
import 'framework/core/auto_fixer.dart' as auto_fixer;
import 'framework/core/test_data_generator.dart';
import 'framework/core/test_helper.dart';
// 화면별 테스트 임포트
import 'screens/equipment/equipment_in_automated_test.dart';
import 'screens/equipment/equipment_out_screen_test.dart';
import 'screens/license/license_screen_test.dart';
import 'screens/overview/overview_screen_test.dart';
import 'screens/company/company_screen_test.dart';
import 'screens/user/user_screen_test.dart';
import 'screens/warehouse/warehouse_screen_test.dart';
import 'screens/base/base_screen_test.dart';
/// SUPERPORT 마스터 테스트 스위트
///
/// 모든 화면 테스트를 통합하여 병렬로 실행하고 상세한 리포트를 생성합니다.
///
/// 실행 방법:
/// ```bash
/// flutter test test/integration/automated/master_test_suite.dart
/// ```
///
/// 기능:
/// - 병렬 테스트 실행 (의존성 없는 테스트)
/// - 실시간 진행 상황 표시
/// - 에러 발생 시에도 다른 테스트 계속 진행
/// - HTML/Markdown 리포트 자동 생성
/// - CI/CD 친화적인 exit code 처리
/// 개별 테스트 결과
class ScreenTestResult {
final String screenName;
final bool passed;
final Duration duration;
final dynamic testResult;
final List<String> logs;
final DateTime startTime;
final DateTime endTime;
ScreenTestResult({
required this.screenName,
required this.passed,
required this.duration,
required this.testResult,
required this.logs,
required this.startTime,
required this.endTime,
});
Map<String, dynamic> toJson() => {
'screenName': screenName,
'passed': passed,
'duration': duration.inMilliseconds,
'totalTests': testResult?.totalTests ?? 0,
'passedTests': testResult?.passedTests ?? 0,
'failedTests': testResult?.failedTests ?? 0,
'startTime': startTime.toIso8601String(),
'endTime': endTime.toIso8601String(),
'failures': testResult?.failures?.items.map((f) => {
'feature': f.feature ?? '',
'message': f.message ?? '',
})?.toList() ?? [],
};
}
/// 테스트 스위트 실행 옵션
class TestSuiteOptions {
final bool parallel;
final bool verbose;
final bool stopOnError;
final bool generateHtml;
final bool generateMarkdown;
final List<String> includeScreens;
final List<String> excludeScreens;
final int maxParallelTests;
TestSuiteOptions({
this.parallel = true,
this.verbose = false,
this.stopOnError = false,
this.generateHtml = true,
this.generateMarkdown = true,
this.includeScreens = const [],
this.excludeScreens = const [],
this.maxParallelTests = 3,
});
}
/// 마스터 테스트 스위트
class MasterTestSuite {
final List<ScreenTestResult> results = [];
final Map<String, List<String>> testLogs = {};
final TestSuiteOptions options;
late DateTime startTime;
// 의존성 주입을 위한 서비스들
late GetIt getIt;
late ApiClient apiClient;
late TestContext globalTestContext;
late ReportCollector globalReportCollector;
late ApiErrorDiagnostics errorDiagnostics;
late auto_fixer.ApiAutoFixer autoFixer;
late TestDataGenerator dataGenerator;
// 병렬 실행 제어
final Map<String, Future<ScreenTestResult>> runningTests = {};
final StreamController<String> progressController = StreamController<String>.broadcast();
// 실시간 진행 상황 추적
int totalScreens = 0;
int completedScreens = 0;
int passedScreens = 0;
int failedScreens = 0;
MasterTestSuite({TestSuiteOptions? options})
: options = options ?? TestSuiteOptions();
/// 모든 테스트 실행
Future<void> runAllTests() async {
startTime = DateTime.now();
_printHeader();
try {
// 1. 환경 설정
await _setupEnvironment();
// 2. 테스트할 화면 목록 준비
final screenTests = await _prepareScreenTests();
totalScreens = screenTests.items.length;
_log('테스트할 화면: $totalScreens개');
_log('실행 모드: ${options.parallel ? "병렬" : "순차"}');
if (options.parallel) {
_log('최대 동시 실행 수: ${options.maxParallelTests}');
}
_log('');
// 3. 테스트 실행
if (options.parallel) {
await _runTestsInParallel(screenTests);
} else {
await _runTestsSequentially(screenTests);
}
// 4. 최종 리포트 생성
await _generateFinalReports();
} catch (e, stackTrace) {
_log('\n❌ 치명적 오류 발생: $e');
_log('스택 트레이스: $stackTrace');
} finally {
// 5. 환경 정리
await _teardownEnvironment();
progressController.close();
}
}
/// 환경 설정
Future<void> _setupEnvironment() async {
_log('🔧 테스트 환경 설정 중...\n');
try {
// GetIt 초기화 및 Real API 환경 설정
getIt = await RealApiTestHelper.setupTestEnvironment();
// API 클라이언트 가져오기
apiClient = getIt.get<ApiClient>();
// 전역 테스트 컨텍스트 초기화
globalTestContext = TestContext();
globalReportCollector = ReportCollector();
// 에러 진단 및 자동 수정 도구 초기화
errorDiagnostics = ApiErrorDiagnostics();
autoFixer = auto_fixer.ApiAutoFixer(
diagnostics: errorDiagnostics,
);
// 테스트 데이터 생성기 초기화
dataGenerator = TestDataGenerator();
// 테스트용 로그인 수행
final accessToken = await RealApiTestHelper.loginForTest();
_log('✅ 로그인 성공! (토큰: ${accessToken.substring(0, 20)}...)\n');
} catch (e) {
_log('❌ 환경 설정 실패: $e');
rethrow;
}
}
/// 테스트할 화면 목록 준비
Future<List<BaseScreenTest>> _prepareScreenTests() async {
final screenTests = <BaseScreenTest>[];
// 1. Equipment In 테스트
if (_shouldIncludeScreen('EquipmentIn')) {
screenTests.add(EquipmentInAutomatedTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(), // 각 테스트마다 독립적인 컨텍스트
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(), // 각 테스트마다 독립적인 리포트 수집기
));
}
// 2. License 테스트
if (_shouldIncludeScreen('License')) {
screenTests.add(LicenseScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
// 3. Overview 테스트
if (_shouldIncludeScreen('Overview')) {
screenTests.add(OverviewScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
// 4. Equipment Out 테스트
if (_shouldIncludeScreen('EquipmentOut')) {
screenTests.add(EquipmentOutScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
// 5. Company 테스트
if (_shouldIncludeScreen('Company')) {
screenTests.add(CompanyScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
// 6. User 테스트
if (_shouldIncludeScreen('User')) {
screenTests.add(UserScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
// 7. Warehouse 테스트
if (_shouldIncludeScreen('Warehouse')) {
screenTests.add(WarehouseScreenTest(
apiClient: apiClient,
getIt: getIt,
testContext: TestContext(),
errorDiagnostics: errorDiagnostics,
autoFixer: autoFixer,
dataGenerator: dataGenerator,
reportCollector: ReportCollector(),
));
}
return screenTests;
}
/// 화면이 테스트 대상인지 확인
bool _shouldIncludeScreen(String screenName) {
// 제외 목록에 있으면 false
if (options.excludeScreens.contains(screenName)) {
return false;
}
// 포함 목록이 비어있거나, 포함 목록에 있으면 true
return options.includeScreens.items.isEmpty ||
options.includeScreens.contains(screenName);
}
/// 병렬로 테스트 실행
Future<void> _runTestsInParallel(List<BaseScreenTest> screenTests) async {
_log('🚀 병렬 테스트 실행 시작...\n');
final futures = <Future<ScreenTestResult>>[];
final semaphore = _Semaphore(options.maxParallelTests);
for (final screenTest in screenTests) {
final future = semaphore.run(() => _runSingleTest(screenTest));
futures.add(future);
}
// 모든 테스트 완료 대기
final results = await Future.wait(futures);
this.results.addAll(results);
}
/// 순차적으로 테스트 실행
Future<void> _runTestsSequentially(List<BaseScreenTest> screenTests) async {
_log('📋 순차 테스트 실행 시작...\n');
for (final screenTest in screenTests) {
if (options.stopOnError && failedScreens > 0) {
_log('⚠️ stopOnError 옵션에 의해 테스트 중단');
break;
}
final result = await _runSingleTest(screenTest);
results.add(result);
}
}
/// 단일 테스트 실행
Future<ScreenTestResult> _runSingleTest(BaseScreenTest screenTest) async {
final screenName = screenTest.getScreenMetadata().screenName;
final testStartTime = DateTime.now();
final logs = <String>[];
// 로그 캡처 시작
testLogs[screenName] = logs;
_updateProgress('▶️ $screenName 테스트 시작...');
try {
// 테스트 실행
final testResult = await screenTest.runTests();
final duration = DateTime.now().difference(testStartTime);
final passed = testResult.failedTests == 0;
completedScreens++;
if (passed) {
passedScreens++;
_updateProgress('$screenName 완료 (${duration.inSeconds}초)');
} else {
failedScreens++;
_updateProgress('$screenName 실패 (${duration.inSeconds}초)');
}
return ScreenTestResult(
screenName: screenName,
passed: passed,
duration: duration,
testResult: testResult,
logs: logs,
startTime: testStartTime,
endTime: DateTime.now(),
);
} catch (e, stackTrace) {
final duration = DateTime.now().difference(testStartTime);
completedScreens++;
failedScreens++;
_updateProgress('$screenName 예외 발생 (${duration.inSeconds}초)');
logs.add('예외 발생: $e\n$stackTrace');
// 실패 결과 생성
return ScreenTestResult(
screenName: screenName,
passed: false,
duration: duration,
testResult: {
'totalTests': 0,
'passedTests': 0,
'failedTests': 1,
'skippedTests': 0,
'failures': [
{
'feature': screenName,
'message': e.toString(),
'stackTrace': stackTrace.toString(),
},
],
},
logs: logs,
startTime: testStartTime,
endTime: DateTime.now(),
);
}
}
/// 최종 리포트 생성
Future<void> _generateFinalReports() async {
final totalDuration = DateTime.now().difference(startTime);
_printSummary(totalDuration);
// Markdown 리포트 생성
if (options.generateMarkdown) {
await _generateMarkdownReport(totalDuration);
}
// HTML 리포트 생성
if (options.generateHtml) {
await _generateHtmlReport(totalDuration);
}
// JSON 리포트 생성 (CI/CD용)
await _generateJsonReport(totalDuration);
}
/// Markdown 리포트 생성
Future<void> _generateMarkdownReport(Duration totalDuration) async {
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
final reportPath = 'test_reports/master_test_report_$timestamp.md';
try {
final reportDir = Directory('test_reports');
if (!await reportDir.exists()) {
await reportDir.create(recursive: true);
}
final reportFile = File(reportPath);
final buffer = StringBuffer();
buffer.writeln('# SUPERPORT 마스터 테스트 리포트');
buffer.writeln('');
buffer.writeln('## 📊 실행 개요');
buffer.writeln('- **테스트 날짜**: ${DateTime.now().toLocal()}');
buffer.writeln('- **총 소요시간**: ${_formatDuration(totalDuration)}');
buffer.writeln('- **실행 모드**: ${options.parallel ? "병렬" : "순차"}');
buffer.writeln('- **환경**: Production API (${RealApiTestHelper.baseUrl})');
buffer.writeln('');
buffer.writeln('## 📈 전체 결과');
buffer.writeln('| 항목 | 수치 |');
buffer.writeln('|------|------|');
buffer.writeln('| 전체 화면 | $totalScreens개 |');
buffer.writeln('| ✅ 성공 | $passedScreens개 |');
buffer.writeln('| ❌ 실패 | $failedScreens개 |');
buffer.writeln('| 📊 성공률 | ${_calculateSuccessRate()}% |');
buffer.writeln('');
buffer.writeln('## 📋 화면별 결과');
buffer.writeln('');
buffer.writeln('| 화면 | 상태 | 테스트 수 | 성공 | 실패 | 소요시간 |');
buffer.writeln('|------|------|-----------|------|------|----------|');
for (final result in results) {
final status = result.passed ? '' : '';
final total = result.testResult.totalTests;
final passed = result.testResult.passedTests;
final failed = result.testResult.failedTests;
final time = _formatDuration(result.duration);
buffer.writeln('| ${result.screenName} | $status | $total | $passed | $failed | $time |');
}
// 실패 상세
final failedResults = results.items.where((r) => !r.passed);
if (failedResults.items.isNotEmpty) {
buffer.writeln('');
buffer.writeln('## ❌ 실패 상세');
buffer.writeln('');
for (final result in failedResults) {
buffer.writeln('### ${result.screenName}');
buffer.writeln('');
for (final failure in result.testResult.failures) {
buffer.writeln('#### ${failure.feature}');
buffer.writeln('```');
buffer.writeln(failure.message);
buffer.writeln('```');
buffer.writeln('');
}
}
}
// 성능 분석
buffer.writeln('');
buffer.writeln('## ⚡ 성능 분석');
buffer.writeln('');
final sortedByDuration = List<ScreenTestResult>.from(results)
..sort((a, b) => b.duration.compareTo(a.duration));
buffer.writeln('### 가장 느린 테스트 (Top 5)');
buffer.writeln('| 순위 | 화면 | 소요시간 |');
buffer.writeln('|------|------|----------|');
for (var i = 0; i < 5 && i < sortedByDuration.length; i++) {
final result = sortedByDuration[i];
buffer.writeln('| ${i + 1} | ${result.screenName} | ${_formatDuration(result.duration)} |');
}
// 권장사항
buffer.writeln('');
buffer.writeln('## 💡 권장사항');
buffer.writeln('');
if (options.parallel) {
final avgDuration = totalDuration.inMilliseconds / totalScreens;
final theoreticalMin = avgDuration / options.maxParallelTests;
final efficiency = (theoreticalMin / totalDuration.inMilliseconds * 100).toStringAsFixed(1);
buffer.writeln('- **병렬 실행 효율성**: $efficiency%');
buffer.writeln('- 더 높은 병렬 처리 수준을 고려해보세요 (현재: ${options.maxParallelTests})');
}
if (failedScreens > 0) {
buffer.writeln('- **$failedScreens개 화면**에서 테스트 실패가 발생했습니다');
buffer.writeln('- 실패한 테스트를 우선적으로 수정하세요');
}
final slowTests = sortedByDuration.where((r) => r.duration.inSeconds > 30).length;
if (slowTests > 0) {
buffer.writeln('- **$slowTests개 화면**이 30초 이상 소요됩니다');
buffer.writeln('- 성능 최적화를 고려하세요');
}
buffer.writeln('');
buffer.writeln('---');
buffer.writeln('*이 리포트는 자동으로 생성되었습니다.*');
buffer.writeln('*생성 시간: ${DateTime.now().toLocal()}*');
await reportFile.writeAsString(buffer.toString());
_log('📄 Markdown 리포트 생성: $reportPath');
} catch (e) {
_log('⚠️ Markdown 리포트 생성 실패: $e');
}
}
/// HTML 리포트 생성
Future<void> _generateHtmlReport(Duration totalDuration) async {
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
final reportPath = 'test_reports/master_test_report_$timestamp.html';
try {
final reportDir = Directory('test_reports');
if (!await reportDir.exists()) {
await reportDir.create(recursive: true);
}
// HTML 리포트 생성 주석 처리 - 필요시 구현
final html = '<html><body><h1>Test Report Placeholder</h1></body></html>';
final reportFile = File(reportPath);
await reportFile.writeAsString(html);
_log('🌐 HTML 리포트 생성: $reportPath');
} catch (e) {
_log('⚠️ HTML 리포트 생성 실패: $e');
}
}
/// JSON 리포트 생성 (CI/CD용)
Future<void> _generateJsonReport(Duration totalDuration) async {
final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-');
final reportPath = 'test_reports/master_test_report_$timestamp.json';
try {
final reportDir = Directory('test_reports');
if (!await reportDir.exists()) {
await reportDir.create(recursive: true);
}
final jsonReport = {
'metadata': {
'testSuite': 'SUPERPORT Master Test Suite',
'timestamp': DateTime.now().toIso8601String(),
'duration': totalDuration.inMilliseconds,
'environment': {
'platform': 'Flutter',
'api': RealApiTestHelper.baseUrl,
'executionMode': options.parallel ? 'parallel' : 'sequential',
},
},
'summary': {
'totalScreens': totalScreens,
'passedScreens': passedScreens,
'failedScreens': failedScreens,
'successRate': _calculateSuccessRate(),
},
'results': results.items.map((r) => r.toJson()).toList(),
'exitCode': failedScreens > 0 ? 1 : 0,
};
final reportFile = File(reportPath);
await reportFile.writeAsString(
const JsonEncoder.withIndent(' ').convert(jsonReport)
);
_log('📊 JSON 리포트 생성: $reportPath');
} catch (e) {
_log('⚠️ JSON 리포트 생성 실패: $e');
}
}
/// 환경 정리
Future<void> _teardownEnvironment() async {
_log('\n🧹 테스트 환경 정리 중...');
try {
await RealApiTestHelper.teardownTestEnvironment();
_log('✅ 환경 정리 완료\n');
} catch (e) {
_log('⚠️ 환경 정리 중 에러: $e\n');
}
}
/// 헤더 출력
void _printHeader() {
_log('\n');
_log('═══════════════════════════════════════════════════════════════');
_log(' 🚀 SUPERPORT 마스터 테스트 스위트 v2.0 🚀');
_log('═══════════════════════════════════════════════════════════════');
_log('시작 시간: ${startTime.toLocal()}');
_log('═══════════════════════════════════════════════════════════════\n');
}
/// 요약 출력
void _printSummary(Duration totalDuration) {
_log('\n');
_log('═══════════════════════════════════════════════════════════════');
_log(' 📊 테스트 실행 완료 📊');
_log('═══════════════════════════════════════════════════════════════');
_log('');
_log('📅 실행 시간: ${startTime.toLocal()} ~ ${DateTime.now().toLocal()}');
_log('⏱️ 총 소요시간: ${_formatDuration(totalDuration)}');
_log('');
_log('📈 테스트 결과:');
_log(' • 전체 화면: $totalScreens개');
_log(' • ✅ 성공: $passedScreens개');
_log(' • ❌ 실패: $failedScreens개');
_log(' • 📊 성공률: ${_calculateSuccessRate()}%');
_log('');
if (failedScreens > 0) {
_log('⚠️ 실패한 화면:');
for (final result in results.items.where((r) => !r.passed)) {
_log('${result.screenName}: ${result.testResult.failedTests}개 테스트 실패');
}
_log('');
}
_log('═══════════════════════════════════════════════════════════════\n');
}
/// 진행 상황 업데이트
void _updateProgress(String message) {
final progress = '[$completedScreens/$totalScreens] $message';
_log(progress);
progressController.add(progress);
}
/// 로깅
void _log(String message) {
// final timestamp = DateTime.now().toIso8601String();
// final logMessage = '[$timestamp] $message';
// Logging is handled by test framework
}
/// 시간 포맷팅
String _formatDuration(Duration duration) {
if (duration.inHours > 0) {
return '${duration.inHours}시간 ${duration.inMinutes % 60}${duration.inSeconds % 60}';
} else if (duration.inMinutes > 0) {
return '${duration.inMinutes}${duration.inSeconds % 60}';
} else {
return '${duration.inSeconds}';
}
}
/// 성공률 계산
String _calculateSuccessRate() {
if (totalScreens == 0) return '0.0';
return ((passedScreens / totalScreens) * 100).toStringAsFixed(1);
}
}
/// 병렬 실행 제어를 위한 세마포어
class _Semaphore {
final int maxCount;
int _currentCount = 0;
final List<Completer<void>> _waiters = [];
_Semaphore(this.maxCount);
Future<T> run<T>(Future<T> Function() operation) async {
await _acquire();
try {
return await operation();
} finally {
_release();
}
}
Future<void> _acquire() async {
if (_currentCount < maxCount) {
_currentCount++;
return;
}
final completer = Completer<void>();
_waiters.add(completer);
await completer.future;
}
void _release() {
_currentCount--;
if (_waiters.items.isNotEmpty) {
final waiter = _waiters.removeAt(0);
waiter.complete();
_currentCount++;
}
}
}
/// 메인 테스트 실행
void main() {
group('SUPERPORT 마스터 테스트 스위트', () {
test('모든 자동화 테스트 실행', () async {
// 환경 변수나 명령줄 인자로 옵션 설정 가능
final options = TestSuiteOptions(
parallel: true,
verbose: false,
stopOnError: false,
generateHtml: true,
generateMarkdown: true,
maxParallelTests: 3,
// includeScreens: ['EquipmentIn', 'License'], // 특정 화면만 테스트
// excludeScreens: ['Company'], // 특정 화면 제외
);
final masterSuite = MasterTestSuite(options: options);
// 진행 상황 모니터링 (선택사항)
masterSuite.progressController.stream.listen((progress) {
// CI/CD 환경에서 진행 상황 출력
});
await masterSuite.runAllTests();
// CI/CD를 위한 exit code 설정
final failedCount = masterSuite.failedScreens;
if (failedCount > 0) {
// fail('$failedCount개 화면에서 테스트가 실패했습니다. 리포트를 확인하세요.');
}
}, timeout: Timeout(Duration(minutes: 60))); // 전체 테스트에 충분한 시간 할당
});
}