blob: 543cd80f4cb0bba78f2b5838f071bf0b872a7a7b [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io' as io;
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as path;
import 'package:watcher/src/watch_event.dart';
import 'pipeline.dart';
import 'steps/compile_tests_step.dart';
import 'steps/run_tests_step.dart';
import 'utils.dart';
/// Runs tests.
class TestCommand extends Command<bool> with ArgUtils<bool> {
TestCommand() {
defaultsTo: false,
help: 'Pauses the browser before running a test, giving you an '
'opportunity to add breakpoints or inspect loaded code before '
'running the code.',
defaultsTo: false,
abbr: 'w',
help: 'Run in watch mode so the tests re-run whenever a change is '
defaultsTo: false,
help: 'felt test command runs the unit tests and the integration tests '
'at the same time. If this flag is set, only run the unit tests.',
defaultsTo: false,
'integration tests are using flutter repository for various tasks'
', such as flutter drive, flutter pub get. If this flag is set, felt '
'will use flutter command without cloning the repository. This flag '
'can save internet bandwidth. However use with caution. Note that '
'since flutter repo is always synced to youngest commit older than '
'the engine commit for the tests running in CI, the tests results '
'won\'t be consistent with CIs when this flag is set. flutter '
'command should be set in the PATH for this flag to be useful.'
'This flag can also be used to test local Flutter changes.')
defaultsTo: false,
'When running screenshot tests writes them to the file system into '
'.dart_tool/goldens. Use this option to bulk-update all screenshots, '
'for example, when a new browser version affects pixels.',
defaultsTo: false,
help: 'If set reuses the existig flutter/goldens repo clone. Use this '
'to avoid overwriting local changes when iterating on golden '
'tests. This is off by default.',
defaultsTo: 'chrome',
help: 'An option to choose a browser to run the tests. By default '
'tests run in Chrome.',
defaultsTo: false,
negatable: true,
help: 'If set, causes the test runner to exit upon the first test '
'failure. If not set, the test runner will continue running '
'test despite failures and will report them after all tests '
help: 'Optional. The path to a local build of CanvasKit to use in '
'tests. If omitted, the test runner uses the default CanvasKit '
final String name = 'test';
final String description = 'Run tests.';
bool get isWatchMode => boolArg('watch');
bool get failEarly => boolArg('fail-early');
/// Whether to start the browser in debug mode.
/// In this mode the browser pauses before running the test to allow
/// you set breakpoints or inspect the code.
bool get isDebug => boolArg('debug');
/// Paths to targets to run, e.g. a single test.
List<String> get targets => argResults!.rest;
/// The target test files to run.
List<FilePath> get targetFiles => t) => FilePath.fromCwd(t)).toList();
/// Whether all tests should run.
bool get runAllTests => targets.isEmpty;
/// The name of the browser to run tests in.
String get browserName => stringArg('browser');
/// When running screenshot tests writes them to the file system into
/// ".dart_tool/goldens".
bool get doUpdateScreenshotGoldens => boolArg('update-screenshot-goldens');
/// Whether to fetch the goldens repo prior to running tests.
bool get skipGoldensRepoFetch => boolArg('skip-goldens-repo-fetch');
/// Path to a CanvasKit build. Overrides the default CanvasKit.
String? get overridePathToCanvasKit => argResults!['canvaskit-path'] as String?;
Future<bool> run() async {
final List<FilePath> testFiles = runAllTests
? findAllTests()
: targetFiles;
final Pipeline testPipeline = Pipeline(steps: <PipelineStep>[
if (isWatchMode) ClearTerminalScreenStep(),
skipGoldensRepoFetch: skipGoldensRepoFetch,
testFiles: testFiles,
browserName: browserName,
testFiles: testFiles,
isDebug: isDebug,
doUpdateScreenshotGoldens: doUpdateScreenshotGoldens,
overridePathToCanvasKit: overridePathToCanvasKit,
if (isWatchMode) {
final FilePath dir = FilePath.fromWebUi('');
print('Initial test run is done!');
'Watching ${dir.relativeToCwd}/lib and ${dir.relativeToCwd}/test to re-run tests');
await PipelineWatcher(
dir: dir.absolute,
pipeline: testPipeline,
ignore: (WatchEvent event) {
// Ignore font files that are copied whenever tests run.
if (event.path.endsWith('.ttf')) {
return true;
// React to changes in lib/ and test/ folders.
final String relativePath =
path.relative(event.path, from: dir.absolute);
if (path.isWithin('lib', relativePath) ||
path.isWithin('test', relativePath)) {
return false;
// Ignore anything else.
return true;
return true;
/// Clears the terminal screen and places the cursor at the top left corner.
/// This works on Linux and Mac. On Windows, it's a no-op.
class ClearTerminalScreenStep implements PipelineStep {
String get description => 'clearing terminal screen';
bool get isSafeToInterrupt => false;
Future<void> interrupt() async {}
Future<void> run() async {
if (!io.Platform.isWindows) {
// See: