Skip to content

Instantly share code, notes, and snippets.

@antonkrasov
Created November 22, 2022 22:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save antonkrasov/35105571128b461eb2188c69e1887639 to your computer and use it in GitHub Desktop.
Save antonkrasov/35105571128b461eb2188c69e1887639 to your computer and use it in GitHub Desktop.
Flutter Automation tutorial files. Dart script to run flutter integration tests on multiple iOs simulators and Android emulators and integration test file itself.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:taskezdev/main.dart' as app;
import 'package:taskezdev/main.dart';
import 'helpers.dart';
void main() {
const deviceName = String.fromEnvironment('DEVICE_NAME');
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('end-to-end test', () {
testWidgets('onboarding UI test', (tester) async {
app.main();
await tester.pumpAndSettle();
await binding.convertFlutterSurfaceToImage();
await precacheImage(
const AssetImage('assets/art_onboarding_1.png'),
tester.element(
find.byType(MyApp),
),
);
await tester.pumpAndSettle();
await binding.delayed(const Duration(seconds: 1));
await tester.pumpAndSettle();
await binding.takeScreenshot('$deviceName/$deviceName-1');
await tapAndTakeScreenshot(
binding,
tester,
find.text('Get Started'),
screenshotName: '$deviceName/$deviceName-2',
);
await tapAndTakeScreenshot(
binding,
tester,
find.text('Continue with Email'),
screenshotName: '$deviceName/$deviceName-3',
);
await tapAndTakeScreenshot(
binding,
tester,
find.text('Continue with Email'),
screenshotName: '$deviceName/$deviceName-4',
);
await tapAndTakeScreenshot(
binding,
tester,
find.widgetWithText(ElevatedButton, 'Sign Up'),
screenshotName: '$deviceName/$deviceName-5',
);
await tapAndTakeScreenshot(
binding,
tester,
find.text('Next'),
screenshotName: '$deviceName/$deviceName-6',
);
await tapAndTakeScreenshot(
binding,
tester,
find.text('Done'),
screenshotName: '$deviceName/$deviceName-7',
);
await tapAndTakeScreenshot(
binding,
tester,
find.image(const AssetImage('assets/avatar.png')),
screenshotName: '$deviceName/$deviceName-8',
);
});
});
}
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
Future<void> tapAndTakeScreenshot(
IntegrationTestWidgetsFlutterBinding binding,
WidgetTester tester,
Finder finder, {
required String screenshotName,
Duration delayDuration = const Duration(seconds: 1),
}) async {
await tester.tap(finder);
await tester.pumpAndSettle();
await binding.delayed(delayDuration);
await tester.pumpAndSettle();
await binding.takeScreenshot(screenshotName);
}
// ignore_for_file: avoid_print
import 'dart:io';
const filteredDeviceIDs = [];
void main() async {
print('-= Run Tests =-');
final screenshotsDir = Directory('screenshots');
if (screenshotsDir.existsSync()) {
screenshotsDir.deleteSync(recursive: true);
}
final startTime = DateTime.now();
final iOsDevices = await getIOsDevices();
await Process.run('open', [
'/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app'
]);
await shutdownIOsDevices(iOsDevices);
final androidDevices = await getAndroidDevices();
final devices = [
...iOsDevices,
...androidDevices,
];
devices.removeWhere((d) => filteredDeviceIDs.contains(d.id));
for (final device in devices) {
if (device.isAndroid) {
await processAndroid(device);
}
if (device.isiOs) {
await processIOs(device);
}
}
final endTime = DateTime.now();
final scriptTimeMillis =
endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch;
print('Done in ${Duration(milliseconds: scriptTimeMillis)}');
}
Future<void> shutdownIOsDevices(List<Device> devices) async {
for (final device in devices) {
await Process.run(
'xcrun',
[
'simctl',
'shutdown',
device.id,
],
);
}
}
Future<void> processIOs(Device device) async {
print('Starting device ${device.name}');
await Process.run(
'xcrun',
[
'simctl',
'boot',
device.id,
],
);
await runTests(device);
print('\tShutting down');
await Process.run(
'xcrun',
[
'simctl',
'shutdown',
device.id,
],
);
}
Future<void> processAndroid(Device device) async {
print('Starting device ${device.name}');
final process = await Process.start(
'emulator',
[
'-avd',
device.id,
'-no-snapshot',
],
);
await process.stdout.map((raw) => String.fromCharCodes(raw)).firstWhere(
(log) => log.contains('boot completed'),
);
await runTests(device);
process.kill();
}
Future<void> runTests(Device device) async {
print('\tRunning tests');
final result = await Process.run(
'flutter',
[
'drive',
'--driver=test_driver/integration_test.dart',
'--target=integration_test/app_test.dart',
'--dart-define',
'DEVICE_NAME=${device.name}'
],
);
// print(result.stdout);
}
Future<List<Device>> getAndroidDevices() async {
final devices = <Device>[];
final result = await Process.run(
'emulator',
['-list-avds'],
);
String devicesListRaw = result.stdout;
final devicesListRawLines = devicesListRaw.split('\n');
for (final devicesListRawLine in devicesListRawLines) {
if (devicesListRawLine.isNotEmpty) {
devices.add(
Device(
name: devicesListRawLine,
id: devicesListRawLine,
type: DeviceType.android),
);
}
}
return devices;
}
Future<List<Device>> getIOsDevices() async {
final devices = <Device>[];
final result = await Process.run(
'xcrun',
['simctl', 'list'],
);
String devicesListRaw = result.stdout;
devicesListRaw = devicesListRaw.substring(
devicesListRaw.indexOf('== Devices =='),
);
final devicesListRawLines = devicesListRaw.split('\n');
final regex = RegExp(r'(\([-A-Z0-9]+)\)');
for (final devicesListRawLine in devicesListRawLines) {
if (regex.hasMatch(devicesListRawLine)) {
final deviceId = regex
.firstMatch(devicesListRawLine)!
.group(0)!
.replaceAll('(', '')
.replaceAll(')', '');
final deviceName = devicesListRawLine
.substring(
0,
devicesListRawLine.indexOf(deviceId) - 1,
)
.trim();
devices.add(Device(
name: deviceName,
id: deviceId,
type: DeviceType.iOs,
));
}
}
return devices;
}
enum DeviceType {
iOs,
android,
}
class Device {
final String name;
final String id;
final DeviceType type;
Device({
required this.name,
required this.id,
required this.type,
});
bool get isAndroid => type == DeviceType.android;
bool get isiOs => type == DeviceType.iOs;
@override
String toString() {
return '$name{$id}';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment