Skip to content

Commit

Permalink
chore: some linting
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasbadstuebner committed Aug 30, 2023
1 parent c771087 commit 8ea5f7b
Show file tree
Hide file tree
Showing 31 changed files with 380 additions and 397 deletions.
9 changes: 4 additions & 5 deletions lib/screenshots.dart
@@ -1,9 +1,8 @@
library screenshots;

export 'src/run.dart' show screenshots;
export 'src/config.dart';
export 'src/base/process_common.dart';
export 'src/capture_screen.dart';
export 'src/config.dart';
export 'src/globals.dart' show DeviceType, kConfigFileName;
export 'src/utils.dart' show isAdbPath, isEmulatorPath;
export 'src/base/process_common.dart';
export 'src/image_magick.dart' show isImageMagicInstalled;
export 'src/run.dart' show screenshots;
export 'src/utils.dart' show isAdbPath, isEmulatorPath;
5 changes: 2 additions & 3 deletions lib/src/archive.dart
Expand Up @@ -3,11 +3,10 @@ import 'utils.dart' as utils;

/// Archive screenshots generated by a run.
class Archive {
Archive(String archiveDir) : archiveDirPrefix = '$archiveDir/$_timeStamp';
static final _timeStamp = getTimestamp();

final archiveDirPrefix;

Archive(String archiveDir) : archiveDirPrefix = '$archiveDir/$_timeStamp';
final String archiveDirPrefix;

String dstDir(DeviceType deviceType, String locale) =>
'$archiveDirPrefix/${utils.getStringFromEnum(deviceType)}/$locale';
Expand Down
22 changes: 11 additions & 11 deletions lib/src/base/process_common.dart
Expand Up @@ -57,28 +57,28 @@ String? getExecutablePath(
assert(_osToPathStyle[platform.operatingSystem] == fs.path.style.name);

workingDirectory ??= fs.currentDirectory.path;
Context context = Context(style: fs.path.style, current: workingDirectory);
final context = Context(style: fs.path.style, current: workingDirectory);

// TODO(goderbauer): refactor when github.com/google/platform.dart/issues/2
// is available.
String pathSeparator = platform.isWindows ? ';' : ':';
final pathSeparator = platform.isWindows ? ';' : ':';

List<String> extensions = <String>[];
var extensions = <String>[];
if (platform.isWindows && context.extension(command).isEmpty) {
extensions = platform.environment['PATHEXT']!.split(pathSeparator);
}

List<String> candidates = <String>[];
var candidates = <String>[];
if (command.contains(context.separator)) {
candidates = _getCandidatePaths(
command, <String>[workingDirectory], extensions, context);
} else {
List<String> searchPath =
final searchPath =
platform.environment['PATH']!.split(pathSeparator);
candidates = _getCandidatePaths(command, searchPath, extensions, context);
}
return candidates.map<String?>((e) => e).firstWhere(
(String? path) => fs.file(path).existsSync(),
(path) => fs.file(path).existsSync(),
orElse: () => null);
}

Expand All @@ -95,16 +95,16 @@ List<String> _getCandidatePaths(
List<String> extensions,
Context context,
) {
List<String> withExtensions = extensions.isNotEmpty
? extensions.map((String ext) => '$command$ext').toList()
final withExtensions = extensions.isNotEmpty
? extensions.map((ext) => '$command$ext').toList()
: <String>[command];
if (context.isAbsolute(command)) {
return withExtensions;
}
return searchPaths
.map((String path) =>
withExtensions.map((String command) => context.join(path, command)))
.expand((Iterable<String> e) => e)
.map((path) =>
withExtensions.map((command) => context.join(path, command)))
.expand((e) => e)
.toList()
.cast<String>();
}
12 changes: 6 additions & 6 deletions lib/src/context_runner.dart
Expand Up @@ -11,7 +11,7 @@ import 'package:tool_base/tool_base.dart';
import 'package:tool_mobile/tool_mobile.dart';

Future<T> runInContext<T>(
FutureOr<T> runner(), {
FutureOr<T> Function() runner, {
Map<Type, Generator>? overrides,
}) async {
return await context.run<T>(
Expand All @@ -21,12 +21,12 @@ Future<T> runInContext<T>(
fallbacks: <Type, Generator>{
AndroidSdk: AndroidSdk.locateAndroidSdk,
BotDetector: () => const BotDetector(),
Config: () => Config(),
DaemonClient: () => DaemonClient(),
ImageMagick: () => ImageMagick(),
Config: Config.new,
DaemonClient: DaemonClient.new,
ImageMagick: ImageMagick.new,
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
OperatingSystemUtils: () => OperatingSystemUtils(),
ProcessManager: () => LocalProcessManager(),
OperatingSystemUtils: OperatingSystemUtils.new,
ProcessManager: LocalProcessManager.new,
Stdio: () => const Stdio(),
TimeoutConfiguration: () => const TimeoutConfiguration(),
},
Expand Down
48 changes: 24 additions & 24 deletions lib/src/daemon_client.dart
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:convert';

import 'package:meta/meta.dart';
import 'package:screenshots/src/utils.dart';
Expand All @@ -22,9 +21,10 @@ class DaemonClient {
Completer<bool>? _waitForConnection;
Completer<String>? _waitForResponse;
Completer<String> _waitForEvent = Completer<String>();
List? _iosDevices; // contains model of device, used by screenshots
StreamSubscription? _stdOutListener;
StreamSubscription? _stdErrListener;
List<Map<String, String?>>?
_iosDevices; // contains model of device, used by screenshots
StreamSubscription<String>? _stdOutListener;
StreamSubscription<List<int>>? _stdErrListener;

/// Start flutter tools daemon.
Future<void> get start async {
Expand All @@ -37,7 +37,7 @@ class DaemonClient {
// maybe should check if iOS run type is active
if (platform.isMacOS) _iosDevices = getIosDevices();
// wait for device discovery
await Future.delayed(Duration(milliseconds: 100));
await Future<void>.delayed(const Duration(milliseconds: 100));
}
}

Expand Down Expand Up @@ -213,7 +213,7 @@ class DaemonClient {
/// Get attached ios devices with id and model.
List<Map<String, String?>> getIosDevices() {
final regExp = RegExp(r'Found (\w+) \(\w+, (.*), \w+, \w+\)');
final noAttachedDevices = 'no attached devices';
const noAttachedDevices = 'no attached devices';
final iosDeployDevices =
cmd(['sh', '-c', 'ios-deploy -c || echo "$noAttachedDevices"'])
.trim()
Expand Down Expand Up @@ -247,19 +247,18 @@ Future<String> waitForEmulatorToStart(DaemonClient daemonClient,
orElse: () => null);
started = device != null;
if (started) deviceId = device.id;
await Future<void>.delayed(Duration(milliseconds: 1000));
await Future<void>.delayed(const Duration(milliseconds: 1000));
}
return deviceId!;
}

abstract class BaseDevice {
BaseDevice(this.id, this.name, this.category, this.platformType);
final String id;
final String name;
final String category;
final String platformType;

BaseDevice(this.id, this.name, this.category, this.platformType);

@override
bool operator ==(other) {
return other is BaseDevice &&
Expand All @@ -278,34 +277,35 @@ abstract class BaseDevice {
/// Describe an emulator.
class DaemonEmulator extends BaseDevice {
DaemonEmulator(
String id,
String name,
String category,
String platformType,
) : super(id, name, category, platformType);
super.id,
super.name,
super.category,
super.platformType,
);
}

/// Describe a device.
class DaemonDevice extends BaseDevice {
final String platform;
final bool emulator;
final bool ephemeral;
final String? emulatorId;
final String? iosModel; // iOS model
// iOS model
DaemonDevice(
String id,
String name,
String category,
String platformType,
super.id,
super.name,
super.category,
super.platformType,
this.platform,
this.emulator,
this.ephemeral,
this.emulatorId, {
this.iosModel,
}) : super(id, name, category, platformType) {
}) {
// debug check in CI
if (emulator && emulatorId == null) throw 'Emulator id is null';
}
final String platform;
final bool emulator;
final bool ephemeral;
final String? emulatorId;
final String? iosModel;

@override
bool operator ==(other) {
Expand Down
23 changes: 12 additions & 11 deletions lib/src/fastlane.dart
@@ -1,26 +1,26 @@
import 'dart:async';

import 'package:path/path.dart' as p;
import 'package:screenshots/src/image_magick.dart';
import 'package:tool_base/tool_base.dart' hide Config;

import 'config.dart';
import 'screens.dart';
import 'package:path/path.dart' as p;
import 'globals.dart';
import 'screens.dart';

/// clear configured fastlane directories.
Future clearFastlaneDirs(
Future<void> clearFastlaneDirs(
ScreenshotsConfig config, Screens screens, RunMode runMode) async {
if (config.isRunTypeActive(DeviceType.android)) {
for (ConfigDevice device in config.androidDevices) {
for (final device in config.androidDevices) {
for (final locale in config.locales) {
await _clearFastlaneDir(
screens, device.name, locale, DeviceType.android, runMode);
}
}
}
if (config.isRunTypeActive(DeviceType.ios)) {
for (ConfigDevice device in config.iosDevices) {
for (final device in config.iosDevices) {
for (final locale in config.locales) {
await _clearFastlaneDir(
screens, device.name, locale, DeviceType.ios, runMode);
Expand All @@ -30,10 +30,10 @@ Future clearFastlaneDirs(
}

/// Clear images destination.
Future _clearFastlaneDir(Screens screens, String deviceName, String locale,
DeviceType deviceType, RunMode runMode) async {
final Map? screenProps = screens.getScreen(deviceName);
String? androidModelType = getAndroidModelType(screenProps, deviceName);
Future<void> _clearFastlaneDir(Screens screens, String deviceName,
String locale, DeviceType deviceType, RunMode runMode) async {
final screenProps = screens.getScreen(deviceName);
final androidModelType = getAndroidModelType(screenProps, deviceName);

final dirPath = getDirPath(deviceType, locale, androidModelType);

Expand Down Expand Up @@ -77,13 +77,14 @@ String getDirPath(
}

/// Get android model type (phone or tablet screen size).
String? getAndroidModelType(Map? screenProps, String deviceName) {
String? getAndroidModelType(
Map<String, dynamic>? screenProps, String deviceName) {
String? androidDeviceType = kFastlanePhone;
if (screenProps == null) {
printStatus(
'Warning: using default value \'$kFastlanePhone\' in \'$deviceName\' fastlane directory.');
} else {
androidDeviceType = screenProps['destName'] as String?;
androidDeviceType = screenProps['destName']?.toString();
}
return androidDeviceType;
}
Expand Down
16 changes: 6 additions & 10 deletions lib/src/image_magick.dart
Expand Up @@ -14,16 +14,16 @@ final ImageMagick _kImageMagick = ImageMagick();
ImageMagick get im => context.get<ImageMagick>() ?? _kImageMagick;

class ImageMagick {
factory ImageMagick() {
return _imageMagick;
}
ImageMagick._internal();
static const _kThreshold = 0.76;
static const kDiffSuffix = '-diff';
//const kThreshold = 0.5;

// singleton
static final ImageMagick _imageMagick = ImageMagick._internal();
factory ImageMagick() {
return _imageMagick;
}
ImageMagick._internal();

///
/// ImageMagick calls.
Expand Down Expand Up @@ -110,7 +110,7 @@ class ImageMagick {
bool compare(String? comparisonImage, String? recordedImage) {
final diffImage = getDiffImagePath(comparisonImage);

int returnCode = _imageMagickCmd('compare', <String>[
final returnCode = _imageMagickCmd('compare', <String>[
'-metric',
'mae',
recordedImage!,
Expand All @@ -127,11 +127,7 @@ class ImageMagick {

/// Append diff suffix [kDiffSuffix] to [imagePath].
String getDiffImagePath(String? imagePath) {
final diffName = p.dirname(imagePath!) +
'/' +
p.basenameWithoutExtension(imagePath) +
kDiffSuffix +
p.extension(imagePath);
final diffName = '${p.dirname(imagePath!)}/${p.basenameWithoutExtension(imagePath)}$kDiffSuffix${p.extension(imagePath)}';
return diffName;
}

Expand Down
10 changes: 5 additions & 5 deletions lib/src/image_processor.dart
Expand Up @@ -15,6 +15,10 @@ import 'screens.dart';
import 'utils.dart' as utils;

class ImageProcessor {

ImageProcessor(Screens screens, ScreenshotsConfig config)
: _screens = screens,
_config = config;
static const _kDefaultIosBackground = 'xc:white';
@visibleForTesting // for now
static const kDefaultAndroidBackground = 'xc:none'; // transparent
Expand All @@ -24,10 +28,6 @@ class ImageProcessor {
final Screens _screens;
final ScreenshotsConfig _config;

ImageProcessor(Screens screens, ScreenshotsConfig config)
: _screens = screens,
_config = config;

/// Process screenshots.
///
/// If android, screenshot is overlaid with a status bar and appended with
Expand Down Expand Up @@ -136,7 +136,7 @@ class ImageProcessor {
@visibleForTesting
static Future<Map<String, Map<String, String>>> compareImages(
String deviceName, String recordingDir, String comparisonDir) async {
var failedCompare = <String, Map<String, String>>{};
final failedCompare = <String, Map<String, String>>{};
final recordedImages = fs.directory(recordingDir).listSync();
fs
.directory(comparisonDir)
Expand Down

24 comments on commit 8ea5f7b

@graux
Copy link

@graux graux commented on 8ea5f7b Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @DrBu7cher !
I know this is not an official package/fork or anything like that. But it seems that is the only thing compatible with an up-to-date flutter project.
I am trying to migrate my old code from test_driver to flutter_test and use your package to generate screenshots.
My problem is that you have changed the signature of the screenshot(driver, config, name) method to screenshot(tester,binding,integrationTestChannel,platformDispatcher,name). I have no clue what are the integrationTestChannel and platformDispatcher.
I am unable to find a test or example using this functionality. (or another of your repos using this package)
Could you send me or update the README.md file with a simple example?

Thank you!!!

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, are you asking for putting this back into screenshots?
I'm using it like (pseudo-code)

// integrationTestChannel is official from flutter
import 'package:integration_test/src/channel.dart';

final binding = PatrolBinding.ensureInitialized();
final tester = PatrolTester; // This is not really how to use it
final platformDispatcher = PlatformDispatcher.instance;

e.g.
integration_test/int_test.dart

void main() {
  final binding = PatrolBinding.ensureInitialized();

  Future<void> takeScreenshot(PatrolTester tester, String name,
      {Duration? timeout}) async {
    return await screenshot(
      tester,
      binding,
      integrationTestChannel,
      PlatformDispatcher.instance,
      name,
      timeout: timeout,
    );
  }

  patrolTest('test_something', nativeAutomation: true, ($) async {
    await MaterialApp();
    await $('Login').waitUntilVisible();
    await takeScreenshot($, 'loggedOutLandingPage');

    // Login logic

    await takeScreenshot(
      $,
      'loggedInLandingPage',
      timeout: const Duration(seconds: 5),
    );
  });
}

and then I run
screenshots -p (For using with patrol)

@graux
Copy link

@graux graux commented on 8ea5f7b Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @DrBu7cher thank you for the code, that is great.
I am getting a nasty error though. Patrol is expecting the test file to be in integration_test/app_test.dart (I think)

Currently screenshots library uses the config file tests array for tests.
The command to run patrol seems to be missing the parameter --target + testPath to use the same path as screenshots. HERE

Going to move my tests there and check if it works.

Thank you again!

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using patrol v2?

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see what you are saying.
Still: If you run patrol test it uses integration_test/test_bundle.dart and collects all tests in the directory into one executable to run the tests with. So you actually would not need to use the --target flag.
If you only want one/explicit tests, I see what you mean.

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

864ed81
Can you test if it works as expected please?

@graux
Copy link

@graux graux commented on 8ea5f7b Aug 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using patrol v2?

Trying.... which version are you using?
I'll test the changes as soon as I can manage this working with the default patrol config.
Thanks

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

patrol: ^2.2.0
patrol_cli v2.1.2

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through leancodepl/patrol#1341 to get v2 to work

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't use group in your test (use patrolTest only) and your main in the test has to be synchronous - that is where I fell flat on my face when migrating.

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just ran my pipeline and at least the change did not break anything. But I don't know, theoretically I should be able to just run the Patrol test without specifying the target - and that should run all the tests in the integration_test folder.

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I will add this later, for me it is not a use case right now.

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not try much more, but I am stuck. It takes a long time to run, and when it finishes, it has not generated any screenshots.
I don't think it really executes the test file at all. Maybe I did screw up setting up Patrol. Tomorrow I'll try again with a smaller project and/or use flutter_test without Patrol, as I really do not need Patrol for my tests at this moment. It is really great, but it also raises the complexity significantly.
Thank you again @DrBu7cher

@jonasbadstuebner
Copy link
Owner Author

@jonasbadstuebner jonasbadstuebner commented on 8ea5f7b Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey man,
sorry to hear that.
Theoretically (I did not test it yet) you can replace the patrol-specific parts and use

final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

(See here) instead.

@jonasbadstuebner
Copy link
Owner Author

@jonasbadstuebner jonasbadstuebner commented on 8ea5f7b Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pseudo, written on my phone:

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  Future<void> takeScreenshot(WidgetTester tester, String name,
      {Duration? timeout}) async {
    return await screenshot(
      tester,
      binding,
      integrationTestChannel,
      PlatformDispatcher.instance,
      name,
      timeout: timeout,
    );
  }

  group('end-to-end test', () {
    testWidgets('tap on the floating action button, verify counter',
        (tester) async {
      // Load app widget.
      await tester.pumpWidget(const MyApp());

      await takeScreenshot($, 'loggedOutLandingPage');
    });
  });
}

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi man!
Thank you for all the help. It has started working, but I am having project issues in both Android and iOS.
Just to share, in Android, because I use flutter_stripe package, I need to use FlutterFragmentActivity instead of FlutterActivity. And there is a bug reported that flutter_test cannot generate screenshots using the fragment activity class. So I am going to make a script to switch that class back and forth when running the tests. (and not have screenshots for stripe payments integration, not a big deal)
In iOS, I have this issue only when using simulators... So I cannot generate iOS screenshots. Not sure what is my best alternative here to unblock myself.

Anyway, thank you again!

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I have fixed the iOS problem 🎉
Now I get the following error running screenshots:

Taking screenshot 'login'
screenshot-receiver: Written screenshot to /tmp/test/login.png
Screenshot login created at /tmp/test/login.png

01:01 +1: end-to-end test Test Login:                                                                                                                                                                  
01:01 +1: (tearDownAll)                                                                                                                                                                                
01:02 +1: (tearDownAll)                                                                                                                                                                                
01:02 +1: All tests passed!                                                                                                                                                                            
Unhandled exception:
type 'Null' is not a subtype of type 'Map<String, dynamic>' in type cast
#0      ImageProcessor.process (package:screenshots/src/image_processor.dart:58:38)
#1      Screenshots.runProcessTests (package:screenshots/src/run.dart:487:28)
<asynchronous suspension>
#2      Screenshots.runTestsOnAll (package:screenshots/src/run.dart:383:13)
<asynchronous suspension>
#3      Screenshots.run (package:screenshots/src/run.dart:141:5)
<asynchronous suspension>
#4      AppContext.run.<anonymous closure> (package:tool_base/src/base/context.dart:148:19)
<asynchronous suspension>
#5      AppContext.run (package:tool_base/src/base/context.dart:147:12)
<asynchronous suspension>
#6      runInContext (package:screenshots/src/context_runner.dart:17:10)
<asynchronous suspension>
#7      main (file:///Users/fgrau/Development/screenshots/bin/main.dart:141:19)
<asynchronous suspension>
make: *** [screenshots_ios] Error 255

Any idea why is this?

Btw, can you share your screenshots.yaml file?

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I solved that one. It was due to the list of devices contained the iPhone 14 Pro Max, but the resources were missing.
Using iPhone 11 Pro Max works fine.

I am trying to capture screenshots for all the app, browsing across the app using taps. In ios works fine, I get all screenshots (small bug though: in the screenshots name, it replaces paces with %20... so screenshot with name like 'Login Screen' becomes:
`Ok, I solved that one. It was due to the list of devices contained the iPhone 14 Pro Max, but the resources were missing.
Using iPhone 11 Pro Max works fine.

I am trying to capture screenshots for all the app, browsing across the app using taps. In ios works fine, I get all screenshots (small bug though: in the screenshots name, it replaces paces with %20... so screenshot with name like 'Login - Screen' becomes: iPhone 11 Pro Max-Portrait-Login%20-%20Screen.png)

But I found a big blocker in Android.
As I take the second screenshot, I get this error:

converting surface to image failed; probably it was already an image?: 'package:integration_test/_callback_io.dart': Failed assertion: line 71 pos 12: '!_isSurfaceRendered': Surface already converted to an image
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following PlatformException was thrown running a test:
PlatformException(Could not copy the pixels, Flutter surface must be converted to image first, null,
null)

When the exception was thrown, this was the stack:
#0      StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:652:7)
#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:310:18)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

I guess this might be due to it tries to execute convertFlutterSurfaceToImage several times?
Have you been able to generate several screenshots in Android?
Am I doing something wrong?

final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  Future<void> takeScreenshot(WidgetTester tester, String name, {Duration? timeout}) async {
[...]
}
[...]
 group('E2E Screenshots', () {
    testWidgets('fetching screenshots... ', (tester) async {
     [...]
      WidgetsFlutterBinding.ensureInitialized();
      await EasyLocalization.ensureInitialized();
      await tester.pumpWidget(app.MainWidget());
      print(' > 1. Screenshot: Login (H)');
      await tester.pumpAndSettle();
      expect(find.byType(Login), findsOneWidget);
      await takeScreenshot(tester, '0 - Login');
      
      print(' > 2. Screenshot: Dashboard (A)');
      await tester.enterText(txtEmail, 'email@address.com');
      await tester.enterText(txtPassword, '123123');
      await tester.ensureVisible(btnLogin);
      await tester.pump();
      expect(btnLogin, findsOneWidget);
      (btnLogin.evaluate().first.widget as KydemyButton).onPressed!();
      await tester.pumpAndSettle(Duration(seconds: 1));
      expect(find.byType(Dashboard), findsOneWidget);
      await takeScreenshot(tester, '1 - Dashboard'); // ERROR HERE
      [...]
    }, timeout: Timeout(Duration(seconds: 300)));

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey man,
happy to hear that at least the basic setup is working.
The %20 is because the device is sending the screenshots pixels as a web request to your pc. I can check if it would be easy to add an urldecode step for the screenshots path.

For the other problem: Yes, I have been able to take multiple screenshots. The screenshots package is reverting the call every time for android: https://github.com/DrBu7cher/screenshots/blob/master/lib/src/capture_screen.dart#L33
The first line in the error output you send comes from here: https://github.com/DrBu7cher/screenshots/blob/master/lib/src/capture_screen.dart#L89
The rest is an uncaught exception. Which I find weird, because the surface is most probably an image already. Did you set a timeout on return await screenshot([...] and/or is it very small?
Because then it would not pump a frame, which it has to. I can push a fix, if this is the cause of your issue, but please check for me first.

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Jonas!

Yes, there is an await when taking the screenshot, and I had a timeout of 1 second in my tests.

const defaultTimeout = Duration(seconds: 1);
Future<void> takeScreenshot(WidgetTester tester, String name, {Duration? timeout}) async {
   return await screenshot(
      tester,
      binding,
      integrationTestChannel,
      PlatformDispatcher.instance,
      name,
      timeout: timeout ?? defaultTimeout,
    );
  }

I am unsure what the problem is... going to continue making changes and see if that works.

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! I've fixed it following this: flutter/flutter#92381 (comment)
Going to send you a PR, ok?

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just in general, you don’t have to ask for permission to open a PR. :)
So of course, open one where I can see exactly what fixed it for you.
I find it weird that it is working for me, but I did not test it with the native implementation yet.
Please only make sure you are using the latest flutter version (stable if you want) to make sure the fix is still necessary.

@graux
Copy link

@graux graux commented on 8ea5f7b Sep 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 3 PRs are running: Flutter 3.13.2 • channel stable

@jonasbadstuebner
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contributions!
I ran my pipeline for both Android and iOS so it is not broken with Patrol either. :)

Please sign in to comment.