runOne method

Future<EnsembleTestRunOutput> runOne(
  1. EnsembleTestCase test,
  2. WidgetTester tester, {
  3. EnsembleTestConfig suiteConfig = const EnsembleTestConfig(),
  4. EnsembleConfig? existingConfig,
  5. 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;
  }
}