runOne method
Future<EnsembleTestRunOutput>
runOne(
- EnsembleTestCase test,
- WidgetTester tester, {
- EnsembleTestConfig suiteConfig = const EnsembleTestConfig(),
- EnsembleConfig? existingConfig,
- AppSessionSnapshot? sessionSnapshot,
Runs a single test, optionally continuing an existing app session.
Implementation
Future<EnsembleTestRunOutput> runOne(
EnsembleTestCase test,
WidgetTester tester, {
EnsembleTestConfig suiteConfig = const EnsembleTestConfig(),
EnsembleConfig? existingConfig,
AppSessionSnapshot? sessionSnapshot,
}) async {
final stopwatch = Stopwatch()..start();
void Function(List<ui.FrameTiming>)? timingsCallback;
final ctx = EnsembleTestContext.fromTestCase(
test,
config: suiteConfig,
);
final previousOnError = FlutterError.onError;
final previousDebugPrint = debugPrint;
final previousLiveAsyncRunner = LiveAsyncCallSupport.runner;
final previousDrainPendingExceptions =
LiveAsyncCallSupport.drainPendingExceptions;
try {
FlutterError.onError = (details) {
ctx.runtime.flutterErrors.add(_formatFlutterError(details));
};
debugPrint = (String? message, {int? wrapWidth}) {
if (message != null) {
ctx.runtime.consoleLogs.add(ctx.runtime.formatConsoleLine(message));
}
previousDebugPrint(message, wrapWidth: wrapWidth);
};
applyWifiTestConfig(suiteConfig.wifi);
timingsCallback = (List<ui.FrameTiming> timings) {
ctx.runtime.addFrameTimings(timings);
};
SchedulerBinding.instance.addTimingsCallback(timingsCallback);
ctx.apiOverlay.liveAsyncRunner = tester.runAsync;
LiveAsyncCallSupport.runner = tester.runAsync;
// Inspect pending framework exceptions at explicit lifecycle boundaries;
// do not discard them from async-call cleanup.
LiveAsyncCallSupport.drainPendingExceptions = null;
return await runZoned(
() async {
final startupStartFrame = ctx.runtime.appFrameTimings.length + 1;
final startupStartTime = DateTime.now();
final config = await harness.loadScreen(
tester: tester,
testCase: test,
existingConfig: existingConfig,
context: ctx,
suiteConfig: suiteConfig,
beforeBootstrap: () async {
await sessionSnapshot?.restore();
await _executeSetup(test);
},
forcedLocale: sessionSnapshot?.locale ?? ctx.runtime.locale,
);
_throwIfUnexpectedFlutterExceptions(
tester,
phase: 'during startup/setup',
);
await YamlTestSession.navigationFlow.flushPending();
YamlTestSession.navigationFlow.beginTest(
ScreenTracker().getCurrentScreenIdentifier(),
);
_recordPerformanceMarker(
ctx: ctx,
testId: test.id,
stepIndex: null,
label: '${test.id} startup',
phase: 'startup',
startFrame: startupStartFrame,
startTime: startupStartTime,
);
final result = await _executeSteps(
test: test,
tester: tester,
ctx: ctx,
config: config,
stopwatch: stopwatch,
);
await _settleLiveApiWorkBestEffort(tester, ctx);
return (result: result, config: config, context: ctx);
},
zoneSpecification: ZoneSpecification(
print: (self, parent, zone, line) {
ctx.runtime.consoleLogs.add(ctx.runtime.formatConsoleLine(line));
parent.print(zone, line);
},
),
);
} catch (error, stackTrace) {
final config = existingConfig ?? Ensemble().getConfig();
final errorMessage = error.toString();
final logs = <String>[];
try {
await _settleLiveApiWorkBestEffort(tester, ctx);
final hadScreenshotFrames =
ctx.runtime.screenshotSheetFrames.isNotEmpty;
await _flushPendingScreenshots(
ctx,
status: TestStatus.failed,
durationMs: stopwatch.elapsedMilliseconds,
failedStepLabel: 'Startup/setup',
failureMessage: errorMessage,
);
await _attachPerTestDebugArtifacts(ctx);
logs.addAll(ctx.logger.logs);
if (!hadScreenshotFrames) {
logs.addAll(
await _writeEmergencyFailureScreenshot(
tester: tester,
test: test,
config: suiteConfig,
error: error,
),
);
}
} catch (_) {
try {
await _attachPerTestDebugArtifacts(ctx);
} catch (_) {}
logs.addAll(ctx.logger.logs);
}
return (
result: EnsembleSingleTestResult.failed(
testId: test.id,
metadata: test.metadataJson,
error: errorMessage,
stackTrace: stackTrace.toString(),
durationMs: stopwatch.elapsedMilliseconds,
logs: logs,
report: buildTestReportDetails(test),
),
config: config ?? await harness.buildConfig(),
context: ctx,
);
} finally {
debugPrint = previousDebugPrint;
final callback = timingsCallback;
if (callback != null) {
SchedulerBinding.instance.removeTimingsCallback(callback);
}
FlutterError.onError = previousOnError;
LiveAsyncCallSupport.runner = previousLiveAsyncRunner;
LiveAsyncCallSupport.drainPendingExceptions =
previousDrainPendingExceptions;
}
}