diff --git a/docs/ci/platforms.mdx b/docs/ci/platforms.mdx
index fab125f87..a2d68f310 100644
--- a/docs/ci/platforms.mdx
+++ b/docs/ci/platforms.mdx
@@ -2,13 +2,13 @@
title: CI - Platforms
---
-In this document we'll outline a few ways for running Patrol UI tests of Flutter
+In this document, we'll outline a few ways to run Patrol UI tests of Flutter
apps.
Generally, the solutions for running UI tests of mobile apps can be divided into
2 groups:
-- Device labs - platforms that provide access mobile devices in the cloud. You
+- Device labs - platforms that provide access to mobile devices in the cloud. You
upload an app binary with tests to the device lab, which runs the tests and
reports the results back to you.
@@ -18,7 +18,7 @@ Generally, the solutions for running UI tests of mobile apps can be divided into
SDK tools, creating a virtual device, running tests, and collecting results.
There are quite a few solutions in each of these groups, and each is unique, but
-generaly, **device labs trade flexibility for ease of use**. They're a good fit
+generally, **device labs trade flexibility for ease of use**. They're a good fit
for most apps but make certain more complicated scenarios impossible.
# Device labs
@@ -39,7 +39,7 @@ See also:
### emulator.wtf
-[emulator.wtf] is a fairly new solution created by the people at CodeMagic. It
+[emulator.wtf] is a fairly new solution created by Madis Pink and Tauno Talimaa. It
claims to provide a 2-5x speedup compared to Firebase Test Lab, and 4-20x
speedup compared to spawning emulators on CI machines. It works similarly to
Firebase Test Lab - you upload your main apk, test apk, select emulators to run
@@ -70,7 +70,7 @@ our findings here.
Another popular device lab is [AWS Device Farm].
-If you use-case is highly specific, you might want to build an in-house device
+If your use-case is highly specific, you might want to build an in-house device
farm. A project that helps with this is [Simple Test Farm].
### Limitations
@@ -78,19 +78,19 @@ farm. A project that helps with this is [Simple Test Farm].
We mentioned above that device labs make certain scenarios impossible to
accomplish.
-An example of such scenario scanning a QR code. One of the apps we worked on had
-this feature, and we wanted to test it because it was critical part of the user
+An example of such a scenario scanning a QR code. One of the apps we worked on had
+this feature, and we wanted to test it because it was a critical part of the user
flow. When you have access to the shell filesystem (which you do have in the
"manual" approach, and don't have in the "device lab" approach), you can easily
[replace the scene that is visible in the camera's viewfinder][so_viewfinder].
-This is no possible on device labs.
+This is not possible on device labs.
# Traditional
### GitHub Actions
-[GitHub Actions] is a very popular CI/CD platforms, especially among open-source
+[GitHub Actions] is a very popular CI/CD platform, especially among open-source
projects thanks to unlimited minutes.
Unfortunately, running Flutter integration tests on GitHub Actions is not a
@@ -100,7 +100,7 @@ pleasant experience.
We used the [ReactiveCircus/android-emulator-runner] GitHub Action to run
Android emulator on GitHub Actions. Our takeaway is this: Running an Android
-emulator on default GitHub Actions runner is a bad idea. It is slow to start and
+emulator on the default GitHub Actions runner is a bad idea. It is slow to start and
unstable (apps crash randomly) and very slow. Really, really slow. We tried to
mitigate its instability by using [Test Butler], but it comes with its own
restrictions, most notably, it doesn't allow for Google Play Services.
@@ -108,10 +108,10 @@ restrictions, most notably, it doesn't allow for Google Play Services.
**iOS**
We use the [futureware-tech/simulator-action] GitHub Action to run iOS simulator
-on GitHub Actions is stable. But given that iOS simulator is just that – a
+on GitHub Actions is stable. But given that the iOS simulator is just that – a
simulator, not an emulator – the range of cases it can be used for is reduced.
-For example, there's no easy way to disable internet connection, which makes it
-very hard to test behavior of an app when offline.
+For example, there's no easy way to disable an internet connection, which makes it
+very hard to test the behavior of an app when offline.
Bear in mind that to run an iOS simulator on GitHub Actions, you have to use a
macOS runner. 1 minute on macos-latest counts as 10 minutes on ubuntu-latest.
diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx
index 56caf1167..fc966fb12 100644
--- a/docs/getting-started.mdx
+++ b/docs/getting-started.mdx
@@ -147,7 +147,7 @@ Psst... Android is a bit easier to set up, so we recommend starting with it!
4. Make sure that the **iOS Deployment Target** of `RunnerUITests` within the
**Build Settings** section is the same as `Runner`.
- The minimum supported **iOS Deployment Target** is `13.0`.
+ The minimum supported **iOS Deployment Target** is `11.0`.
![Xcode iOS deployment target](/assets/ios_deployment_target.png)
@@ -294,8 +294,10 @@ up. To run `integration_test/example_test.dart` on a local Android or iOS device
To prevent issues during Patrol tests, please follow these guidelines:
- 1. Avoid using `WidgetsFlutterBinding.ensureInitialized` as Patrol already initializes its own binding. Using another one may lead to complications.
- 2. Refrain from overriding `FlutterError.onError` as it hinders Patrol's ability to listen to timeouts and ultimately causes it to never finish.
+ 1. Do not call `IntegrationTestWidgetsFlutterBinding.ensureInitialized`.
+ Patrol automatically initializes its own test binding.
+ 2. Do not modify the global `FlutterError.onError` callback. Patrol's
+ internals depend on it.
### Going from here
diff --git a/docs/patrol/finders/advanced.mdx b/docs/patrol/finders/advanced.mdx
new file mode 100644
index 000000000..9a8aba064
--- /dev/null
+++ b/docs/patrol/finders/advanced.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /finders/advanced
+---
diff --git a/docs/patrol/finders/finders-setup.mdx b/docs/patrol/finders/finders-setup.mdx
new file mode 100644
index 000000000..ee38c9573
--- /dev/null
+++ b/docs/patrol/finders/finders-setup.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /finders/finders-setup
+---
diff --git a/docs/patrol/finders/overview.mdx b/docs/patrol/finders/overview.mdx
new file mode 100644
index 000000000..ff33c72bc
--- /dev/null
+++ b/docs/patrol/finders/overview.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /finders/overview
+---
diff --git a/docs/patrol/finders/usage.mdx b/docs/patrol/finders/usage.mdx
new file mode 100644
index 000000000..df9cd5211
--- /dev/null
+++ b/docs/patrol/finders/usage.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /finders/usage
+---
diff --git a/docs/patrol/native/advanced.mdx b/docs/patrol/native/advanced.mdx
new file mode 100644
index 000000000..f56697249
--- /dev/null
+++ b/docs/patrol/native/advanced.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /native/advanced
+---
diff --git a/docs/patrol/native/feature-parity.mdx b/docs/patrol/native/feature-parity.mdx
new file mode 100644
index 000000000..ddcb4c0d4
--- /dev/null
+++ b/docs/patrol/native/feature-parity.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /native/feature-parity
+---
diff --git a/docs/patrol/native/overview.mdx b/docs/patrol/native/overview.mdx
new file mode 100644
index 000000000..1d9a5ad43
--- /dev/null
+++ b/docs/patrol/native/overview.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /native/overview
+---
diff --git a/docs/patrol/native/usage.mdx b/docs/patrol/native/usage.mdx
new file mode 100644
index 000000000..ed1b1adaf
--- /dev/null
+++ b/docs/patrol/native/usage.mdx
@@ -0,0 +1,3 @@
+---
+redirect: /native/usage
+---
diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md
index bc4d442ab..96c4d1edd 100644
--- a/packages/patrol/CHANGELOG.md
+++ b/packages/patrol/CHANGELOG.md
@@ -12,6 +12,10 @@ This version requires version 2.3.0-dev.1 of `patrol_cli` package.
- Add `patrolSetUp()` and `patrolTearDown()` (#1721)
+## 2.3.2
+
+- Add `PatrolFinder.longPress()` (#1825)
+
## 2.3.1
- Add support for iOS 11 and 12 (#1733)
diff --git a/packages/patrol/example/integration_test/common.dart b/packages/patrol/example/integration_test/common.dart
index 8d782490a..ffb049789 100644
--- a/packages/patrol/example/integration_test/common.dart
+++ b/packages/patrol/example/integration_test/common.dart
@@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
-import 'package:patrol_example/main.dart';
+import 'package:patrol_example/main.dart' as app_main;
export 'package:flutter_test/flutter_test.dart';
export 'package:patrol/patrol.dart';
@@ -11,8 +11,7 @@ final _nativeAutomatorConfig = NativeAutomatorConfig(
);
Future createApp(PatrolTester $) async {
- await setUpTimezone();
- await $.pumpWidget(ExampleApp());
+ await app_main.main();
}
void patrol(
diff --git a/packages/patrol/example/ios/Podfile.lock b/packages/patrol/example/ios/Podfile.lock
index 5bd9677ea..4f53d1c17 100644
--- a/packages/patrol/example/ios/Podfile.lock
+++ b/packages/patrol/example/ios/Podfile.lock
@@ -70,4 +70,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: b2bb71756d032256bcb4043384dd40772d5e6a93
-COCOAPODS: 1.13.0
+COCOAPODS: 1.14.2
diff --git a/packages/patrol/example/lib/main.dart b/packages/patrol/example/lib/main.dart
index 5ef9c378f..1c378b738 100644
--- a/packages/patrol/example/lib/main.dart
+++ b/packages/patrol/example/lib/main.dart
@@ -10,10 +10,11 @@ import 'package:patrol_example/webview_screen.dart';
import 'package:timezone/data/latest.dart' as tz_data;
import 'package:timezone/timezone.dart' as tz;
-void main() {
- runApp(const ExampleApp());
+Future main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await setUpTimezone();
- setUpTimezone();
+ runApp(const ExampleApp());
}
Future setUpTimezone() async {
diff --git a/packages/patrol_cli/CHANGELOG.md b/packages/patrol_cli/CHANGELOG.md
index cfd14e77c..6c0b853cc 100644
--- a/packages/patrol_cli/CHANGELOG.md
+++ b/packages/patrol_cli/CHANGELOG.md
@@ -4,6 +4,10 @@
This version requires version 2.4.0-dev.4 of `patrol` package.
+## 2.2.2
+
+- Fix parsing `--dart-defines` when a value contains a comma (#1845)
+
## 2.2.1
- Fix bug with `test_bundle.dart` being sometimes garbled on Windows (#1797)
diff --git a/packages/patrol_cli/lib/src/dart_defines_reader.dart b/packages/patrol_cli/lib/src/dart_defines_reader.dart
index 9f7e0f6db..29cf25914 100644
--- a/packages/patrol_cli/lib/src/dart_defines_reader.dart
+++ b/packages/patrol_cli/lib/src/dart_defines_reader.dart
@@ -27,15 +27,20 @@ class DartDefinesReader {
Map _parse(List args) {
final map = {};
+ var currentKey = ' ';
for (final arg in args) {
+ if (!arg.contains('=') && currentKey != ' ') {
+ map[currentKey] = '${map[currentKey]}, $arg';
+ continue;
+ }
final parts = arg.splitFirst('=');
- final key = parts.first;
- if (key.contains(' ')) {
- throw FormatException('key "$key" contains whitespace');
+ currentKey = parts.first;
+ if (currentKey.contains(' ')) {
+ throw FormatException('key "$currentKey" contains whitespace');
}
final value = parts.elementAt(1);
- map[key] = value;
+ map[currentKey] = value;
}
return map;
diff --git a/packages/patrol_cli/lib/src/test_bundler.dart b/packages/patrol_cli/lib/src/test_bundler.dart
index 656a15e86..d0841bb11 100644
--- a/packages/patrol_cli/lib/src/test_bundler.dart
+++ b/packages/patrol_cli/lib/src/test_bundler.dart
@@ -80,7 +80,7 @@ Future main() async {
final didExploreTests = Completer();
final didExploreLifecycleCallbacks = Completer();
- // A special test to expore the hierarchy of groups and tests. This is a hack
+ // A special test to explore the hierarchy of groups and tests. This is a hack
// around https://github.com/dart-lang/test/issues/1998.
//
// This test must be the first to run. If not, the native side likely won't
diff --git a/packages/patrol_cli/test/general/dart_defines_reader_test.dart b/packages/patrol_cli/test/general/dart_defines_reader_test.dart
index d24993164..066a6c48a 100644
--- a/packages/patrol_cli/test/general/dart_defines_reader_test.dart
+++ b/packages/patrol_cli/test/general/dart_defines_reader_test.dart
@@ -43,6 +43,8 @@ void _test(Platform platform) {
final args = [
'EMAIL=email@wrong=domain.com',
r'PASSWORD="ny4ncat\n"',
+ 'ATTRIBUTES=foo',
+ 'bar',
];
expect(
@@ -50,6 +52,7 @@ void _test(Platform platform) {
equals({
'EMAIL': 'email@wrong=domain.com',
'PASSWORD': r'"ny4ncat\n"',
+ 'ATTRIBUTES': 'foo, bar',
}),
);
});
diff --git a/packages/patrol_finders/lib/src/custom_finders/patrol_finder.dart b/packages/patrol_finders/lib/src/custom_finders/patrol_finder.dart
index 79b0d3faa..e75931cc6 100644
--- a/packages/patrol_finders/lib/src/custom_finders/patrol_finder.dart
+++ b/packages/patrol_finders/lib/src/custom_finders/patrol_finder.dart
@@ -215,6 +215,48 @@ class PatrolFinder extends MatchFinder {
);
}
+ /// Waits until this finder finds at least 1 visible widget and then makes
+ /// long press gesture on it.
+ ///
+ /// Example:
+ /// ```dart
+ /// // long presses on the first widget having Key('createAccount')
+ /// await $(#createAccount).longPress();
+ /// ```
+ ///
+ /// If the finder finds more than 1 widget, you can choose which one to make
+ /// long press on:
+ ///
+ /// ```dart
+ /// // long presses on the third TextButton widget
+ /// await $(TextButton).at(2).longPress();
+ /// ```
+ ///
+ /// After long press gesture this method automatically calls
+ /// [WidgetTester.pumpAndSettle]. If you want to disable this behavior,
+ /// set [settlePolicy] to [SettlePolicy.noSettle].
+ ///
+ /// See also:
+ /// - [PatrolFinder.waitUntilVisible], which is used to wait for the widget
+ /// to appear
+ /// - [WidgetController.longPress]
+ Future longPress({
+ @Deprecated('Use settlePolicy argument instead') bool? andSettle,
+ SettlePolicy? settlePolicy,
+ Duration? visibleTimeout,
+ Duration? settleTimeout,
+ }) async {
+ await tester.longPress(
+ this,
+ settlePolicy: chooseSettlePolicy(
+ andSettle: andSettle,
+ settlePolicy: settlePolicy,
+ ),
+ visibleTimeout: visibleTimeout,
+ settleTimeout: settleTimeout,
+ );
+ }
+
/// Waits until this finder finds at least 1 visible widget and then enters
/// text into it.
///
diff --git a/packages/patrol_finders/lib/src/custom_finders/patrol_tester.dart b/packages/patrol_finders/lib/src/custom_finders/patrol_tester.dart
index 6ecf94207..9620e6a38 100644
--- a/packages/patrol_finders/lib/src/custom_finders/patrol_tester.dart
+++ b/packages/patrol_finders/lib/src/custom_finders/patrol_tester.dart
@@ -275,6 +275,55 @@ class PatrolTester {
});
}
+ /// Waits until this finder finds at least 1 visible widget and then makes
+ /// long press gesture on it.
+ ///
+ /// Example:
+ /// ```dart
+ /// // long presses on the first widget having Key('createAccount')
+ /// await $(#createAccount).longPress();
+ /// ```
+ ///
+ /// If the finder finds more than 1 widget, you can choose which one to make
+ /// long press on:
+ ///
+ /// ```dart
+ /// // long presses on the third TextButton widget
+ /// await $(TextButton).at(2).longPress();
+ /// ```
+ ///
+ /// After long press gesture this method automatically calls
+ /// [WidgetTester.pumpAndSettle]. If you want to disable this behavior,
+ /// set [settlePolicy] to [SettlePolicy.noSettle].
+ ///
+ /// See also:
+ /// - [PatrolFinder.waitUntilVisible], which is used to wait for the widget
+ /// to appear
+ /// - [WidgetController.longPress]
+ Future longPress(
+ Finder finder, {
+ @Deprecated('Use settlePolicy argument instead') bool? andSettle,
+ SettlePolicy? settlePolicy,
+ Duration? visibleTimeout,
+ Duration? settleTimeout,
+ }) {
+ return TestAsyncUtils.guard(() async {
+ final resolvedFinder = await waitUntilVisible(
+ finder,
+ timeout: visibleTimeout,
+ );
+ await tester.longPress(resolvedFinder.first);
+ final settle = chooseSettlePolicy(
+ andSettle: andSettle,
+ settlePolicy: settlePolicy,
+ );
+ await _performPump(
+ settlePolicy: settle,
+ settleTimeout: settleTimeout,
+ );
+ });
+ }
+
/// Waits until [finder] finds at least 1 visible widget and then enters text
/// into it.
///
diff --git a/packages/patrol_finders/test/patrol_finder_test.dart b/packages/patrol_finders/test/patrol_finder_test.dart
index 633464b80..54d7b13c3 100644
--- a/packages/patrol_finders/test/patrol_finder_test.dart
+++ b/packages/patrol_finders/test/patrol_finder_test.dart
@@ -294,6 +294,71 @@ void main() {
});
});
+ group('longPress()', () {
+ patrolWidgetTest(
+ 'throws exception when no widget to make longPress gesture on is found',
+ ($) async {
+ await $.pumpWidget(const MaterialApp());
+
+ await expectLater(
+ $('some text').longPress,
+ throwsA(isA()),
+ );
+ },
+ );
+
+ patrolWidgetTest('makes longPress gesture on widget and pumps',
+ ($) async {
+ var count = 0;
+ await $.pumpWidget(
+ MaterialApp(
+ home: StatefulBuilder(
+ builder: (state, setState) => Column(
+ children: [
+ Text('count: $count'),
+ GestureDetector(
+ onLongPress: () => setState(() => count++),
+ child: const Text('Long press'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ await $('Long press').longPress();
+ expect($('count: 1'), findsOneWidget);
+ });
+
+ patrolWidgetTest(
+ 'makes longPress gesture on the first widget by default and pumps',
+ ($) async {
+ var count = 0;
+ await $.pumpWidget(
+ MaterialApp(
+ home: StatefulBuilder(
+ builder: (state, setState) => Column(
+ children: [
+ Text('count: $count'),
+ GestureDetector(
+ onLongPress: () => setState(() => count++),
+ child: const Text('Long press'),
+ ),
+ GestureDetector(
+ onLongPress: () {},
+ child: const Text('Long press'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ await $('Long press').longPress();
+ expect($('count: 1'), findsOneWidget);
+ });
+ });
+
group('enterText()', () {
patrolWidgetTest(
'throws exception when no widget to enter text in is found',