run method

Future<void> run(
  1. String testTarget, {
  2. required String testDriver,
  3. bool headless = false,
  4. List<String> dartDefineArgs = const <String>[],
  5. bool debugLogging = false,
})

Implementation

Future<void> run(
  String testTarget, {
  required String testDriver,
  bool headless = false,
  List<String> dartDefineArgs = const <String>[],
  bool debugLogging = false,
}) async {
  void debugLog(String log) {
    if (debugLogging) print(log);
  }

  Future<void> runTest({required int attemptNumber}) async {
    debugLog('starting attempt #$attemptNumber for $testTarget');
    debugLog('starting the flutter drive process');

    final flutterDriveArgs = [
      'drive',
      // Debug outputs from the test will not show up in profile mode. Since
      // we rely on debug outputs for detecting errors and exceptions from the
      // test, we cannot run this these tests in profile mode until this issue
      // is resolved.  See https://github.com/flutter/flutter/issues/69070.
      // '--profile',
      '--driver=$testDriver',
      '--target=$testTarget',
      '-d',
      headless ? 'web-server' : 'chrome',
      // --disable-gpu speeds up tests that use ChromeDriver when run on
      // GitHub Actions. See https://github.com/flutter/devtools/issues/8301.
      '--web-browser-flag=--disable-gpu',
      if (headless) ...[
        // Flags to avoid breakage with chromedriver 128. See
        // https://github.com/flutter/devtools/issues/8301.
        '--web-browser-flag=--headless=old',
        '--web-browser-flag=--disable-search-engine-choice-screen',
      ],
      for (final arg in dartDefineArgs) '--dart-define=$arg',
    ];

    debugLog('> flutter ${flutterDriveArgs.join(' ')}');
    final process = await Process.start('flutter', flutterDriveArgs);

    bool stdOutWriteInProgress = false;
    bool stdErrWriteInProgress = false;
    final exceptionBuffer = StringBuffer();

    var testsPassed = false;
    listenToProcessOutput(
      process,
      printTag: 'FlutterDriveProcess',
      onStdout: (line) {
        if (line.endsWith(_allTestsPassed)) {
          testsPassed = true;
        }

        if (line.startsWith(_IntegrationTestResult.testResultPrefix)) {
          final testResultJson = line.substring(line.indexOf('{'));
          final testResultMap =
              jsonDecode(testResultJson) as Map<String, Object?>;
          final result = _IntegrationTestResult.fromJson(testResultMap);
          if (!result.result) {
            exceptionBuffer
              ..writeln('$result')
              ..writeln();
          }
        }

        if (line.contains(_beginExceptionMarker)) {
          stdOutWriteInProgress = true;
        }
        if (stdOutWriteInProgress) {
          exceptionBuffer.writeln(line);
          // Marks the end of the exception caught by flutter.
          if (line.contains(_endExceptionMarker) &&
              !line.contains(_beginExceptionMarker)) {
            stdOutWriteInProgress = false;
            exceptionBuffer.writeln();
          }
        }
      },
      onStderr: (line) {
        if (line.contains(_errorMarker) ||
            line.contains(_unhandledExceptionMarker)) {
          stdErrWriteInProgress = true;
        }
        if (stdErrWriteInProgress) {
          exceptionBuffer.writeln(line);
        }
      },
    );

    bool testTimedOut = false;
    final timeout = Future.delayed(const Duration(minutes: 8)).then((_) {
      testTimedOut = true;
    });

    await Future.any([
      process.exitCode,
      timeout,
    ]);

    debugLog(
      'shutting down processes because '
      '${testTimedOut ? 'test timed out' : 'test finished'}',
    );
    debugLog('attempting to kill the flutter drive process');
    process.kill();
    debugLog('flutter drive process has exited');

    // Ignore exception handling and retries if the tests passed. This is to
    // avoid bugs with the test runner where the test can fail after the test
    // has passed. See https://github.com/flutter/flutter/issues/129041.
    if (!testsPassed) {
      if (testTimedOut) {
        if (attemptNumber >= _maxRetriesOnTimeout) {
          throw Exception(
            'Integration test timed out on try #$attemptNumber: $testTarget',
          );
        } else {
          debugLog(
            'Integration test timed out on try #$attemptNumber. Retrying '
            '$testTarget now.',
          );
          await runTest(attemptNumber: ++attemptNumber);
        }
      }

      if (exceptionBuffer.isNotEmpty) {
        throw Exception(exceptionBuffer.toString());
      }
    }
  }

  await runTest(attemptNumber: 0);
}