// 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:convert'; import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/common/plugin_utils.dart'; import 'package:flutter_plugin_tools/src/drive_examples_command.dart'; import 'package:platform/platform.dart'; import 'package:test/test.dart'; import 'mocks.dart'; import 'util.dart'; const String _fakeIOSDevice = '67d5c3d1-8bdf-46ad-8f6b-b00e2a972dda'; const String _fakeAndroidDevice = 'emulator-1234'; void main() { group('test drive_example_command', () { late FileSystem fileSystem; late Platform mockPlatform; late Directory packagesDir; late CommandRunner runner; late RecordingProcessRunner processRunner; setUp(() { fileSystem = MemoryFileSystem(); mockPlatform = MockPlatform(); packagesDir = createPackagesDirectory(fileSystem: fileSystem); processRunner = RecordingProcessRunner(); final DriveExamplesCommand command = DriveExamplesCommand(packagesDir, processRunner: processRunner, platform: mockPlatform); runner = CommandRunner( 'drive_examples_command', 'Test for drive_example_command'); runner.addCommand(command); // TODO(dit): Clean this up, https://github.com/flutter/flutter/issues/151869 mockPlatform.environment['CHANNEL'] = 'master'; mockPlatform.environment['FLUTTER_LOGS_DIR'] = '/path/to/logs'; }); void setMockFlutterDevicesOutput({ bool hasIOSDevice = true, bool hasAndroidDevice = true, bool includeBanner = false, }) { const String updateBanner = ''' ╔════════════════════════════════════════════════════════════════════════════╗ ║ A new version of Flutter is available! ║ ║ ║ ║ To update to the latest version, run "flutter upgrade". ║ ╚════════════════════════════════════════════════════════════════════════════╝ '''; final List devices = [ if (hasIOSDevice) '{"id": "$_fakeIOSDevice", "targetPlatform": "ios"}', if (hasAndroidDevice) '{"id": "$_fakeAndroidDevice", "targetPlatform": "android-x86"}', ]; final String output = '''${includeBanner ? updateBanner : ''}[${devices.join(',')}]'''; final MockProcess mockDevicesProcess = MockProcess(stdout: output, stdoutEncoding: utf8); processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = [ FakeProcessInfo(mockDevicesProcess, ['devices']) ]; } test('fails if no platforms are provided', () async { setMockFlutterDevicesOutput(); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Exactly one of'), ]), ); }); test('fails if wasm flag is present but not web platform', () async { setMockFlutterDevicesOutput(); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--android', '--wasm'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('--wasm is only supported on the web platform'), ]), ); }); test('fails if multiple platforms are provided', () async { setMockFlutterDevicesOutput(); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--ios', '--macos'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Exactly one of'), ]), ); }); test('fails for iOS if no iOS devices are present', () async { setMockFlutterDevicesOutput(hasIOSDevice: false); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--ios'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('No iOS devices'), ]), ); }); test('handles flutter tool banners when checking devices', () async { createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/test_driver/integration_test.dart', 'example/integration_test/foo_test.dart', 'example/ios/ios.m', ], platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); setMockFlutterDevicesOutput(includeBanner: true); final List output = await runCapturingPrint(runner, ['drive-examples', '--ios']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); }); test('fails for iOS if getting devices fails', () async { // Simulate failure from `flutter devices`. processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = [ FakeProcessInfo(MockProcess(exitCode: 1), ['devices']) ]; Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--ios'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('No iOS devices'), ]), ); }); test('fails for Android if no Android devices are present', () async { setMockFlutterDevicesOutput(hasAndroidDevice: false); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--android'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('No Android devices'), ]), ); }); test('a plugin without any integration test files is reported as an error', () async { setMockFlutterDevicesOutput(); createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/lib/main.dart', 'example/android/android.java', 'example/ios/ios.m', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--android'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No driver tests were run (1 example(s) found).'), contains('No tests ran'), ]), ); }); test('integration tests using test(...) fail validation', () async { setMockFlutterDevicesOutput(); final RepositoryPackage package = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/test_driver/integration_test.dart', 'example/integration_test/foo_test.dart', 'example/android/android.java', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); package.directory .childDirectory('example') .childDirectory('integration_test') .childFile('foo_test.dart') .writeAsStringSync(''' test('this is the wrong kind of test!'), () { ... } '''); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--android'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('foo_test.dart failed validation'), ]), ); }); test('tests an iOS plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/bar_test.dart', 'example/integration_test/foo_test.dart', 'example/integration_test/ignore_me.dart', 'example/android/android.java', 'example/ios/ios.m', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint(runner, ['drive-examples', '--ios']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeIOSDevice, '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('handles missing CI debug logs directory', () async { mockPlatform.environment.remove('FLUTTER_LOGS_DIR'); final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/bar_test.dart', 'example/integration_test/foo_test.dart', 'example/integration_test/ignore_me.dart', 'example/android/android.java', 'example/ios/ios.m', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint(runner, ['drive-examples', '--ios']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeIOSDevice, 'integration_test', ], pluginExampleDirectory.path), ])); }); test('driving when plugin does not support Linux is a no-op', () async { createFakePlugin('plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ]); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--linux', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('Skipping unsupported platform linux...'), contains('No issues found!'), ]), ); // Output should be empty since running drive-examples --linux on a non-Linux // plugin is a no-op. expect(processRunner.recordedCalls, []); }); test('tests a Linux plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/linux/linux.cc', ], platformSupport: { platformLinux: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--linux', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'linux', '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('driving when plugin does not suppport macOS is a no-op', () async { createFakePlugin('plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ]); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--macos', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('Skipping unsupported platform macos...'), contains('No issues found!'), ]), ); // Output should be empty since running drive-examples --macos with no macos // implementation is a no-op. expect(processRunner.recordedCalls, []); }); test('tests a macOS plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/macos/macos.swift', ], platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--macos', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'macos', '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); // This tests the workaround for https://github.com/flutter/flutter/issues/135673 // and the behavior it tests should be removed once that is fixed. group('runs tests separately on desktop', () { test('macOS', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/first_test.dart', 'example/integration_test/second_test.dart', 'example/macos/macos.swift', ], platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--macos', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'macos', '--debug-logs-dir=/path/to/logs', 'integration_test/first_test.dart', ], pluginExampleDirectory.path), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'macos', '--debug-logs-dir=/path/to/logs', 'integration_test/second_test.dart', ], pluginExampleDirectory.path), ])); }); // This tests the workaround for https://github.com/flutter/flutter/issues/135673 // and the behavior it tests should be removed once that is fixed. test('Linux', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/first_test.dart', 'example/integration_test/second_test.dart', 'example/linux/foo.cc', ], platformSupport: { platformLinux: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--linux', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'linux', '--debug-logs-dir=/path/to/logs', 'integration_test/first_test.dart', ], pluginExampleDirectory.path), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'linux', '--debug-logs-dir=/path/to/logs', 'integration_test/second_test.dart', ], pluginExampleDirectory.path), ])); }); // This tests the workaround for https://github.com/flutter/flutter/issues/135673 // and the behavior it tests should be removed once that is fixed. test('Windows', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/first_test.dart', 'example/integration_test/second_test.dart', 'example/windows/foo.cpp', ], platformSupport: { platformWindows: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--windows', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'windows', '--debug-logs-dir=/path/to/logs', 'integration_test/first_test.dart', ], pluginExampleDirectory.path), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'windows', '--debug-logs-dir=/path/to/logs', 'integration_test/second_test.dart', ], pluginExampleDirectory.path), ])); }); }); test('driving when plugin does not suppport web is a no-op', () async { createFakePlugin('plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ]); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); // Output should be empty since running drive-examples --web on a non-web // plugin is a no-op. expect(processRunner.recordedCalls, []); }); test('drives a web plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/plugin_test.dart', ], pluginExampleDirectory.path), ])); }); test('drives a web plugin compiled to WASM', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', '--wasm', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--wasm', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/plugin_test.dart', ], pluginExampleDirectory.path), ])); }); // TODO(dit): Clean this up, https://github.com/flutter/flutter/issues/151869 test('drives a web plugin (html renderer in stable)', () async { // Override the platform to simulate CHANNEL: stable mockPlatform.environment['CHANNEL'] = 'stable'; final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=html', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/plugin_test.dart', ], pluginExampleDirectory.path), ])); }); test('runs chromedriver when requested', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint( runner, ['drive-examples', '--web', '--run-chromedriver']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ const ProcessCall('chromedriver', ['--port=4444'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/plugin_test.dart', ], pluginExampleDirectory.path), ])); }); test('drives a web plugin with CHROME_EXECUTABLE', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); mockPlatform.environment['CHROME_EXECUTABLE'] = '/path/to/chrome'; final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--chrome-binary=/path/to/chrome', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/plugin_test.dart', ], pluginExampleDirectory.path), ])); }); test('driving when plugin does not suppport Windows is a no-op', () async { createFakePlugin('plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ]); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--windows', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('Skipping unsupported platform windows...'), contains('No issues found!'), ]), ); // Output should be empty since running drive-examples --windows on a // non-Windows plugin is a no-op. expect(processRunner.recordedCalls, []); }); test('tests a Windows plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/windows/windows.cpp', ], platformSupport: { platformWindows: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--windows', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', 'windows', '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('tests an Android plugin', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/android/android.java', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--android', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeAndroidDevice, '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('tests an Android plugin with "apk" alias', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/android/android.java', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--apk', ]); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeAndroidDevice, '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('driving when plugin does not support Android is no-op', () async { createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ], platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint( runner, ['drive-examples', '--android']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('Skipping unsupported platform android...'), contains('No issues found!'), ]), ); // Output should be empty other than the device query. expect(processRunner.recordedCalls, [ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ]); }); test('driving when plugin does not support iOS is no-op', () async { createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', ], platformSupport: { platformMacOS: const PlatformDetails(PlatformSupport.inline), }, ); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint(runner, ['drive-examples', '--ios']); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('Skipping unsupported platform ios...'), contains('No issues found!'), ]), ); // Output should be empty other than the device query. expect(processRunner.recordedCalls, [ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ]); }); test('platform interface plugins are silently skipped', () async { createFakePlugin('aplugin_platform_interface', packagesDir, examples: []); setMockFlutterDevicesOutput(); final List output = await runCapturingPrint( runner, ['drive-examples', '--macos']); expect( output, containsAllInOrder([ contains('Running for aplugin_platform_interface'), contains( 'SKIPPING: Platform interfaces are not expected to have integration tests.'), contains('No issues found!'), ]), ); // Output should be empty since it's skipped. expect(processRunner.recordedCalls, []); }); test('enable-experiment flag', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/plugin_test.dart', 'example/android/android.java', 'example/ios/ios.m', ], platformSupport: { platformAndroid: const PlatformDetails(PlatformSupport.inline), platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); final Directory pluginExampleDirectory = getExampleDir(plugin); setMockFlutterDevicesOutput(); await runCapturingPrint(runner, [ 'drive-examples', '--ios', '--enable-experiment=exp1', ]); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeIOSDevice, '--enable-experiment=exp1', '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); test('fails when no example is present', () async { createFakePlugin( 'plugin', packagesDir, examples: [], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--web'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No driver tests were run (0 example(s) found).'), contains('The following packages had errors:'), contains(' plugin:\n' ' No tests ran (use --exclude if this is intentional)'), ]), ); }); test('web fails when no driver is present', () async { createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/bar_test.dart', 'example/integration_test/foo_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--web'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No driver found for plugin/example'), contains('No driver tests were run (1 example(s) found).'), contains('The following packages had errors:'), contains(' plugin:\n' ' No tests ran (use --exclude if this is intentional)'), ]), ); }); test('web fails when no integration tests are present', () async { createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/test_driver/integration_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--web'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('No driver tests were run (1 example(s) found).'), contains('The following packages had errors:'), contains(' plugin:\n' ' No tests ran (use --exclude if this is intentional)'), ]), ); }); test('"flutter drive" reports test failures', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/test_driver/integration_test.dart', 'example/integration_test/bar_test.dart', 'example/integration_test/foo_test.dart', 'example/web/index.html', ], platformSupport: { platformWeb: const PlatformDetails(PlatformSupport.inline), }, ); // Simulate failure from `flutter drive`. processRunner .mockProcessesForExecutable[getFlutterCommand(mockPlatform)] = [ // Fail both bar_test.dart and foo_test.dart. FakeProcessInfo(MockProcess(exitCode: 1), ['drive']), FakeProcessInfo(MockProcess(exitCode: 1), ['drive']), ]; Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--web'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('The following packages had errors:'), contains(' plugin:\n' ' example/integration_test/bar_test.dart\n' ' example/integration_test/foo_test.dart'), ]), ); final Directory pluginExampleDirectory = getExampleDir(plugin); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/bar_test.dart', ], pluginExampleDirectory.path), ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/plugin_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/foo_test.dart', ], pluginExampleDirectory.path), ])); }); test('"flutter test" reports test failures', () async { final RepositoryPackage plugin = createFakePlugin( 'plugin', packagesDir, extraFiles: [ 'example/integration_test/bar_test.dart', 'example/integration_test/foo_test.dart', 'example/ios/ios.swift', ], platformSupport: { platformIOS: const PlatformDetails(PlatformSupport.inline), }, ); setMockFlutterDevicesOutput(); // Simulate failure from `flutter test`. processRunner.mockProcessesForExecutable[getFlutterCommand(mockPlatform)]! .add(FakeProcessInfo(MockProcess(exitCode: 1), ['test'])); Error? commandError; final List output = await runCapturingPrint( runner, ['drive-examples', '--ios'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Running for plugin'), contains('The following packages had errors:'), contains(' plugin:\n' ' Integration tests failed.'), ]), ); final Directory pluginExampleDirectory = getExampleDir(plugin); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall(getFlutterCommand(mockPlatform), const ['devices', '--machine'], null), ProcessCall( getFlutterCommand(mockPlatform), const [ 'test', '-d', _fakeIOSDevice, '--debug-logs-dir=/path/to/logs', 'integration_test', ], pluginExampleDirectory.path), ])); }); group('packages', () { test('can be driven', () async { final RepositoryPackage package = createFakePackage('a_package', packagesDir, extraFiles: [ 'example/integration_test/foo_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ]); final Directory exampleDirectory = getExampleDir(package); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for a_package'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/a_package_example-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/foo_test.dart' ], exampleDirectory.path), ])); }); test('drive handles missing CI screenshot directory', () async { mockPlatform.environment.remove('FLUTTER_LOGS_DIR'); final RepositoryPackage package = createFakePackage('a_package', packagesDir, extraFiles: [ 'example/integration_test/foo_test.dart', 'example/test_driver/integration_test.dart', 'example/web/index.html', ]); final Directory exampleDirectory = getExampleDir(package); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for a_package'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/foo_test.dart' ], exampleDirectory.path), ])); }); test('are skipped when example does not support platform', () async { createFakePackage('a_package', packagesDir, isFlutter: true, extraFiles: [ 'example/integration_test/foo_test.dart', 'example/test_driver/integration_test.dart', ]); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for a_package'), contains('Skipping a_package/example; does not support any ' 'requested platforms'), contains('SKIPPING: No example supports requested platform(s).'), ]), ); expect(processRunner.recordedCalls.isEmpty, true); }); test('drive only supported examples if there is more than one', () async { final RepositoryPackage package = createFakePackage( 'a_package', packagesDir, isFlutter: true, examples: [ 'with_web', 'without_web' ], extraFiles: [ 'example/with_web/integration_test/foo_test.dart', 'example/with_web/test_driver/integration_test.dart', 'example/with_web/web/index.html', 'example/without_web/integration_test/foo_test.dart', 'example/without_web/test_driver/integration_test.dart', ]); final Directory supportedExampleDirectory = getExampleDir(package).childDirectory('with_web'); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for a_package'), contains( 'Skipping a_package/example/without_web; does not support any requested platforms.'), contains('No issues found!'), ]), ); expect( processRunner.recordedCalls, orderedEquals([ ProcessCall( getFlutterCommand(mockPlatform), const [ 'drive', '-d', 'web-server', '--web-port=7357', '--browser-name=chrome', '--web-renderer=canvaskit', '--screenshot=/path/to/logs/a_package_example_with_web-drive', '--driver', 'test_driver/integration_test.dart', '--target', 'integration_test/foo_test.dart' ], supportedExampleDirectory.path), ])); }); test('are skipped when there is no integration testing', () async { createFakePackage('a_package', packagesDir, isFlutter: true, extraFiles: ['example/web/index.html']); final List output = await runCapturingPrint(runner, [ 'drive-examples', '--web', ]); expect( output, containsAllInOrder([ contains('Running for a_package'), contains( 'SKIPPING: No example is configured for integration tests.'), ]), ); expect(processRunner.recordedCalls.isEmpty, true); }); }); }); }