Skip to content

Commit fe09d6a

Browse files
Implement ensemble test framework (#2258)
* Enhance ensemble test framework with new features and improvements - Updated .gitignore to include IntelliJ/Android Studio module files and runtime-generated GetStorage files. - Added new scripts in melos.yaml for running Ensemble YAML tests and generating test schemas. - Introduced a reset method in Ensemble class to clear singleton state between tests. - Implemented a new ensemble_test_runner package with a CLI for running declarative YAML tests. - Added extensive documentation for the new test runner, including a step vocabulary and JSON schema for test files. - Developed assertion engine and extended step handlers to support a wider range of test actions and assertions. This commit lays the groundwork for a more robust testing framework for Ensemble applications. * Update README and schema descriptions to reflect app-local test paths; refactor test discovery and execution to support new directory structure. Add new test cases for helloApp and remove legacy tests. Enhance YAML test app patcher for dynamic test directory resolution. * Update schema references and regenerate ensemble_tests_schema.json for app-local tests. Modify documentation and YAML files to reflect new schema path and ensure consistency across the project. * Remove generated Flutter environment files to clean up the project structure. * Enhance YAML test app patcher to validate test directory configuration and presence of test files. Add error handling for missing configuration and implement tests to verify directory resolution and test file detection. * Implement error handling for missing declarative tests in CLI and enhance output extraction for known failures. Update tests to verify new functionality. * Remove additional generated Flutter environment files to further clean up the project structure. * Enhance ensemble test runner CLI with validation and reporting features. Add `--doctor` command for setup validation, implement JSON reporting options, and improve error messages for widget visibility and API calls. Update documentation with usage examples and clarify test directory requirements. * Enhance ensemble test runner with new features for test authoring and validation. Add commands for app inspection, test scaffolding, and improved reporting options including JUnit format. Update README and documentation to reflect new functionalities and usage examples. Modify schema to include additional metadata fields for tests. * Update ensemble dependency to version v1.2.46 in pubspec.yaml * Implement timeout feature for ensemble test runner CLI. Add `--timeout` argument to specify test suite duration, with default set to 10 minutes. Update README with usage examples and modify CLI output handling to accommodate timeout settings. * Add agent debug logging functionality to ensemble test runner Introduce a new `agentDebugLog` function for logging debug information during test execution. This includes logging key events such as test entry, execution plan readiness, configuration building, and widget pumping. Update relevant files to integrate logging at various stages of the test harness and runner processes, enhancing traceability and debugging capabilities. * Enhance local definition provider with detailed agent logging Add comprehensive logging throughout the LocalDefinitionProvider class to track the loading of app configurations, themes, widgets, scripts, and actions. This includes logging before and after significant operations to improve traceability and debugging capabilities. Update the ensemble test harness to log the state before and after updating the app bundle, ensuring better visibility during test execution. * Add agent debug logging to CLI test execution Integrate additional logging within the `runEnsembleYamlTestsCli` function to capture key events during the CLI startup and before the Flutter test process. This enhancement improves traceability and debugging capabilities by logging the current working directory and arguments passed to the CLI. * Add agent debug logging throughout assertion engine, entry point, and test reporter Integrate detailed logging using the `agentDebugLog` function in the AssertionEngine, EnsembleTestEntry, and TestReporter classes. This enhancement captures key events and state information during test execution, improving traceability and debugging capabilities across the test runner's workflow. * Refactor local provider and test runner by removing agent debug logging Eliminate the `agentDebugLog` function calls from the LocalDefinitionProvider, AssertionEngine, and EnsembleTestRunner classes to streamline the codebase. This cleanup enhances readability and maintainability by removing unnecessary logging statements while retaining core functionality. Additionally, update the test harness to mock various method channels for improved testing capabilities. * Update ensemble test harness to allow loading of app bundle through existing config path. This change preserves test-installed API providers and avoids unnecessary initialization of real providers, enhancing the test execution environment. * Refactor LocalDefinitionProvider to use rootBundle for asset loading Replace custom asset loading method with rootBundle.loadString for improved consistency and performance in loading local assets. This change enhances the readability of the code and aligns with best practices for asset management in Flutter applications. * Refactor LocalDefinitionProvider to utilize a centralized asset loading method Replace direct calls to rootBundle.loadString with a new _loadLocalAssetString method for improved consistency and maintainability in loading local assets. This change enhances code readability and aligns with best practices for asset management in Flutter applications. * Add temporary storage path for tests in EnsembleTestHarness Introduce a static variable for a temporary storage path in the EnsembleTestHarness class, enhancing the test environment by directing certain method channel calls to this path instead of the current directory. This change improves test isolation and management of temporary files during test execution.
2 parents 52ab49b + 55bf777 commit fe09d6a

69 files changed

Lines changed: 16390 additions & 65 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ starter/ios/Runner.xcodeproj/project.pbxproj
1111
starter/node_modules
1212
starter/package-lock.json
1313

14+
# IntelliJ / Android Studio module files (generated by Melos / IDE)
15+
*.iml
16+
1417
# FVM Version Cache
1518
.fvm/
1619

@@ -31,6 +34,14 @@ starter/package-lock.json
3134
starter/android/app/src/main/java/io/
3235
starter/ios/Runner/GeneratedPluginRegistrant.h
3336
starter/ios/Runner/GeneratedPluginRegistrant.m
37+
starter/macos/Flutter/ephemeral/Flutter-Generated.xcconfig
38+
starter/macos/Flutter/ephemeral/flutter_export_environment.sh
39+
40+
# GetStorage local persistence (runtime-generated when running starter)
41+
starter/GetStorage.gs
42+
starter/GetStorage.bak
43+
starter/system.gs
44+
starter/system.bak
3445

3546
# remove molos .iml files
36-
**/melos_*.iml
47+
**/melos_*.iml

melos.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ ignore:
1515
- modules/ensemble_ts_interpreter
1616
- "**/build/**"
1717

18+
scripts:
19+
ensemble:test:
20+
description: Run declarative Ensemble YAML tests in starter.
21+
run: cd starter && dart run ensemble_test_runner:ensemble_test
22+
23+
ensemble:generate-test-schema:
24+
description: Regenerate ensemble_tests_schema.json from the step vocabulary.
25+
run: |
26+
cd packages/ensemble_test_runner && dart run tool/generate_schema.dart
27+
28+
ensemble:generate-step-registry:
29+
description: Regenerate test_step_registry.dart from tool/generate_step_registry.dart.
30+
run: |
31+
cd packages/ensemble_test_runner && dart run tool/generate_step_registry.dart
32+
1833
command:
1934
version:
2035
updateGitTagRefs: true

modules/ensemble/lib/ensemble.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ class Ensemble extends WithEnsemble with EnsembleRouteObserver {
9898
late FirebaseApp ensembleFirebaseApp;
9999
static final Map<String, dynamic> externalDataContext = {};
100100

101+
/// Clears [initManagers] singleton state between Flutter widget tests.
102+
static void resetInitManagersForTest() {
103+
_instance._completer = null;
104+
}
105+
101106
/// initialize all the singleton/managers. Note that this function can be
102107
/// called multiple times since it's being called inside a widget.
103108
/// The actual code block to initialize the managers is guaranteed to run

modules/ensemble/lib/ensemble_app.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
159159
bool _hasInternet = true;
160160
late final StreamSubscription<List<ConnectivityResult>>
161161
_connectivitySubscription;
162+
SemanticsHandle? _testSemanticsHandle;
162163

163164
@override
164165
void initState() {
@@ -195,7 +196,7 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
195196
}
196197
});
197198
if (EnvConfig().isTestMode) {
198-
SemanticsBinding.instance.ensureSemantics();
199+
_testSemanticsHandle = SemanticsBinding.instance.ensureSemantics();
199200
}
200201
}
201202

@@ -245,6 +246,7 @@ class EnsembleAppState extends State<EnsembleApp> with WidgetsBindingObserver {
245246
@override
246247
void dispose() {
247248
_connectivitySubscription.cancel();
249+
_testSemanticsHandle?.dispose();
248250
WidgetsBinding.instance.removeObserver(this);
249251
super.dispose();
250252
}

modules/ensemble/lib/framework/definition_providers/local_provider.dart

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'dart:io';
21
import 'dart:ui';
32

43
import 'package:ensemble/action/action_scope_util.dart';
@@ -13,7 +12,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
1312
import 'package:flutter_i18n/loaders/decoders/yaml_decode_strategy.dart';
1413
import 'package:yaml/yaml.dart';
1514
import 'dart:convert';
16-
import 'package:flutter/foundation.dart' as foundation;
1715

1816
/**
1917
* Store all the definitions and assets locally together with the App
@@ -43,8 +41,7 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
4341
}
4442
// Note: Web with local definition caches even if we disable browser cache
4543
// so you may need to re-run the app on definition changes
46-
var pageStr =
47-
await rootBundle.loadString('${path}screens/$screen.yaml');
44+
var pageStr = await _loadLocalAssetString('${path}screens/$screen.yaml');
4845
if (pageStr.isEmpty) {
4946
return ScreenDefinition(YamlMap());
5047
}
@@ -55,7 +52,7 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
5552
Future<AppBundle> getAppBundle({bool? bypassCache = false}) async {
5653
try {
5754
final configString =
58-
await rootBundle.loadString('${path}config/appConfig.json');
55+
await _loadLocalAssetString('${path}config/appConfig.json');
5956
final Map<String, dynamic> appConfigMap = json.decode(configString);
6057
if (appConfigMap.isNotEmpty) {
6158
appConfig = UserAppConfig(
@@ -75,7 +72,7 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
7572

7673
Future<YamlMap?> _readFile(String file) async {
7774
try {
78-
var value = await rootBundle.loadString(path + file);
75+
var value = await _loadLocalAssetString(path + file);
7976
return loadYaml(value);
8077
} catch (error) {
8178
// ignore error
@@ -92,7 +89,7 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
9289
try {
9390
// Get the manifest content
9491
final manifestContent =
95-
await rootBundle.loadString(path + '.manifest.json');
92+
await _loadLocalAssetString(path + '.manifest.json');
9693
final Map<String, dynamic> manifestMap = json.decode(manifestContent);
9794

9895
// Process App Widgets
@@ -130,8 +127,8 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
130127
for (var script in scriptsList) {
131128
try {
132129
// Load the script content in string
133-
final scriptContent = await rootBundle
134-
.loadString("${path}scripts/${script["name"]}.js");
130+
final scriptContent = await _loadLocalAssetString(
131+
"${path}scripts/${script["name"]}.js");
135132
code[script["name"]] = scriptContent;
136133
} catch (e) {
137134
// ignore error
@@ -209,3 +206,8 @@ class LocalDefinitionProvider extends FileDefinitionProvider {
209206
return this;
210207
}
211208
}
209+
210+
Future<String> _loadLocalAssetString(String key) async {
211+
final data = await rootBundle.load(key);
212+
return utf8.decode(data.buffer.asUint8List());
213+
}

modules/ensemble/lib/framework/definition_providers/screen_selector_security.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
import 'package:meta/meta.dart';
2-
31
/// Returns true when [screen] is a single safe path segment for resolving
42
/// screen YAML from a remote base URL or from bundled assets under
53
/// `.../screens/<screen>.yaml`.
64
///
75
/// Rejects empty values, `..`, path separators, percent-encoded separators, and
86
/// ASCII control characters so untrusted inputs cannot traverse outside the
97
/// intended screens directory.
10-
@visibleForTesting
118
bool isSafeRemoteScreenSelector(String screen) {
129
if (screen.isEmpty || screen.length > 256) {
1310
return false;

modules/firebase_analytics/ios/Flutter/Generated.xcconfig

Lines changed: 0 additions & 14 deletions
This file was deleted.

modules/firebase_analytics/ios/Flutter/flutter_export_environment.sh

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)