From 7e2746c1f2b80eaa1bd57faca13eaded277e730e Mon Sep 17 00:00:00 2001 From: piotruela Date: Fri, 20 Sep 2024 16:30:47 +0200 Subject: [PATCH 01/18] Implement native scrollTo for iOS and use it in webview_leancode_test.dart --- .../webview_leancode_test.dart | 49 +++++++------ dev/e2e_app/pubspec.lock | 2 +- .../kotlin/pl/leancode/patrol/Automator.kt | 4 ++ .../pl/leancode/patrol/AutomatorServer.kt | 20 ++++++ .../pl/leancode/patrol/contracts/Contracts.kt | 24 +++++++ .../patrol/contracts/NativeAutomatorServer.kt | 6 ++ .../AutomatorServer/Automator/Automator.swift | 12 ++++ .../Automator/IOSAutomator.swift | 68 +++++++++++++++++++ .../AutomatorServer/AutomatorServer.swift | 34 ++++++++-- .../Classes/AutomatorServer/Contracts.swift | 12 +++- .../NativeAutomatorServer.swift | 12 ++++ .../lib/src/native/contracts/contracts.dart | 31 +++++++++ .../lib/src/native/contracts/contracts.g.dart | 25 +++++++ .../contracts/native_automator_client.dart | 9 +++ .../lib/src/native/native_automator.dart | 27 ++++++++ .../lib/src/native/native_automator2.dart | 28 ++++++++ .../compatibility_checker.dart | 2 +- .../lib/api/contracts.dart | 37 ++++++++++ .../lib/api/contracts.g.dart | 29 ++++++++ schema.dart | 11 +++ 20 files changed, 406 insertions(+), 36 deletions(-) diff --git a/dev/e2e_app/integration_test/webview_leancode_test.dart b/dev/e2e_app/integration_test/webview_leancode_test.dart index d328f23ff..1d8d1abba 100644 --- a/dev/e2e_app/integration_test/webview_leancode_test.dart +++ b/dev/e2e_app/integration_test/webview_leancode_test.dart @@ -1,27 +1,8 @@ +import 'dart:io' as io; + import 'common.dart'; void main() { - patrol('interacts with the LeanCode website in a webview', ($) async { - await createApp($); - - await $('Open webview (LeanCode)').scrollTo().tap(); - await $.pump(Duration(seconds: 8)); - - try { - await $.native.tap(Selector(text: 'Accept cookies')); - } on PatrolActionException catch (_) { - // ignore - } - await $.pumpAndSettle(); - - await $.native.enterTextByIndex( - 'test@leancode.pl', - index: 0, - keyboardBehavior: KeyboardBehavior.showAndDismiss, - ); - await $.native.tap(Selector(text: 'Subscribe')); - }); - patrol('interacts with the LeanCode website in a webview native2', ($) async { await createApp($); @@ -32,8 +13,8 @@ void main() { try { await $.native2.tap( NativeSelector( - android: AndroidSelector(text: 'Accept cookies'), - ios: IOSSelector(label: 'Accept cookies'), + android: AndroidSelector(text: 'ACCEPT ALL COOKIES'), + ios: IOSSelector(label: 'ACCEPT ALL COOKIES'), ), ); } on PatrolActionException catch (_) { @@ -41,11 +22,27 @@ void main() { } await $.pumpAndSettle(); - await $.native2.enterTextByIndex( - 'test@leancode.pl', - index: 0, + if (io.Platform.isIOS) { + await $.native2.scrollTo( + NativeSelector( + ios: IOSSelector(placeholderValue: 'Type your email'), + ), + maxScrolls: 20, + ); + } + + await $.pump(Duration(seconds: 5)); + + await $.native2.enterText( + NativeSelector( + android: AndroidSelector(className: 'android.widget.EditText'), + ios: IOSSelector(placeholderValue: 'Type your email'), + ), + text: 'test@leancode.pl', keyboardBehavior: KeyboardBehavior.showAndDismiss, + tapLocation: Offset(0.5, 0.5), ); + await $.native2.tap( NativeSelector( android: AndroidSelector(text: 'Subscribe'), diff --git a/dev/e2e_app/pubspec.lock b/dev/e2e_app/pubspec.lock index 4ec538357..0b252dbf4 100644 --- a/dev/e2e_app/pubspec.lock +++ b/dev/e2e_app/pubspec.lock @@ -446,7 +446,7 @@ packages: path: "../../packages/patrol" relative: true source: path - version: "3.10.0" + version: "3.11.0" patrol_finders: dependency: transitive description: diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt index 335a6d217..12f115654 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt @@ -451,6 +451,10 @@ class Automator private constructor() { delay() } + fun scrollTo(uiSelector: UiSelector, bySelector: BySelector, index: Int, maxScrolls: Long) { + throw PatrolException("scrollTo() is not implemented") + } + fun waitUntilVisible(uiSelector: UiSelector, bySelector: BySelector, index: Int, timeout: Long? = null) { Logger.d("waitUntilVisible(): $uiSelector, $bySelector") diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt index 32b5392fc..946351f4e 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt @@ -262,6 +262,26 @@ class AutomatorServer(private val automation: Automator) : NativeAutomatorServer ) } + override fun scrollTo(request: Contracts.ScrollToRequest) { + if (request.selector != null) { + automation.scrollTo( + uiSelector = request.selector.toUiSelector(), + bySelector = request.selector.toBySelector(), + index = request.selector.instance?.toInt() ?: 0, + maxScrolls = request.maxScrolls, + ) + } else if (request.androidSelector != null) { + automation.scrollTo( + uiSelector = request.androidSelector.toUiSelector(), + bySelector = request.androidSelector.toBySelector(), + index = request.androidSelector.instance?.toInt() ?: 0, + maxScrolls = request.maxScrolls, + ) + } else { + throw PatrolException("scrollTo(): neither selector nor androidSelector are set") + } + } + override fun waitUntilVisible(request: WaitUntilVisibleRequest) { if (request.selector != null) { automation.waitUntilVisible( diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt index 01025b84d..47c884ea2 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/Contracts.kt @@ -557,6 +557,12 @@ class Contracts { fun hasTimeoutMillis(): Boolean { return timeoutMillis != null } + fun hasDx(): Boolean { + return dx != null + } + fun hasDy(): Boolean { + return dy != null + } } data class SwipeRequest ( @@ -589,6 +595,24 @@ class Contracts { } } + data class ScrollToRequest ( + val selector: Selector? = null, + val androidSelector: AndroidSelector? = null, + val iosSelector: IOSSelector? = null, + val appId: String, + val maxScrolls: Long + ){ + fun hasSelector(): Boolean { + return selector != null + } + fun hasAndroidSelector(): Boolean { + return androidSelector != null + } + fun hasIosSelector(): Boolean { + return iosSelector != null + } + } + data class DarkModeRequest ( val appId: String ) diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/NativeAutomatorServer.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/NativeAutomatorServer.kt index ade144b74..7e89e67e6 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/NativeAutomatorServer.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/contracts/NativeAutomatorServer.kt @@ -29,6 +29,7 @@ abstract class NativeAutomatorServer { abstract fun tapAt(request: Contracts.TapAtRequest) abstract fun enterText(request: Contracts.EnterTextRequest) abstract fun swipe(request: Contracts.SwipeRequest) + abstract fun scrollTo(request: Contracts.ScrollToRequest) abstract fun waitUntilVisible(request: Contracts.WaitUntilVisibleRequest) abstract fun pressVolumeUp() abstract fun pressVolumeDown() @@ -131,6 +132,11 @@ abstract class NativeAutomatorServer { swipe(body) Response(OK) }, + "scrollTo" bind POST to { + val body = json.fromJson(it.bodyString(), Contracts.ScrollToRequest::class.java) + scrollTo(body) + Response(OK) + }, "waitUntilVisible" bind POST to { val body = json.fromJson(it.bodyString(), Contracts.WaitUntilVisibleRequest::class.java) waitUntilVisible(body) diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift index 6df758e25..398b84948 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift @@ -238,6 +238,18 @@ extension Selector { dy: CGFloat ) throws func swipe(from start: CGVector, to end: CGVector, inApp bundleId: String) throws + func scrollTo( + on selector: Selector, + inApp bundleId: String, + atIndex index: Int, + maxScrolls scrolls: Int + ) throws + func scrollTo( + on selector: IOSSelector, + inApp bundleId: String, + atIndex index: Int, + maxScrolls scrolls: Int + ) throws func waitUntilVisible( on selector: Selector, inApp bundleId: String, diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift index 108337369..2894261ee 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift @@ -326,6 +326,48 @@ } } + func scrollTo(on selector: Selector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int) throws { + var view = createLogMessage(element: "view", from: selector) + view += " in app \(bundleId)" + + try runAction("scrolling to \(view)") { + let app = try self.getApp(withBundleId: bundleId) + + let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) + + Logger.shared.i("waiting for existence of \(view)") + guard + let element = self.waitFor( + query: query, index: selector.instance ?? 0, timeout: self.timeout) + else { + throw PatrolError.viewNotExists(view) + } + + try self.scrollToElement(element: element, bundleId: bundleId, index: index, scrolls: scrolls) + } + } + + func scrollTo(on selector: IOSSelector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int) throws { + var view = createLogMessage(element: "view", from: selector) + view += " in app \(bundleId)" + + try runAction("scrolling to \(view)") { + let app = try self.getApp(withBundleId: bundleId) + + let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) + + Logger.shared.i("waiting for existence of \(view)") + guard + let element = self.waitFor( + query: query, index: selector.instance ?? 0, timeout: self.timeout) + else { + throw PatrolError.viewNotExists(view) + } + + try self.scrollToElement(element: element, bundleId: bundleId, index: index, scrolls: scrolls) + } + } + func waitUntilVisible( on selector: Selector, inApp bundleId: String, @@ -889,6 +931,32 @@ element.typeText(delete + data) } + + private func scrollToElement(element: XCUIElement, bundleId: String, index: Int, scrolls: Int) throws { + var attempts = 0 // Track the number of scrolls + while try isVisible(element: element, bundleId: bundleId, index: index) == false + { + if attempts >= scrolls { + throw PatrolError.viewNotExists("Element not found after \(scrolls) scrolls.") + } + let app = try self.getApp(withBundleId: bundleId) + + let startCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.9)) + let endCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) + startCoordinate.press(forDuration: 0.1, thenDragTo: endCoordinate) + + attempts += 1 // Increment the scroll count after each scroll + } + } + + private func isVisible(element: XCUIElement, bundleId: String, index: Int) throws-> Bool + { + let app = try self.getApp(withBundleId: bundleId) + + guard element.exists && element.isHittable && !CGRectIsEmpty(element.frame) else { return false } + + return CGRectContainsRect(app.windows.element(boundBy: index).frame, element.frame) + } private func isSimulator() -> Bool { #if targetEnvironment(simulator) diff --git a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift index 62456fff0..9b9b59885 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift @@ -155,8 +155,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx, - dy: request.dy + dx: request.dx ?? 0.9, + dy: request.dy ?? 0.9 ) } else if let selector = request.selector { try automator.enterText( @@ -165,8 +165,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx, - dy: request.dy + dx: request.dx ?? 0.9, + dy: request.dy ?? 0.9 ) } else if let iosSelector = request.iosSelector { try automator.enterText( @@ -175,8 +175,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx, - dy: request.dy + dx: request.dx ?? 0.9, + dy: request.dy ?? 0.9 ) } else { throw PatrolError.internal("enterText(): neither index nor selector are set") @@ -194,6 +194,28 @@ } } + func scrollTo(request: ScrollToRequest) throws { + return try runCatching { + if let selector = request.selector { + return try automator.scrollTo( + on: selector, + inApp: request.appId, + atIndex: request.selector?.instance ?? 0, + maxScrolls: request.maxScrolls + ) + } else if let iosSelector = request.iosSelector { + return try automator.scrollTo( + on: iosSelector, + inApp: request.appId, + atIndex: request.iosSelector?.instance ?? 0, + maxScrolls: request.maxScrolls + ) + } else { + throw PatrolError.internal("scrollTo(): neither selector nor iosSelector are set") + } + } + } + func waitUntilVisible(request: WaitUntilVisibleRequest) throws { return try runCatching { if let selector = request.selector { diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift index 7fd506acd..5dd6e54a0 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Contracts.swift @@ -318,8 +318,8 @@ public struct EnterTextRequest: Codable { public var iosSelector: IOSSelector? public var keyboardBehavior: KeyboardBehavior public var timeoutMillis: Int? - public var dx: Double - public var dy: Double + public var dx: Double? + public var dy: Double? } public struct SwipeRequest: Codable { @@ -339,6 +339,14 @@ public struct WaitUntilVisibleRequest: Codable { public var timeoutMillis: Int? } +public struct ScrollToRequest: Codable { + public var selector: Selector? + public var androidSelector: AndroidSelector? + public var iosSelector: IOSSelector? + public var appId: String + public var maxScrolls: Int +} + public struct DarkModeRequest: Codable { public var appId: String } diff --git a/packages/patrol/darwin/Classes/AutomatorServer/NativeAutomatorServer.swift b/packages/patrol/darwin/Classes/AutomatorServer/NativeAutomatorServer.swift index d02998b9b..5cb5f684e 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/NativeAutomatorServer.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/NativeAutomatorServer.swift @@ -22,6 +22,7 @@ protocol NativeAutomatorServer { func tapAt(request: TapAtRequest) throws func enterText(request: EnterTextRequest) throws func swipe(request: SwipeRequest) throws + func scrollTo(request: ScrollToRequest) throws func waitUntilVisible(request: WaitUntilVisibleRequest) throws func pressVolumeUp() throws func pressVolumeDown() throws @@ -143,6 +144,12 @@ extension NativeAutomatorServer { return HTTPResponse(.ok) } + private func scrollToHandler(request: HTTPRequest) throws -> HTTPResponse { + let requestArg = try JSONDecoder().decode(ScrollToRequest.self, from: request.body) + try scrollTo(request: requestArg) + return HTTPResponse(.ok) + } + private func waitUntilVisibleHandler(request: HTTPRequest) throws -> HTTPResponse { let requestArg = try JSONDecoder().decode(WaitUntilVisibleRequest.self, from: request.body) try waitUntilVisible(request: requestArg) @@ -361,6 +368,11 @@ extension NativeAutomatorServer { request: request, handler: swipeHandler) } + server.route(.POST, "scrollTo") { + request in handleRequest( + request: request, + handler: scrollToHandler) + } server.route(.POST, "waitUntilVisible") { request in handleRequest( request: request, diff --git a/packages/patrol/lib/src/native/contracts/contracts.dart b/packages/patrol/lib/src/native/contracts/contracts.dart index b0d55dce0..ec056e20f 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.dart @@ -1049,6 +1049,37 @@ class WaitUntilVisibleRequest with EquatableMixin { ]; } +@JsonSerializable() +class ScrollToRequest with EquatableMixin { + ScrollToRequest({ + this.selector, + this.androidSelector, + this.iosSelector, + required this.appId, + required this.maxScrolls, + }); + + factory ScrollToRequest.fromJson(Map json) => + _$ScrollToRequestFromJson(json); + + final Selector? selector; + final AndroidSelector? androidSelector; + final IOSSelector? iosSelector; + final String appId; + final int maxScrolls; + + Map toJson() => _$ScrollToRequestToJson(this); + + @override + List get props => [ + selector, + androidSelector, + iosSelector, + appId, + maxScrolls, + ]; +} + @JsonSerializable() class DarkModeRequest with EquatableMixin { DarkModeRequest({ diff --git a/packages/patrol/lib/src/native/contracts/contracts.g.dart b/packages/patrol/lib/src/native/contracts/contracts.g.dart index a69f4d13c..e6be5b6ce 100644 --- a/packages/patrol/lib/src/native/contracts/contracts.g.dart +++ b/packages/patrol/lib/src/native/contracts/contracts.g.dart @@ -656,6 +656,31 @@ Map _$WaitUntilVisibleRequestToJson( 'timeoutMillis': instance.timeoutMillis, }; +ScrollToRequest _$ScrollToRequestFromJson(Map json) => + ScrollToRequest( + selector: json['selector'] == null + ? null + : Selector.fromJson(json['selector'] as Map), + androidSelector: json['androidSelector'] == null + ? null + : AndroidSelector.fromJson( + json['androidSelector'] as Map), + iosSelector: json['iosSelector'] == null + ? null + : IOSSelector.fromJson(json['iosSelector'] as Map), + appId: json['appId'] as String, + maxScrolls: (json['maxScrolls'] as num).toInt(), + ); + +Map _$ScrollToRequestToJson(ScrollToRequest instance) => + { + 'selector': instance.selector, + 'androidSelector': instance.androidSelector, + 'iosSelector': instance.iosSelector, + 'appId': instance.appId, + 'maxScrolls': instance.maxScrolls, + }; + DarkModeRequest _$DarkModeRequestFromJson(Map json) => DarkModeRequest( appId: json['appId'] as String, diff --git a/packages/patrol/lib/src/native/contracts/native_automator_client.dart b/packages/patrol/lib/src/native/contracts/native_automator_client.dart index 9f8a580b2..c2e2c1239 100644 --- a/packages/patrol/lib/src/native/contracts/native_automator_client.dart +++ b/packages/patrol/lib/src/native/contracts/native_automator_client.dart @@ -170,6 +170,15 @@ class NativeAutomatorClient { ); } + Future scrollTo( + ScrollToRequest request, + ) { + return _sendRequest( + 'scrollTo', + request.toJson(), + ); + } + Future waitUntilVisible( WaitUntilVisibleRequest request, ) { diff --git a/packages/patrol/lib/src/native/native_automator.dart b/packages/patrol/lib/src/native/native_automator.dart index 14edb9420..749bf9d13 100644 --- a/packages/patrol/lib/src/native/native_automator.dart +++ b/packages/patrol/lib/src/native/native_automator.dart @@ -7,6 +7,9 @@ import 'package:patrol/src/native/contracts/contracts.dart' as contracts; import 'package:patrol/src/native/contracts/contracts.dart'; import 'package:patrol/src/native/contracts/native_automator_client.dart'; +/// Default maximum number of drags during scrolling. +const _defaultScrollMaxIteration = 15; + /// Thrown when a native action fails. class PatrolActionException implements Exception { /// Creates a new [PatrolActionException]. @@ -768,6 +771,30 @@ class NativeAutomator { ); } + /// Scrolls to the native view specified by [selector]. + /// + /// Performs [maxScrolls] number of scrolls before giving up. + /// If [maxScrolls] is not specified, it utilizes the + /// [_defaultScrollMaxIteration] duration from the configuration. + /// + /// Works only on iOS. + Future scrollTo( + Selector selector, { + String? appId, + int? maxScrolls, + }) async { + await _wrapRequest( + 'scrollTo', + () => _client.scrollTo( + ScrollToRequest( + selector: selector, + appId: appId ?? resolvedAppId, + maxScrolls: maxScrolls ?? _defaultScrollMaxIteration, + ), + ), + ); + } + /// Waits until the native view specified by [selector] becomes visible. /// It waits for the view to become visible for [timeout] duration. If /// [timeout] is not specified, it utilizes the diff --git a/packages/patrol/lib/src/native/native_automator2.dart b/packages/patrol/lib/src/native/native_automator2.dart index 745c1d82f..7648bd44c 100644 --- a/packages/patrol/lib/src/native/native_automator2.dart +++ b/packages/patrol/lib/src/native/native_automator2.dart @@ -9,6 +9,9 @@ import 'package:patrol/src/native/contracts/native_automator_client.dart'; import 'package:patrol/src/native/native_automator.dart'; import 'package:patrol/src/native/native_automator.dart' as native_automator; +/// Default maximum number of drags during scrolling. +const _defaultScrollMaxIteration = 15; + /// This class represents the result of [NativeAutomator.getNativeViews]. class GetNativeViewsResult { /// Creates a new [GetNativeViewsResult]. @@ -643,6 +646,31 @@ class NativeAutomator2 { ); } + /// Scrolls to the native view specified by [selector]. + /// + /// Performs [maxScrolls] number of scrolls before giving up. + /// If [maxScrolls] is not specified, it utilizes the + /// [_defaultScrollMaxIteration] duration from the configuration. + /// + /// Works only on iOS. + Future scrollTo( + NativeSelector selector, { + String? appId, + int? maxScrolls, + }) async { + await _wrapRequest( + 'scrollTo', + () => _client.scrollTo( + ScrollToRequest( + androidSelector: selector.android, + iosSelector: selector.ios, + appId: appId ?? resolvedAppId, + maxScrolls: maxScrolls ?? _defaultScrollMaxIteration, + ), + ), + ); + } + /// Waits until the native view specified by [selector] becomes visible. /// It waits for the view to become visible for [timeout] duration. If /// [timeout] is not specified, it utilizes the diff --git a/packages/patrol_cli/lib/src/compatibility_checker/compatibility_checker.dart b/packages/patrol_cli/lib/src/compatibility_checker/compatibility_checker.dart index 9797942eb..43dee27f6 100644 --- a/packages/patrol_cli/lib/src/compatibility_checker/compatibility_checker.dart +++ b/packages/patrol_cli/lib/src/compatibility_checker/compatibility_checker.dart @@ -63,7 +63,7 @@ class CompatibilityChecker { process.listenStdOut( (line) async { - if (line.startsWith('- patrol ')) { + if (line.startsWith('- patrol ') && !packageCompleter.isCompleted) { packageCompleter.complete(line.split(' ').last); } }, diff --git a/packages/patrol_devtools_extension/lib/api/contracts.dart b/packages/patrol_devtools_extension/lib/api/contracts.dart index d7bc9649b..ec056e20f 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.dart @@ -949,6 +949,8 @@ class EnterTextRequest with EquatableMixin { this.iosSelector, required this.keyboardBehavior, this.timeoutMillis, + this.dx, + this.dy, }); factory EnterTextRequest.fromJson(Map json) => @@ -962,6 +964,8 @@ class EnterTextRequest with EquatableMixin { final IOSSelector? iosSelector; final KeyboardBehavior keyboardBehavior; final int? timeoutMillis; + final double? dx; + final double? dy; Map toJson() => _$EnterTextRequestToJson(this); @@ -975,6 +979,8 @@ class EnterTextRequest with EquatableMixin { iosSelector, keyboardBehavior, timeoutMillis, + dx, + dy, ]; } @@ -1043,6 +1049,37 @@ class WaitUntilVisibleRequest with EquatableMixin { ]; } +@JsonSerializable() +class ScrollToRequest with EquatableMixin { + ScrollToRequest({ + this.selector, + this.androidSelector, + this.iosSelector, + required this.appId, + required this.maxScrolls, + }); + + factory ScrollToRequest.fromJson(Map json) => + _$ScrollToRequestFromJson(json); + + final Selector? selector; + final AndroidSelector? androidSelector; + final IOSSelector? iosSelector; + final String appId; + final int maxScrolls; + + Map toJson() => _$ScrollToRequestToJson(this); + + @override + List get props => [ + selector, + androidSelector, + iosSelector, + appId, + maxScrolls, + ]; +} + @JsonSerializable() class DarkModeRequest with EquatableMixin { DarkModeRequest({ diff --git a/packages/patrol_devtools_extension/lib/api/contracts.g.dart b/packages/patrol_devtools_extension/lib/api/contracts.g.dart index 43928232c..e6be5b6ce 100644 --- a/packages/patrol_devtools_extension/lib/api/contracts.g.dart +++ b/packages/patrol_devtools_extension/lib/api/contracts.g.dart @@ -587,6 +587,8 @@ EnterTextRequest _$EnterTextRequestFromJson(Map json) => keyboardBehavior: $enumDecode(_$KeyboardBehaviorEnumMap, json['keyboardBehavior']), timeoutMillis: (json['timeoutMillis'] as num?)?.toInt(), + dx: (json['dx'] as num?)?.toDouble(), + dy: (json['dy'] as num?)?.toDouble(), ); Map _$EnterTextRequestToJson(EnterTextRequest instance) => @@ -599,6 +601,8 @@ Map _$EnterTextRequestToJson(EnterTextRequest instance) => 'iosSelector': instance.iosSelector, 'keyboardBehavior': _$KeyboardBehaviorEnumMap[instance.keyboardBehavior]!, 'timeoutMillis': instance.timeoutMillis, + 'dx': instance.dx, + 'dy': instance.dy, }; const _$KeyboardBehaviorEnumMap = { @@ -652,6 +656,31 @@ Map _$WaitUntilVisibleRequestToJson( 'timeoutMillis': instance.timeoutMillis, }; +ScrollToRequest _$ScrollToRequestFromJson(Map json) => + ScrollToRequest( + selector: json['selector'] == null + ? null + : Selector.fromJson(json['selector'] as Map), + androidSelector: json['androidSelector'] == null + ? null + : AndroidSelector.fromJson( + json['androidSelector'] as Map), + iosSelector: json['iosSelector'] == null + ? null + : IOSSelector.fromJson(json['iosSelector'] as Map), + appId: json['appId'] as String, + maxScrolls: (json['maxScrolls'] as num).toInt(), + ); + +Map _$ScrollToRequestToJson(ScrollToRequest instance) => + { + 'selector': instance.selector, + 'androidSelector': instance.androidSelector, + 'iosSelector': instance.iosSelector, + 'appId': instance.appId, + 'maxScrolls': instance.maxScrolls, + }; + DarkModeRequest _$DarkModeRequestFromJson(Map json) => DarkModeRequest( appId: json['appId'] as String, diff --git a/schema.dart b/schema.dart index 681c9acd5..49f9b0d42 100644 --- a/schema.dart +++ b/schema.dart @@ -225,6 +225,8 @@ class EnterTextRequest { IOSSelector? iosSelector; late KeyboardBehavior keyboardBehavior; int? timeoutMillis; + double? dx; + double? dy; } class SwipeRequest { @@ -244,6 +246,14 @@ class WaitUntilVisibleRequest { int? timeoutMillis; } +class ScrollToRequest { + Selector? selector; + AndroidSelector? androidSelector; + IOSSelector? iosSelector; + late String appId; + late int maxScrolls; +} + class DarkModeRequest { late String appId; } @@ -317,6 +327,7 @@ abstract class NativeAutomator { void tapAt(TapAtRequest request); void enterText(EnterTextRequest request); void swipe(SwipeRequest request); + void scrollTo(ScrollToRequest request); void waitUntilVisible(WaitUntilVisibleRequest request); // volume settings From 5333b7c458087fb930bbd914d07ca50af2bc5383 Mon Sep 17 00:00:00 2001 From: piotruela Date: Fri, 20 Sep 2024 16:34:32 +0200 Subject: [PATCH 02/18] Fix code formatting --- .../pl/leancode/patrol/AutomatorServer.kt | 4 +- .../Automator/IOSAutomator.swift | 114 ++++++++++-------- 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt index 946351f4e..f1383b781 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt @@ -268,14 +268,14 @@ class AutomatorServer(private val automation: Automator) : NativeAutomatorServer uiSelector = request.selector.toUiSelector(), bySelector = request.selector.toBySelector(), index = request.selector.instance?.toInt() ?: 0, - maxScrolls = request.maxScrolls, + maxScrolls = request.maxScrolls ) } else if (request.androidSelector != null) { automation.scrollTo( uiSelector = request.androidSelector.toUiSelector(), bySelector = request.androidSelector.toBySelector(), index = request.androidSelector.instance?.toInt() ?: 0, - maxScrolls = request.maxScrolls, + maxScrolls = request.maxScrolls ) } else { throw PatrolException("scrollTo(): neither selector nor androidSelector are set") diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift index 2894261ee..18b9ef107 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift @@ -326,45 +326,51 @@ } } - func scrollTo(on selector: Selector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int) throws { - var view = createLogMessage(element: "view", from: selector) - view += " in app \(bundleId)" - - try runAction("scrolling to \(view)") { - let app = try self.getApp(withBundleId: bundleId) - - let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) - - Logger.shared.i("waiting for existence of \(view)") - guard - let element = self.waitFor( + func scrollTo( + on selector: Selector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int + ) throws { + var view = createLogMessage(element: "view", from: selector) + view += " in app \(bundleId)" + + try runAction("scrolling to \(view)") { + let app = try self.getApp(withBundleId: bundleId) + + let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) + + Logger.shared.i("waiting for existence of \(view)") + guard + let element = self.waitFor( query: query, index: selector.instance ?? 0, timeout: self.timeout) - else { - throw PatrolError.viewNotExists(view) - } + else { + throw PatrolError.viewNotExists(view) + } - try self.scrollToElement(element: element, bundleId: bundleId, index: index, scrolls: scrolls) + try self.scrollToElement( + element: element, bundleId: bundleId, index: index, scrolls: scrolls) } } - func scrollTo(on selector: IOSSelector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int) throws { - var view = createLogMessage(element: "view", from: selector) - view += " in app \(bundleId)" - - try runAction("scrolling to \(view)") { - let app = try self.getApp(withBundleId: bundleId) + func scrollTo( + on selector: IOSSelector, inApp bundleId: String, atIndex index: Int, maxScrolls scrolls: Int + ) throws { + var view = createLogMessage(element: "view", from: selector) + view += " in app \(bundleId)" + + try runAction("scrolling to \(view)") { + let app = try self.getApp(withBundleId: bundleId) - let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) + let query = app.descendants(matching: .any).matching(selector.toNSPredicate()) - Logger.shared.i("waiting for existence of \(view)") - guard - let element = self.waitFor( + Logger.shared.i("waiting for existence of \(view)") + guard + let element = self.waitFor( query: query, index: selector.instance ?? 0, timeout: self.timeout) - else { - throw PatrolError.viewNotExists(view) - } + else { + throw PatrolError.viewNotExists(view) + } - try self.scrollToElement(element: element, bundleId: bundleId, index: index, scrolls: scrolls) + try self.scrollToElement( + element: element, bundleId: bundleId, index: index, scrolls: scrolls) } } @@ -931,31 +937,33 @@ element.typeText(delete + data) } - - private func scrollToElement(element: XCUIElement, bundleId: String, index: Int, scrolls: Int) throws { - var attempts = 0 // Track the number of scrolls - while try isVisible(element: element, bundleId: bundleId, index: index) == false - { - if attempts >= scrolls { - throw PatrolError.viewNotExists("Element not found after \(scrolls) scrolls.") - } - let app = try self.getApp(withBundleId: bundleId) - - let startCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.9)) - let endCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) - startCoordinate.press(forDuration: 0.1, thenDragTo: endCoordinate) - - attempts += 1 // Increment the scroll count after each scroll - } - } - - private func isVisible(element: XCUIElement, bundleId: String, index: Int) throws-> Bool + + private func scrollToElement(element: XCUIElement, bundleId: String, index: Int, scrolls: Int) + throws { + var attempts = 0 // Track the number of scrolls + while try isVisible(element: element, bundleId: bundleId, index: index) == false { + if attempts >= scrolls { + throw PatrolError.viewNotExists("Element not found after \(scrolls) scrolls.") + } let app = try self.getApp(withBundleId: bundleId) - - guard element.exists && element.isHittable && !CGRectIsEmpty(element.frame) else { return false } - - return CGRectContainsRect(app.windows.element(boundBy: index).frame, element.frame) + + let startCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.9)) + let endCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) + startCoordinate.press(forDuration: 0.1, thenDragTo: endCoordinate) + + attempts += 1 // Increment the scroll count after each scroll + } + } + + private func isVisible(element: XCUIElement, bundleId: String, index: Int) throws -> Bool { + let app = try self.getApp(withBundleId: bundleId) + + guard element.exists && element.isHittable && !CGRectIsEmpty(element.frame) else { + return false + } + + return CGRectContainsRect(app.windows.element(boundBy: index).frame, element.frame) } private func isSimulator() -> Bool { From 5ca8606046c1627dd821ad7aaeb9f6b6d9d1c38a Mon Sep 17 00:00:00 2001 From: piotruela Date: Fri, 20 Sep 2024 16:37:14 +0200 Subject: [PATCH 03/18] Add swipe to fix webview_stackoverflow_test.dart --- .../integration_test/webview_stackoverflow_test.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dev/e2e_app/integration_test/webview_stackoverflow_test.dart b/dev/e2e_app/integration_test/webview_stackoverflow_test.dart index e6ae94f62..d31a2f7ac 100644 --- a/dev/e2e_app/integration_test/webview_stackoverflow_test.dart +++ b/dev/e2e_app/integration_test/webview_stackoverflow_test.dart @@ -19,6 +19,12 @@ void main() { // bug: using `Email` and `Password` selectors doesn't work (#1554) await $.native.enterTextByIndex('test@leancode.pl', index: 0); + + await $.native.swipe( + from: Offset(0.5, 0.5), + to: Offset(0.5, 0.1), + ); + await $.native.enterTextByIndex('ny4ncat', index: 1); await $.native.tap(Selector(text: 'Log in')); }, @@ -52,6 +58,12 @@ void main() { // bug: using `Email` and `Password` selectors doesn't work (#1554) await $.native2.enterTextByIndex('test@leancode.pl', index: 0); + + await $.native.swipe( + from: Offset(0.5, 0.5), + to: Offset(0.5, 0.1), + ); + await $.native2.enterTextByIndex('ny4ncat', index: 1); await $.native2.tap( NativeSelector( From 5615fb1059bb96d90fa15cb7062c30627b21b7c4 Mon Sep 17 00:00:00 2001 From: piotruela Date: Fri, 20 Sep 2024 16:44:19 +0200 Subject: [PATCH 04/18] Setup Flutter in check-semver.yaml --- .github/workflows/check-semver.yaml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-semver.yaml b/.github/workflows/check-semver.yaml index 10f325cb2..44f841309 100644 --- a/.github/workflows/check-semver.yaml +++ b/.github/workflows/check-semver.yaml @@ -11,6 +11,12 @@ jobs: name: Check semver runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + flutter-version: ['3.22.x'] + flutter-channel: ['stable'] + steps: - name: Clone repository uses: actions/checkout@v4 @@ -24,10 +30,11 @@ jobs: echo "last_version=$last_version" >> $GITHUB_ENV echo "::set-output name=last_version::$last_version" - - name: Set up Dart - uses: dart-lang/setup-dart@v1 + - name: Set up Flutter + uses: subosito/flutter-action@v2 with: - sdk: stable + flutter-version: ${{ matrix.flutter-version }} + channel: ${{ matrix.flutter-channel }} - name: Install dart-apitool run: dart pub global activate dart_apitool From bc6e4da4cc8d6424cb9323f7bf2a7543750a2808 Mon Sep 17 00:00:00 2001 From: piotruela Date: Sun, 29 Sep 2024 21:08:35 +0200 Subject: [PATCH 05/18] Bump uiautomator and implement native scrollTo for Android --- dev/e2e_app/pubspec.lock | 24 ++--- packages/patrol/android/build.gradle | 2 +- .../kotlin/pl/leancode/patrol/Automator.kt | 22 ++++- .../pl/leancode/patrol/AutomatorServer.kt | 6 -- .../pl/leancode/patrol/ContractsExtensions.kt | 94 +++++++++---------- .../lib/src/native/native_automator.dart | 9 +- .../lib/src/native/native_automator2.dart | 9 +- 7 files changed, 92 insertions(+), 74 deletions(-) diff --git a/dev/e2e_app/pubspec.lock b/dev/e2e_app/pubspec.lock index 0b252dbf4..1f1e6c486 100644 --- a/dev/e2e_app/pubspec.lock +++ b/dev/e2e_app/pubspec.lock @@ -364,18 +364,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -412,18 +412,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" package_config: dependency: transitive description: @@ -608,10 +608,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timezone: dependency: "direct main" description: @@ -648,10 +648,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/packages/patrol/android/build.gradle b/packages/patrol/android/build.gradle index b966e6d97..54b8c7a1e 100644 --- a/packages/patrol/android/build.gradle +++ b/packages/patrol/android/build.gradle @@ -67,7 +67,7 @@ android { api "androidx.test:runner:1.5.1" api "androidx.test.espresso:espresso-core:3.5.0" - api "androidx.test.uiautomator:uiautomator:2.2.0" + api "androidx.test.uiautomator:uiautomator:2.3.0" // We need to downgrade http4k-bom to 4.48.0.0 because apps with kotlin version (ext.kotlin_version) lower than 1.8 // end up with compile-time errors. We can find more details in a similar problem: diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt index 12f115654..d04d4011f 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt @@ -16,11 +16,14 @@ import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.Configurator +import androidx.test.uiautomator.Direction import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiObject import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.UiObjectNotFoundException import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.Until +import io.ktor.util.reflect.instanceOf import pl.leancode.patrol.contracts.Contracts.AndroidNativeView import pl.leancode.patrol.contracts.Contracts.AndroidSelector import pl.leancode.patrol.contracts.Contracts.KeyboardBehavior @@ -373,7 +376,7 @@ class Automator private constructor() { uiDevice.click(x.toInt(), y.toInt()) } - uiObject.text = text + uiObject.setText(text) if (keyboardBehavior == KeyboardBehavior.showAndDismiss) { pressBack() // Hide keyboard. @@ -411,7 +414,7 @@ class Automator private constructor() { uiDevice.click(x.toInt(), y.toInt()) } - uiObject.text = text + uiObject.setText(text) if (keyboardBehavior == KeyboardBehavior.showAndDismiss) { pressBack() // Hide keyboard. @@ -451,8 +454,19 @@ class Automator private constructor() { delay() } - fun scrollTo(uiSelector: UiSelector, bySelector: BySelector, index: Int, maxScrolls: Long) { - throw PatrolException("scrollTo() is not implemented") + fun scrollTo(bySelector: BySelector) { + Logger.d("scrollTo(): $bySelector") + + val scrollableSelector = By.scrollable(true) + + waitForView(scrollableSelector, 0) + + + val scrollableUiObject = uiDevice.findObject(scrollableSelector) + ?: throw UiObjectNotFoundException("$scrollableSelector") + + scrollableUiObject.scrollUntil(Direction.DOWN, Until.findObject(bySelector)) + ?: throw UiObjectNotFoundException("$bySelector") } fun waitUntilVisible(uiSelector: UiSelector, bySelector: BySelector, index: Int, timeout: Long? = null) { diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt index f1383b781..2d8c165da 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt @@ -265,17 +265,11 @@ class AutomatorServer(private val automation: Automator) : NativeAutomatorServer override fun scrollTo(request: Contracts.ScrollToRequest) { if (request.selector != null) { automation.scrollTo( - uiSelector = request.selector.toUiSelector(), bySelector = request.selector.toBySelector(), - index = request.selector.instance?.toInt() ?: 0, - maxScrolls = request.maxScrolls ) } else if (request.androidSelector != null) { automation.scrollTo( - uiSelector = request.androidSelector.toUiSelector(), bySelector = request.androidSelector.toBySelector(), - index = request.androidSelector.instance?.toInt() ?: 0, - maxScrolls = request.maxScrolls ) } else { throw PatrolException("scrollTo(): neither selector nor androidSelector are set") diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt index c5cf4eda1..2ee234d2a 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt @@ -29,35 +29,35 @@ fun Selector.toUiSelector(): UiSelector { var selector = UiSelector() if (hasText()) { - selector = selector.text(text) + selector = selector.text(text!!) } if (hasTextStartsWith()) { - selector = selector.textStartsWith(textStartsWith) + selector = selector.textStartsWith(textStartsWith!!) } if (hasTextContains()) { - selector = selector.textContains(textContains) + selector = selector.textContains(textContains!!) } if (hasClassName()) { - selector = selector.className(className) + selector = selector.className(className!!) } if (hasContentDescription()) { - selector = selector.description(contentDescription) + selector = selector.description(contentDescription!!) } if (hasContentDescriptionStartsWith()) { - selector = selector.descriptionStartsWith(contentDescriptionStartsWith) + selector = selector.descriptionStartsWith(contentDescriptionStartsWith!!) } if (hasContentDescriptionContains()) { - selector = selector.descriptionContains(contentDescriptionContains) + selector = selector.descriptionContains(contentDescriptionContains!!) } if (hasResourceId()) { - selector = selector.resourceId(resourceId) + selector = selector.resourceId(resourceId!!) } if (hasInstance()) { @@ -73,7 +73,7 @@ fun Selector.toUiSelector(): UiSelector { } if (hasPkg()) { - selector = selector.packageName(pkg) + selector = selector.packageName(pkg!!) } return selector @@ -99,28 +99,28 @@ fun Selector.toBySelector(): BySelector { var bySelector = if (hasText()) { matchedText = true - By.text(text) + By.text(text!!) } else if (hasTextStartsWith()) { matchedTextStartsWith = true - By.textStartsWith(textStartsWith) + By.textStartsWith(textStartsWith!!) } else if (hasTextContains()) { matchedTextContains = true - By.textContains(textContains) + By.textContains(textContains!!) } else if (hasClassName()) { matchedClassName = true - By.clazz(className) + By.clazz(className!!) } else if (hasContentDescription()) { matchedContentDescription = true - By.desc(contentDescription) + By.desc(contentDescription!!) } else if (hasContentDescriptionStartsWith()) { matchedContentDescriptionStartsWith = true - By.descStartsWith(contentDescriptionStartsWith) + By.descStartsWith(contentDescriptionStartsWith!!) } else if (hasContentDescriptionContains()) { matchedContentDescriptionContains = true - By.descContains(contentDescriptionContains) + By.descContains(contentDescriptionContains!!) } else if (hasResourceId()) { matchedResourceId = true - By.res(resourceId) + By.res(resourceId!!) } else if (hasInstance()) { throw IllegalArgumentException("instance() argument is not supported for BySelector") } else if (hasEnabled()) { @@ -131,41 +131,41 @@ fun Selector.toBySelector(): BySelector { By.focused(focused!!) } else if (hasPkg()) { matchedPkg = true - By.pkg(pkg) + By.pkg(pkg!!) } else { throw IllegalArgumentException("SelectorQuery is empty") } if (!matchedText && hasText()) { - bySelector = By.copy(bySelector).text(text) + bySelector = By.copy(bySelector).text(text!!) } if (!matchedTextStartsWith && hasTextStartsWith()) { - bySelector = By.copy(bySelector).textStartsWith(textStartsWith) + bySelector = By.copy(bySelector).textStartsWith(textStartsWith!!) } if (!matchedTextContains && hasTextContains()) { - bySelector = By.copy(bySelector).textContains(textContains) + bySelector = By.copy(bySelector).textContains(textContains!!) } if (!matchedClassName && hasClassName()) { - bySelector = By.copy(bySelector).clazz(className) + bySelector = By.copy(bySelector).clazz(className!!) } if (!matchedContentDescription && hasContentDescription()) { - bySelector = By.copy(bySelector).desc(contentDescription) + bySelector = By.copy(bySelector).desc(contentDescription!!) } if (!matchedContentDescriptionStartsWith && hasContentDescriptionStartsWith()) { - bySelector = By.copy(bySelector).descStartsWith(contentDescriptionStartsWith) + bySelector = By.copy(bySelector).descStartsWith(contentDescriptionStartsWith!!) } if (!matchedContentDescriptionContains && hasContentDescriptionContains()) { - bySelector = By.copy(bySelector).descContains(contentDescriptionContains) + bySelector = By.copy(bySelector).descContains(contentDescriptionContains!!) } if (!matchedResourceId && hasResourceId()) { - bySelector = By.copy(bySelector).res(resourceId) + bySelector = By.copy(bySelector).res(resourceId!!) } if (hasInstance()) { @@ -181,7 +181,7 @@ fun Selector.toBySelector(): BySelector { } if (!matchedPkg && hasPkg()) { - bySelector = bySelector.pkg(pkg) + bySelector = bySelector.pkg(pkg!!) } return bySelector @@ -191,7 +191,7 @@ fun AndroidSelector.toUiSelector(): UiSelector { var selector = UiSelector() if (hasClassName()) { - selector = selector.className(className) + selector = selector.className(className!!) } if (hasIsCheckable()) { selector = selector.checkable(isCheckable!!) @@ -221,28 +221,28 @@ fun AndroidSelector.toUiSelector(): UiSelector { selector = selector.selected(isSelected!!) } if (hasApplicationPackage()) { - selector = selector.packageName(applicationPackage) + selector = selector.packageName(applicationPackage!!) } if (hasContentDescription()) { - selector = selector.description(contentDescription) + selector = selector.description(contentDescription!!) } if (hasContentDescriptionStartsWith()) { - selector = selector.descriptionStartsWith(contentDescriptionStartsWith) + selector = selector.descriptionStartsWith(contentDescriptionStartsWith!!) } if (hasContentDescriptionContains()) { - selector = selector.descriptionContains(contentDescriptionContains) + selector = selector.descriptionContains(contentDescriptionContains!!) } if (hasText()) { - selector = selector.text(text) + selector = selector.text(text!!) } if (hasTextStartsWith()) { - selector = selector.textStartsWith(textStartsWith) + selector = selector.textStartsWith(textStartsWith!!) } if (hasTextContains()) { - selector = selector.textContains(textContains) + selector = selector.textContains(textContains!!) } if (hasResourceName()) { - selector = selector.resourceId(resourceName) + selector = selector.resourceId(resourceName!!) } if (hasInstance()) { selector = selector.instance(instance!!.toInt()) @@ -259,7 +259,7 @@ fun AndroidSelector.toBySelector(): BySelector { var selector: BySelector? = null if (hasClassName()) { - selector = By.clazz(className) + selector = By.clazz(className!!) } if (hasIsCheckable()) { selector = selector?.checkable(isCheckable!!) ?: By.checkable(isCheckable!!) @@ -289,32 +289,32 @@ fun AndroidSelector.toBySelector(): BySelector { selector = selector?.selected(isSelected!!) ?: By.selected(isSelected!!) } if (hasApplicationPackage()) { - selector = selector?.pkg(applicationPackage) ?: By.pkg(applicationPackage) + selector = selector?.pkg(applicationPackage!!) ?: By.pkg(applicationPackage!!) } if (hasContentDescription()) { - selector = selector?.desc(contentDescription) ?: By.desc(contentDescription) + selector = selector?.desc(contentDescription!!) ?: By.desc(contentDescription!!) } if (hasContentDescriptionStartsWith()) { - selector = selector?.descStartsWith(contentDescriptionStartsWith) ?: By.descStartsWith( - contentDescriptionStartsWith + selector = selector?.descStartsWith(contentDescriptionStartsWith!!) ?: By.descStartsWith( + contentDescriptionStartsWith!! ) } if (hasContentDescriptionContains()) { - selector = selector?.descContains(contentDescriptionContains) ?: By.descContains( - contentDescriptionContains + selector = selector?.descContains(contentDescriptionContains!!) ?: By.descContains( + contentDescriptionContains!! ) } if (hasText()) { - selector = selector?.text(text) ?: By.text(text) + selector = selector?.text(text!!) ?: By.text(text!!) } if (hasTextStartsWith()) { - selector = selector?.textStartsWith(textStartsWith) ?: By.textStartsWith(textStartsWith) + selector = selector?.textStartsWith(textStartsWith!!) ?: By.textStartsWith(textStartsWith!!) } if (hasTextContains()) { - selector = selector?.textContains(textContains) ?: By.textContains(textContains) + selector = selector?.textContains(textContains!!) ?: By.textContains(textContains!!) } if (hasResourceName()) { - selector = selector?.res(resourceName) ?: By.res(resourceName) + selector = selector?.res(resourceName!!) ?: By.res(resourceName!!) } if (selector == null) { diff --git a/packages/patrol/lib/src/native/native_automator.dart b/packages/patrol/lib/src/native/native_automator.dart index 749bf9d13..a19e9513d 100644 --- a/packages/patrol/lib/src/native/native_automator.dart +++ b/packages/patrol/lib/src/native/native_automator.dart @@ -771,13 +771,18 @@ class NativeAutomator { ); } - /// Scrolls to the native view specified by [selector]. + /// Scrolls down until finds the native view specified by [selector]. /// + /// On iOS: /// Performs [maxScrolls] number of scrolls before giving up. /// If [maxScrolls] is not specified, it utilizes the /// [_defaultScrollMaxIteration] duration from the configuration. /// - /// Works only on iOS. + /// On Android: + /// Scrolls to bottom while searching for the view. If the view is not found + /// until the end, it throws an exception. + /// + /// [Selector.instance] field is ignored on Android. Future scrollTo( Selector selector, { String? appId, diff --git a/packages/patrol/lib/src/native/native_automator2.dart b/packages/patrol/lib/src/native/native_automator2.dart index 7648bd44c..c7969481f 100644 --- a/packages/patrol/lib/src/native/native_automator2.dart +++ b/packages/patrol/lib/src/native/native_automator2.dart @@ -646,13 +646,18 @@ class NativeAutomator2 { ); } - /// Scrolls to the native view specified by [selector]. + /// Scrolls down until finds the native view specified by [selector]. /// + /// On iOS: /// Performs [maxScrolls] number of scrolls before giving up. /// If [maxScrolls] is not specified, it utilizes the /// [_defaultScrollMaxIteration] duration from the configuration. /// - /// Works only on iOS. + /// On Android: + /// Scrolls to bottom while searching for the view. If the view is not found + /// until the end, it throws an exception. + /// + /// [AndroidSelector.instance] field is ignored. Future scrollTo( NativeSelector selector, { String? appId, From daad1cad0ed7092ac00e275cc2c77f5c98630f86 Mon Sep 17 00:00:00 2001 From: piotruela Date: Sun, 29 Sep 2024 21:49:27 +0200 Subject: [PATCH 06/18] Bump min sdk and compileSdk --- dev/e2e_app/android/app/build.gradle | 2 +- packages/patrol/android/build.gradle | 2 +- packages/patrol/example/pubspec.yaml | 2 +- packages/patrol/pubspec.yaml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/e2e_app/android/app/build.gradle b/dev/e2e_app/android/app/build.gradle index 1aea450db..62dca90d9 100644 --- a/dev/e2e_app/android/app/build.gradle +++ b/dev/e2e_app/android/app/build.gradle @@ -23,7 +23,7 @@ if (flutterVersionName == null) { } android { - compileSdk 33 + compileSdk 34 namespace "pl.leancode.patrol.e2e_app" kotlinOptions { diff --git a/packages/patrol/android/build.gradle b/packages/patrol/android/build.gradle index 54b8c7a1e..ffe351937 100644 --- a/packages/patrol/android/build.gradle +++ b/packages/patrol/android/build.gradle @@ -35,7 +35,7 @@ allprojects { android { namespace "pl.leancode.patrol" - compileSdk 33 + compileSdk 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/patrol/example/pubspec.yaml b/packages/patrol/example/pubspec.yaml index ed2fc64e9..86489f6af 100644 --- a/packages/patrol/example/pubspec.yaml +++ b/packages/patrol/example/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: sdk: flutter flutter_bloc: ^8.1.3 flutter_holo_date_picker: ^1.1.3 - flutter_local_notifications: ^16.1.0 + flutter_local_notifications: ^17.2.3 flutter_svg: ^2.0.9 geocoding: ^2.1.1 geolocator: ^10.1.0 diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index 702be1d0c..bb5d9540a 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -13,8 +13,8 @@ screenshots: path: screenshots/logo.png environment: - sdk: '>=3.2.0 <4.0.0' - flutter: '>=3.22.0' + sdk: '>=3.5.0 <4.0.0' + flutter: '>=3.24.0' dependencies: boolean_selector: ^2.1.1 From 902b07f6774f7f2d20b0a30e5bf8ce72ea6cc58e Mon Sep 17 00:00:00 2001 From: piotruela Date: Sun, 29 Sep 2024 21:50:13 +0200 Subject: [PATCH 07/18] Prepare changelog --- packages/patrol/CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index 6a63e7e83..d8b304e13 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -1,8 +1,15 @@ +## Unreleased + +- Add native `scrollTo` method. (#2343) +- Updated Flutter environment: sdk to >=3.5.0 <4.0.0 and Flutter version to >=3.24.0 (#2343) +- Bump `compileSdk` to 34. (#2343) + ## 3.11.0 - Add code coverage collection support. (#2294) - No throw error in `selectFineLocation` when it's already selected. (#2302) - Add option to select tap location in `enterText` and `enterTextByIndex` (#2312) + This version requires version 3.2.0 of `patrol/patrol_cli` package. ## 3.10.0 From dc76013cb2b2c6cce3671f4b5be0200efd81231f Mon Sep 17 00:00:00 2001 From: piotruela Date: Sun, 29 Sep 2024 21:54:28 +0200 Subject: [PATCH 08/18] Bump Flutter version to 3.24.x in gh workflow files --- .cirrus.yml | 4 ++-- .github/workflows/check-semver.yaml | 2 +- .github/workflows/patrol-prepare.yaml | 8 ++++---- .github/workflows/patrol-publish.yaml | 2 +- .github/workflows/patrol_cli-prepare.yaml | 2 +- .github/workflows/patrol_devtools_extension-prepare.yaml | 2 +- .github/workflows/patrol_finders-prepare.yaml | 2 +- .github/workflows/patrol_finders-publish.yaml | 2 +- .github/workflows/prepare-e2e_app.yaml | 8 ++++---- .github/workflows/test-android-device.yaml | 2 +- .github/workflows/test-ios-device.yaml | 2 +- .github/workflows/test-ios-simulator-webview.yaml | 2 +- .github/workflows/test-ios-simulator.yaml | 2 +- .github/workflows/test-macos.yaml | 2 +- 14 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 48de37a78..714e5b804 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,7 +9,7 @@ test_linux_task: kvm: "true" env: PATH: $HOME/.pub-cache/bin:$HOME/fvm/default/bin:$CIRRUS_WORKING_DIR/fvm:${PATH} - FLUTTER_VERSION: "3.22.0" + FLUTTER_VERSION: "3.24.0" EMULATOR_API_LEVEL: "34" EMULATOR_ABI: google_apis_playstore;x86_64 EMULATOR_IMAGE: system-images;android-${EMULATOR_API_LEVEL};${EMULATOR_ABI} @@ -66,7 +66,7 @@ test_macos_task: image: ghcr.io/cirruslabs/macos-sonoma-xcode:latest env: PATH: $HOME/.pub-cache/bin:$HOME/fvm/default/bin:${PATH} - FLUTTER_VERSION: "3.22.0" + FLUTTER_VERSION: "3.24.0" timeout_in: 30m set_up_fvm_script: | diff --git a/.github/workflows/check-semver.yaml b/.github/workflows/check-semver.yaml index 44f841309..718c6f82c 100644 --- a/.github/workflows/check-semver.yaml +++ b/.github/workflows/check-semver.yaml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] steps: diff --git a/.github/workflows/patrol-prepare.yaml b/.github/workflows/patrol-prepare.yaml index 04204960b..9f475d4dc 100644 --- a/.github/workflows/patrol-prepare.yaml +++ b/.github/workflows/patrol-prepare.yaml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [windows-latest] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -65,7 +65,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -133,7 +133,7 @@ jobs: fail-fast: false matrix: os: [macos-13] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -173,7 +173,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: diff --git a/.github/workflows/patrol-publish.yaml b/.github/workflows/patrol-publish.yaml index 617035079..4e89662ba 100644 --- a/.github/workflows/patrol-publish.yaml +++ b/.github/workflows/patrol-publish.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] steps: diff --git a/.github/workflows/patrol_cli-prepare.yaml b/.github/workflows/patrol_cli-prepare.yaml index 8742bd22c..6a817ea49 100644 --- a/.github/workflows/patrol_cli-prepare.yaml +++ b/.github/workflows/patrol_cli-prepare.yaml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: diff --git a/.github/workflows/patrol_devtools_extension-prepare.yaml b/.github/workflows/patrol_devtools_extension-prepare.yaml index 4c7d6b2b2..c41f98050 100644 --- a/.github/workflows/patrol_devtools_extension-prepare.yaml +++ b/.github/workflows/patrol_devtools_extension-prepare.yaml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: diff --git a/.github/workflows/patrol_finders-prepare.yaml b/.github/workflows/patrol_finders-prepare.yaml index e324dd2bf..301f419d8 100644 --- a/.github/workflows/patrol_finders-prepare.yaml +++ b/.github/workflows/patrol_finders-prepare.yaml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: diff --git a/.github/workflows/patrol_finders-publish.yaml b/.github/workflows/patrol_finders-publish.yaml index 9d1641cc0..7d99e21a7 100644 --- a/.github/workflows/patrol_finders-publish.yaml +++ b/.github/workflows/patrol_finders-publish.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] steps: diff --git a/.github/workflows/prepare-e2e_app.yaml b/.github/workflows/prepare-e2e_app.yaml index 1d4da05e5..3e147ddc5 100644 --- a/.github/workflows/prepare-e2e_app.yaml +++ b/.github/workflows/prepare-e2e_app.yaml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [windows-latest] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -66,7 +66,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -121,7 +121,7 @@ jobs: fail-fast: false matrix: os: [macos-13] - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: @@ -193,7 +193,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] defaults: diff --git a/.github/workflows/test-android-device.yaml b/.github/workflows/test-android-device.yaml index 3ec95b801..4d3d4288b 100644 --- a/.github/workflows/test-android-device.yaml +++ b/.github/workflows/test-android-device.yaml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] os: ['Android API'] include: diff --git a/.github/workflows/test-ios-device.yaml b/.github/workflows/test-ios-device.yaml index 3ca7c1296..fceb61ed5 100644 --- a/.github/workflows/test-ios-device.yaml +++ b/.github/workflows/test-ios-device.yaml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] device_model: ['iphone14pro'] os_version: ['16.6'] diff --git a/.github/workflows/test-ios-simulator-webview.yaml b/.github/workflows/test-ios-simulator-webview.yaml index 88c1a264e..1e8f2ce3c 100644 --- a/.github/workflows/test-ios-simulator-webview.yaml +++ b/.github/workflows/test-ios-simulator-webview.yaml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ["3.22.x"] + flutter-version: ["3.24.x"] flutter-channel: ["stable"] device_model: [iPhone 14] os: [iOS] diff --git a/.github/workflows/test-ios-simulator.yaml b/.github/workflows/test-ios-simulator.yaml index 1d7bdefa1..44e24984a 100644 --- a/.github/workflows/test-ios-simulator.yaml +++ b/.github/workflows/test-ios-simulator.yaml @@ -24,7 +24,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ["3.22.x"] + flutter-version: ["3.24.x"] flutter-channel: ["stable"] device_model: [iPhone SE (3rd generation), iPhone 14, iPad (10th generation)] diff --git a/.github/workflows/test-macos.yaml b/.github/workflows/test-macos.yaml index 679fc0005..3f5974b42 100644 --- a/.github/workflows/test-macos.yaml +++ b/.github/workflows/test-macos.yaml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - flutter-version: ['3.22.x'] + flutter-version: ['3.24.x'] flutter-channel: ['stable'] device_model: [macOS] os: [macOS] From e539fb2a59f1303536a064dbad419c3ca2e73ab4 Mon Sep 17 00:00:00 2001 From: piotruela Date: Sun, 29 Sep 2024 21:58:16 +0200 Subject: [PATCH 09/18] Revert bumping sdk version to >=3.5.0 --- packages/patrol/CHANGELOG.md | 2 +- packages/patrol/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index d8b304e13..ea367435d 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -1,7 +1,7 @@ ## Unreleased - Add native `scrollTo` method. (#2343) -- Updated Flutter environment: sdk to >=3.5.0 <4.0.0 and Flutter version to >=3.24.0 (#2343) +- Updated Flutter version to >=3.24.0 (#2343) - Bump `compileSdk` to 34. (#2343) ## 3.11.0 diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index bb5d9540a..ed22f7a29 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -13,7 +13,7 @@ screenshots: path: screenshots/logo.png environment: - sdk: '>=3.5.0 <4.0.0' + sdk: '>=3.2.0 <4.0.0' flutter: '>=3.24.0' dependencies: From a6c123d17eadd6b18e03c24f7fc020410fd7e341 Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:10:36 +0200 Subject: [PATCH 10/18] Bump leancode_lint to 14.1.0 and format kotlin code --- dev/cli_tests/pubspec.yaml | 2 +- dev/e2e_app/lib/main.dart | 3 ++ dev/e2e_app/pubspec.yaml | 2 +- packages/adb/pubspec.yaml | 2 +- .../kotlin/pl/leancode/patrol/Automator.kt | 2 -- .../pl/leancode/patrol/AutomatorServer.kt | 4 +-- .../example/lib/ui/components/scaffold.dart | 2 +- packages/patrol/example/pubspec.yaml | 2 +- packages/patrol/pubspec.yaml | 2 +- packages/patrol_cli/pubspec.yaml | 2 +- .../patrol_devtools_extension/pubspec.lock | 30 +++++++++---------- .../patrol_devtools_extension/pubspec.yaml | 2 +- packages/patrol_finders/example/pubspec.yaml | 2 +- packages/patrol_finders/pubspec.yaml | 2 +- packages/patrol_gen/pubspec.lock | 6 ++-- packages/patrol_gen/pubspec.yaml | 2 +- 16 files changed, 34 insertions(+), 33 deletions(-) diff --git a/dev/cli_tests/pubspec.yaml b/dev/cli_tests/pubspec.yaml index 02053eb26..65cbe3caf 100644 --- a/dev/cli_tests/pubspec.yaml +++ b/dev/cli_tests/pubspec.yaml @@ -8,4 +8,4 @@ dependencies: path: ^1.8.3 dev_dependencies: - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 diff --git a/dev/e2e_app/lib/main.dart b/dev/e2e_app/lib/main.dart index ad7356c0d..a412e23aa 100644 --- a/dev/e2e_app/lib/main.dart +++ b/dev/e2e_app/lib/main.dart @@ -76,6 +76,9 @@ class _ExampleHomePageState extends State { @override Widget build(BuildContext context) { _appLinks.uriLinkStream.listen((uri) { + if (!context.mounted) { + return; + } Navigator.of(context).push( MaterialPageRoute( builder: (_) => ApplinkScreen( diff --git a/dev/e2e_app/pubspec.yaml b/dev/e2e_app/pubspec.yaml index eafeadd52..9a135c41d 100644 --- a/dev/e2e_app/pubspec.yaml +++ b/dev/e2e_app/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 patrol: path: ../../packages/patrol diff --git a/packages/adb/pubspec.yaml b/packages/adb/pubspec.yaml index 3c803ccef..7646c27f1 100644 --- a/packages/adb/pubspec.yaml +++ b/packages/adb/pubspec.yaml @@ -9,7 +9,7 @@ environment: dev_dependencies: custom_lint: ^0.6.4 - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 mocktail: ^1.0.1 test: ^1.24.9 diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt index d04d4011f..474f653e9 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/Automator.kt @@ -23,7 +23,6 @@ import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.UiObjectNotFoundException import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.Until -import io.ktor.util.reflect.instanceOf import pl.leancode.patrol.contracts.Contracts.AndroidNativeView import pl.leancode.patrol.contracts.Contracts.AndroidSelector import pl.leancode.patrol.contracts.Contracts.KeyboardBehavior @@ -461,7 +460,6 @@ class Automator private constructor() { waitForView(scrollableSelector, 0) - val scrollableUiObject = uiDevice.findObject(scrollableSelector) ?: throw UiObjectNotFoundException("$scrollableSelector") diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt index 2d8c165da..3676ed209 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/AutomatorServer.kt @@ -265,11 +265,11 @@ class AutomatorServer(private val automation: Automator) : NativeAutomatorServer override fun scrollTo(request: Contracts.ScrollToRequest) { if (request.selector != null) { automation.scrollTo( - bySelector = request.selector.toBySelector(), + bySelector = request.selector.toBySelector() ) } else if (request.androidSelector != null) { automation.scrollTo( - bySelector = request.androidSelector.toBySelector(), + bySelector = request.androidSelector.toBySelector() ) } else { throw PatrolException("scrollTo(): neither selector nor androidSelector are set") diff --git a/packages/patrol/example/lib/ui/components/scaffold.dart b/packages/patrol/example/lib/ui/components/scaffold.dart index e6f7c20a8..27ca440cb 100644 --- a/packages/patrol/example/lib/ui/components/scaffold.dart +++ b/packages/patrol/example/lib/ui/components/scaffold.dart @@ -20,7 +20,7 @@ class PTScaffold extends StatelessWidget { final top = this.top; return PopScope( - onPopInvoked: (didPop) => Future.value(false), + onPopInvokedWithResult: (didPop, _) => Future.value(false), child: Scaffold( backgroundColor: PTColors.textDark, body: DefaultTextStyle( diff --git a/packages/patrol/example/pubspec.yaml b/packages/patrol/example/pubspec.yaml index 86489f6af..2147e08d4 100644 --- a/packages/patrol/example/pubspec.yaml +++ b/packages/patrol/example/pubspec.yaml @@ -31,7 +31,7 @@ dev_dependencies: flutter_native_splash: ^2.3.6 flutter_test: sdk: flutter - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 patrol: path: .. diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index ed22f7a29..1d9e606af 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: build_runner: ^2.4.6 custom_lint: ^0.6.4 json_serializable: ^6.7.1 - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 flutter: plugin: diff --git a/packages/patrol_cli/pubspec.yaml b/packages/patrol_cli/pubspec.yaml index 80986e266..51c3174ad 100644 --- a/packages/patrol_cli/pubspec.yaml +++ b/packages/patrol_cli/pubspec.yaml @@ -41,7 +41,7 @@ dev_dependencies: build_runner: ^2.4.6 custom_lint: ^0.6.4 fake_async: ^1.3.1 - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 mocktail: ^1.0.1 test: ^1.24.9 diff --git a/packages/patrol_devtools_extension/pubspec.lock b/packages/patrol_devtools_extension/pubspec.lock index a95a27a75..28c500cd5 100644 --- a/packages/patrol_devtools_extension/pubspec.lock +++ b/packages/patrol_devtools_extension/pubspec.lock @@ -327,18 +327,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -351,10 +351,10 @@ packages: dependency: "direct dev" description: name: leancode_lint - sha256: "24c7380d0d46b3927614ca86c82ba8b7373e3906e5227b9aceb748a78fd2c387" + sha256: "6e6500dac54ea3d0ad01f6e851a09b815f3d75f7e87bb9ab589d1fd19dd826f1" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "14.1.0" logging: dependency: transitive description: @@ -375,18 +375,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" package_config: dependency: transitive description: @@ -524,10 +524,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -572,10 +572,10 @@ packages: dependency: "direct main" description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" watcher: dependency: transitive description: @@ -625,5 +625,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=3.4.0-282.1.beta <4.0.0" + dart: ">=3.5.0 <4.0.0" flutter: ">=3.22.0" diff --git a/packages/patrol_devtools_extension/pubspec.yaml b/packages/patrol_devtools_extension/pubspec.yaml index 6fd783eb2..c5a248dee 100644 --- a/packages/patrol_devtools_extension/pubspec.yaml +++ b/packages/patrol_devtools_extension/pubspec.yaml @@ -24,7 +24,7 @@ dev_dependencies: custom_lint: ^0.6.4 flutter_test: sdk: flutter - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 flutter: uses-material-design: true diff --git a/packages/patrol_finders/example/pubspec.yaml b/packages/patrol_finders/example/pubspec.yaml index 1a75683aa..1293481de 100644 --- a/packages/patrol_finders/example/pubspec.yaml +++ b/packages/patrol_finders/example/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 patrol_finders: path: ../ diff --git a/packages/patrol_finders/pubspec.yaml b/packages/patrol_finders/pubspec.yaml index b8db1c77a..5a35fd929 100644 --- a/packages/patrol_finders/pubspec.yaml +++ b/packages/patrol_finders/pubspec.yaml @@ -22,4 +22,4 @@ dependencies: meta: ^1.10.0 dev_dependencies: - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 diff --git a/packages/patrol_gen/pubspec.lock b/packages/patrol_gen/pubspec.lock index 23359ffe5..21ae506a6 100644 --- a/packages/patrol_gen/pubspec.lock +++ b/packages/patrol_gen/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: "direct dev" description: name: leancode_lint - sha256: "24c7380d0d46b3927614ca86c82ba8b7373e3906e5227b9aceb748a78fd2c387" + sha256: "6e6500dac54ea3d0ad01f6e851a09b815f3d75f7e87bb9ab589d1fd19dd826f1" url: "https://pub.dev" source: hosted - version: "12.1.0" + version: "14.1.0" logging: dependency: transitive description: @@ -346,4 +346,4 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" diff --git a/packages/patrol_gen/pubspec.yaml b/packages/patrol_gen/pubspec.yaml index 7ce151190..276cd51d0 100644 --- a/packages/patrol_gen/pubspec.yaml +++ b/packages/patrol_gen/pubspec.yaml @@ -17,4 +17,4 @@ dependencies: dev_dependencies: custom_lint: ^0.6.4 - leancode_lint: ^12.1.0 + leancode_lint: ^14.1.0 From 0b0d58f23d4921d6b1cffac3c3df10ca8a26889a Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:13:14 +0200 Subject: [PATCH 11/18] Downgrade leancode_lint to 12.1.0 --- dev/cli_tests/pubspec.yaml | 2 +- dev/e2e_app/pubspec.yaml | 2 +- packages/adb/pubspec.yaml | 2 +- packages/patrol/example/pubspec.yaml | 2 +- packages/patrol/pubspec.yaml | 2 +- packages/patrol_cli/pubspec.yaml | 2 +- packages/patrol_devtools_extension/pubspec.yaml | 2 +- packages/patrol_finders/example/pubspec.yaml | 2 +- packages/patrol_finders/pubspec.yaml | 2 +- packages/patrol_gen/pubspec.yaml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev/cli_tests/pubspec.yaml b/dev/cli_tests/pubspec.yaml index 65cbe3caf..02053eb26 100644 --- a/dev/cli_tests/pubspec.yaml +++ b/dev/cli_tests/pubspec.yaml @@ -8,4 +8,4 @@ dependencies: path: ^1.8.3 dev_dependencies: - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 diff --git a/dev/e2e_app/pubspec.yaml b/dev/e2e_app/pubspec.yaml index 9a135c41d..eafeadd52 100644 --- a/dev/e2e_app/pubspec.yaml +++ b/dev/e2e_app/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 patrol: path: ../../packages/patrol diff --git a/packages/adb/pubspec.yaml b/packages/adb/pubspec.yaml index 7646c27f1..3c803ccef 100644 --- a/packages/adb/pubspec.yaml +++ b/packages/adb/pubspec.yaml @@ -9,7 +9,7 @@ environment: dev_dependencies: custom_lint: ^0.6.4 - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 mocktail: ^1.0.1 test: ^1.24.9 diff --git a/packages/patrol/example/pubspec.yaml b/packages/patrol/example/pubspec.yaml index 2147e08d4..86489f6af 100644 --- a/packages/patrol/example/pubspec.yaml +++ b/packages/patrol/example/pubspec.yaml @@ -31,7 +31,7 @@ dev_dependencies: flutter_native_splash: ^2.3.6 flutter_test: sdk: flutter - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 patrol: path: .. diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index 1d9e606af..ed22f7a29 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: build_runner: ^2.4.6 custom_lint: ^0.6.4 json_serializable: ^6.7.1 - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 flutter: plugin: diff --git a/packages/patrol_cli/pubspec.yaml b/packages/patrol_cli/pubspec.yaml index 51c3174ad..80986e266 100644 --- a/packages/patrol_cli/pubspec.yaml +++ b/packages/patrol_cli/pubspec.yaml @@ -41,7 +41,7 @@ dev_dependencies: build_runner: ^2.4.6 custom_lint: ^0.6.4 fake_async: ^1.3.1 - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 mocktail: ^1.0.1 test: ^1.24.9 diff --git a/packages/patrol_devtools_extension/pubspec.yaml b/packages/patrol_devtools_extension/pubspec.yaml index c5a248dee..6fd783eb2 100644 --- a/packages/patrol_devtools_extension/pubspec.yaml +++ b/packages/patrol_devtools_extension/pubspec.yaml @@ -24,7 +24,7 @@ dev_dependencies: custom_lint: ^0.6.4 flutter_test: sdk: flutter - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 flutter: uses-material-design: true diff --git a/packages/patrol_finders/example/pubspec.yaml b/packages/patrol_finders/example/pubspec.yaml index 1293481de..1a75683aa 100644 --- a/packages/patrol_finders/example/pubspec.yaml +++ b/packages/patrol_finders/example/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 patrol_finders: path: ../ diff --git a/packages/patrol_finders/pubspec.yaml b/packages/patrol_finders/pubspec.yaml index 5a35fd929..b8db1c77a 100644 --- a/packages/patrol_finders/pubspec.yaml +++ b/packages/patrol_finders/pubspec.yaml @@ -22,4 +22,4 @@ dependencies: meta: ^1.10.0 dev_dependencies: - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 diff --git a/packages/patrol_gen/pubspec.yaml b/packages/patrol_gen/pubspec.yaml index 276cd51d0..7ce151190 100644 --- a/packages/patrol_gen/pubspec.yaml +++ b/packages/patrol_gen/pubspec.yaml @@ -17,4 +17,4 @@ dependencies: dev_dependencies: custom_lint: ^0.6.4 - leancode_lint: ^14.1.0 + leancode_lint: ^12.1.0 From a443ed449d30ae96623f536c5772734df2932bd9 Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:23:49 +0200 Subject: [PATCH 12/18] Bump flutter_local_notifications in e2e_app to 17.2.3 --- dev/e2e_app/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/e2e_app/pubspec.yaml b/dev/e2e_app/pubspec.yaml index eafeadd52..cac8027e2 100644 --- a/dev/e2e_app/pubspec.yaml +++ b/dev/e2e_app/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: cupertino_icons: ^1.0.6 flutter: sdk: flutter - flutter_local_notifications: ^16.1.0 + flutter_local_notifications: ^17.2.3 flutter_timezone: ^1.0.8 geolocator: ^10.1.0 permission_handler: ^10.4.5 From 152463e8e025fc88e95385308463ebc8f576cdef Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:31:24 +0200 Subject: [PATCH 13/18] Bump package/patrol version to 3.12.0 --- packages/patrol/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/patrol/pubspec.yaml b/packages/patrol/pubspec.yaml index ed22f7a29..8b3d38001 100644 --- a/packages/patrol/pubspec.yaml +++ b/packages/patrol/pubspec.yaml @@ -2,7 +2,7 @@ name: patrol description: > Powerful Flutter-native UI testing framework overcoming limitations of existing Flutter testing tools. Ready for action! -version: 3.11.0 +version: 3.12.0 homepage: https://patrol.leancode.co repository: https://github.com/leancodepl/patrol/tree/master/packages/patrol issue_tracker: https://github.com/leancodepl/patrol/issues From e77a2daf60bd2f9284821ed32ad3ea9a387bda97 Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:46:23 +0200 Subject: [PATCH 14/18] Set minSdk to 24 in example apps --- dev/e2e_app/android/app/build.gradle | 2 +- packages/patrol/example/android/app/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/e2e_app/android/app/build.gradle b/dev/e2e_app/android/app/build.gradle index 62dca90d9..7579ca48c 100644 --- a/dev/e2e_app/android/app/build.gradle +++ b/dev/e2e_app/android/app/build.gradle @@ -36,7 +36,7 @@ android { defaultConfig { applicationId "pl.leancode.patrol.e2e_app" - minSdk 21 + minSdk 24 targetSdk 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/packages/patrol/example/android/app/build.gradle b/packages/patrol/example/android/app/build.gradle index f5de74ea7..5469845aa 100644 --- a/packages/patrol/example/android/app/build.gradle +++ b/packages/patrol/example/android/app/build.gradle @@ -58,7 +58,7 @@ android { defaultConfig { applicationId "pl.leancode.patrol.example" - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName From 875fc6ec2d9be3b6358be439d5dc0b451eec1de3 Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 16:46:42 +0200 Subject: [PATCH 15/18] Set version in packages/patrol changelog to 3.12.0 --- packages/patrol/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/patrol/CHANGELOG.md b/packages/patrol/CHANGELOG.md index ea367435d..ad5196064 100644 --- a/packages/patrol/CHANGELOG.md +++ b/packages/patrol/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## 3.12.0 - Add native `scrollTo` method. (#2343) - Updated Flutter version to >=3.24.0 (#2343) From d2a3319517bbe9dd71dff423f1a90bb6eb32cd6a Mon Sep 17 00:00:00 2001 From: piotruela Date: Mon, 30 Sep 2024 17:00:34 +0200 Subject: [PATCH 16/18] Set patrol_cli-prepare.yaml Flutter version to 3.22.x --- .github/workflows/patrol_cli-prepare.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/patrol_cli-prepare.yaml b/.github/workflows/patrol_cli-prepare.yaml index 6a817ea49..8742bd22c 100644 --- a/.github/workflows/patrol_cli-prepare.yaml +++ b/.github/workflows/patrol_cli-prepare.yaml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - flutter-version: ['3.24.x'] + flutter-version: ['3.22.x'] flutter-channel: ['stable'] defaults: From 24e26f7cc196d0bd0e4741227ab0674d835fe8cb Mon Sep 17 00:00:00 2001 From: piotruela Date: Tue, 1 Oct 2024 17:22:21 +0200 Subject: [PATCH 17/18] Move default tap coords for enterText other place --- .../Classes/AutomatorServer/Automator/IOSAutomator.swift | 7 ++++--- .../darwin/Classes/AutomatorServer/AutomatorServer.swift | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift index 18b9ef107..7e1fdc0d7 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift @@ -924,15 +924,16 @@ } // MARK: Private stuff - private func clearAndEnterText(data: String, element: XCUIElement, dx: CGFloat, dy: CGFloat) { + private func clearAndEnterText(data: String, element: XCUIElement, dx: CGFloat?, dy: CGFloat?) { let currentValue = element.value as? String var delete: String = "" if let value = currentValue { delete = String(repeating: XCUIKeyboardKey.delete.rawValue, count: value.count) } - // We need to tap at the end of the field to ensure the cursor is at the end - let coordinate = element.coordinate(withNormalizedOffset: CGVector(dx: dx, dy: dy)) + // By default we tap at the end of the field to ensure the cursor is at the end + let coordinate = element.coordinate( + withNormalizedOffset: CGVector(dx: dx ?? 0.9, dy: dy ?? 0.9)) coordinate.tap() element.typeText(delete + data) diff --git a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift index 9b9b59885..092a48a5c 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift @@ -155,8 +155,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx ?? 0.9, - dy: request.dy ?? 0.9 + dx: request.dx, + dy: request.dy ) } else if let selector = request.selector { try automator.enterText( @@ -165,8 +165,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx ?? 0.9, - dy: request.dy ?? 0.9 + dx: request.dx, + dy: request.dy ) } else if let iosSelector = request.iosSelector { try automator.enterText( From a656ce06117f965b1cdd925595c3b50d0bab67bf Mon Sep 17 00:00:00 2001 From: piotruela Date: Wed, 2 Oct 2024 09:06:14 +0200 Subject: [PATCH 18/18] Fix webview tests on Android --- .../integration_test/external_link_test.dart | 50 ++++++++++++------- .../webview_hackernews_test.dart | 21 +++++--- .../webview_leancode_test.dart | 36 ++++++------- .../webview_stackoverflow_test.dart | 28 +++++++++-- .../pl/leancode/patrol/ContractsExtensions.kt | 4 -- .../AutomatorServer/Automator/Automator.swift | 12 ++--- .../Automator/IOSAutomator.swift | 18 +++---- .../AutomatorServer/AutomatorServer.swift | 4 +- 8 files changed, 102 insertions(+), 71 deletions(-) diff --git a/dev/e2e_app/integration_test/external_link_test.dart b/dev/e2e_app/integration_test/external_link_test.dart index d91c3b37f..a6dafb433 100644 --- a/dev/e2e_app/integration_test/external_link_test.dart +++ b/dev/e2e_app/integration_test/external_link_test.dart @@ -1,44 +1,56 @@ -import 'dart:io'; - import 'common.dart'; void main() { patrol('Open external url', ($) async { await createApp($); - await $.native.openUrl('https://leancode.co'); + await $.native2.openUrl('https://leancode.co'); - try { - await $.native.tap(Selector(text: 'Use without an account')); - } on PatrolActionException catch (_) { - // ignore - } + await $.pump(Duration(seconds: 5)); try { - await $.native.tap(Selector(text: 'Contact us')); + await $.native2.tap( + NativeSelector( + android: AndroidSelector(text: 'Use without an account'), + ), + ); } on PatrolActionException catch (_) { // ignore } try { - await $.native.tap(Selector(text: 'No thanks')); + await $.native2.tap( + NativeSelector( + android: AndroidSelector(text: 'No thanks'), + ios: IOSSelector(label: 'No thanks'), + ), + appId: 'com.apple.mobilesafari', + ); } on PatrolActionException catch (_) { // ignore } try { - await $.native.tap(Selector(text: 'ACCEPT ALL COOKIES')); + await $.native2.tap( + NativeSelector( + android: AndroidSelector( + text: 'ACCEPT ALL COOKIES', + applicationPackage: 'com.android.chrome', + ), + ios: IOSSelector(label: 'ACCEPT ALL COOKIES'), + ), + appId: 'com.apple.mobilesafari', + ); } on PatrolActionException catch (_) { // ignore } - if (Platform.isIOS) { - await $.native.waitUntilVisible( - Selector(text: 'Subscribe'), - appId: 'com.apple.mobilesafari', - ); - } else { - await $.native.waitUntilVisible(Selector(text: 'Subscribe')); - } + await $.native2.waitUntilVisible( + NativeSelector( + android: AndroidSelector(text: 'Contact us'), + ios: IOSSelector(label: 'Contact us'), + ), + appId: 'com.apple.mobilesafari', + ); }); } diff --git a/dev/e2e_app/integration_test/webview_hackernews_test.dart b/dev/e2e_app/integration_test/webview_hackernews_test.dart index 6af4216a8..0b9895058 100644 --- a/dev/e2e_app/integration_test/webview_hackernews_test.dart +++ b/dev/e2e_app/integration_test/webview_hackernews_test.dart @@ -6,7 +6,7 @@ void main() { await $('Open webview (Hacker News)').scrollTo().tap(); - await Future.delayed(const Duration(seconds: 3)); + await $.pump(Duration(seconds: 3)); await $.native.tap(Selector(text: 'login')); await $.native.enterTextByIndex( @@ -26,7 +26,7 @@ void main() { await $('Open webview (Hacker News)').scrollTo().tap(); - await Future.delayed(const Duration(seconds: 3)); + await $.pump(Duration(seconds: 3)); await $.native2.tap( NativeSelector( @@ -34,14 +34,23 @@ void main() { ios: IOSSelector(label: 'login'), ), ); - await $.native2.enterTextByIndex( - 'test@leancode.pl', - index: 0, + await $.native2.enterText( + NativeSelector( + android: AndroidSelector( + className: 'android.widget.EditText', + instance: 0, + ), + ios: IOSSelector(elementType: IOSElementType.textField), + ), + text: 'test@leancode.pl', keyboardBehavior: KeyboardBehavior.showAndDismiss, ); await $.native2.enterText( NativeSelector( - android: AndroidSelector(className: 'android.widget.EditText'), + android: AndroidSelector( + className: 'android.widget.EditText', + instance: 1, + ), ios: IOSSelector(elementType: IOSElementType.secureTextField), ), text: 'ny4ncat', diff --git a/dev/e2e_app/integration_test/webview_leancode_test.dart b/dev/e2e_app/integration_test/webview_leancode_test.dart index 1d8d1abba..1a4806fcf 100644 --- a/dev/e2e_app/integration_test/webview_leancode_test.dart +++ b/dev/e2e_app/integration_test/webview_leancode_test.dart @@ -1,5 +1,3 @@ -import 'dart:io' as io; - import 'common.dart'; void main() { @@ -22,32 +20,28 @@ void main() { } await $.pumpAndSettle(); - if (io.Platform.isIOS) { - await $.native2.scrollTo( - NativeSelector( - ios: IOSSelector(placeholderValue: 'Type your email'), - ), - maxScrolls: 20, - ); - } + final emailInputSelector = NativeSelector( + android: AndroidSelector(className: 'android.widget.EditText'), + ios: IOSSelector(placeholderValue: 'Type your email'), + ); - await $.pump(Duration(seconds: 5)); + await $.native2.scrollTo(emailInputSelector, maxScrolls: 20); + + await $.pump(Duration(seconds: 2)); await $.native2.enterText( - NativeSelector( - android: AndroidSelector(className: 'android.widget.EditText'), - ios: IOSSelector(placeholderValue: 'Type your email'), - ), + emailInputSelector, text: 'test@leancode.pl', - keyboardBehavior: KeyboardBehavior.showAndDismiss, + keyboardBehavior: KeyboardBehavior.alternative, tapLocation: Offset(0.5, 0.5), ); - await $.native2.tap( - NativeSelector( - android: AndroidSelector(text: 'Subscribe'), - ios: IOSSelector(label: 'Subscribe'), - ), + final subscribeButtonSelector = NativeSelector( + android: AndroidSelector(text: 'Subscribe'), + ios: IOSSelector(label: 'Subscribe'), ); + + await $.native2.scrollTo(subscribeButtonSelector); + await $.native2.tap(subscribeButtonSelector); }); } diff --git a/dev/e2e_app/integration_test/webview_stackoverflow_test.dart b/dev/e2e_app/integration_test/webview_stackoverflow_test.dart index d31a2f7ac..252eb78e6 100644 --- a/dev/e2e_app/integration_test/webview_stackoverflow_test.dart +++ b/dev/e2e_app/integration_test/webview_stackoverflow_test.dart @@ -8,6 +8,8 @@ void main() { await $('Open webview (StackOverflow)').scrollTo().tap(); + await $.pump(Duration(seconds: 2)); + try { await $.native.tap(Selector(text: 'Accept all cookies')); } on PatrolActionException catch (_) { @@ -18,14 +20,22 @@ void main() { await $.pump(Duration(seconds: 2)); // bug: using `Email` and `Password` selectors doesn't work (#1554) - await $.native.enterTextByIndex('test@leancode.pl', index: 0); + await $.native.enterTextByIndex( + 'test@leancode.pl', + index: 0, + keyboardBehavior: KeyboardBehavior.alternative, + ); await $.native.swipe( from: Offset(0.5, 0.5), to: Offset(0.5, 0.1), ); - await $.native.enterTextByIndex('ny4ncat', index: 1); + await $.native.enterTextByIndex( + 'ny4ncat', + index: 1, + keyboardBehavior: KeyboardBehavior.alternative, + ); await $.native.tap(Selector(text: 'Log in')); }, ); @@ -37,6 +47,8 @@ void main() { await $('Open webview (StackOverflow)').scrollTo().tap(); + await $.pump(Duration(seconds: 2)); + try { await $.native2.tap( NativeSelector( @@ -57,14 +69,22 @@ void main() { await $.pump(Duration(seconds: 2)); // bug: using `Email` and `Password` selectors doesn't work (#1554) - await $.native2.enterTextByIndex('test@leancode.pl', index: 0); + await $.native2.enterTextByIndex( + 'test@leancode.pl', + index: 0, + keyboardBehavior: KeyboardBehavior.alternative, + ); await $.native.swipe( from: Offset(0.5, 0.5), to: Offset(0.5, 0.1), ); - await $.native2.enterTextByIndex('ny4ncat', index: 1); + await $.native2.enterTextByIndex( + 'ny4ncat', + index: 1, + keyboardBehavior: KeyboardBehavior.alternative, + ); await $.native2.tap( NativeSelector( android: AndroidSelector(text: 'Log in'), diff --git a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt index 2ee234d2a..72a29501a 100644 --- a/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt +++ b/packages/patrol/android/src/main/kotlin/pl/leancode/patrol/ContractsExtensions.kt @@ -252,10 +252,6 @@ fun AndroidSelector.toUiSelector(): UiSelector { } fun AndroidSelector.toBySelector(): BySelector { - if (hasInstance()) { - throw IllegalArgumentException("instance() argument is not supported for BySelector") - } - var selector: BySelector? = null if (hasClassName()) { diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift index 398b84948..c0a804e1c 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/Automator.swift @@ -216,8 +216,8 @@ extension Selector { inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws func enterText( _ data: String, @@ -225,8 +225,8 @@ extension Selector { inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws func enterText( _ data: String, @@ -234,8 +234,8 @@ extension Selector { inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws func swipe(from start: CGVector, to end: CGVector, inApp bundleId: String) throws func scrollTo( diff --git a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift index 7e1fdc0d7..caa210944 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/Automator/IOSAutomator.swift @@ -183,8 +183,8 @@ inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws { var data = data if dismissKeyboard { @@ -238,8 +238,8 @@ inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws { var data = data if dismissKeyboard { @@ -276,8 +276,8 @@ inApp bundleId: String, dismissKeyboard: Bool, withTimeout timeout: TimeInterval?, - dx: CGFloat, - dy: CGFloat + dx: Double?, + dy: Double? ) throws { var data = data if dismissKeyboard { @@ -924,7 +924,7 @@ } // MARK: Private stuff - private func clearAndEnterText(data: String, element: XCUIElement, dx: CGFloat?, dy: CGFloat?) { + private func clearAndEnterText(data: String, element: XCUIElement, dx: Double?, dy: Double?) { let currentValue = element.value as? String var delete: String = "" if let value = currentValue { @@ -949,8 +949,8 @@ } let app = try self.getApp(withBundleId: bundleId) - let startCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.9)) - let endCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)) + let startCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.7)) + let endCoordinate = app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.3)) startCoordinate.press(forDuration: 0.1, thenDragTo: endCoordinate) attempts += 1 // Increment the scroll count after each scroll diff --git a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift index 092a48a5c..ff4cbd5f9 100644 --- a/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift +++ b/packages/patrol/darwin/Classes/AutomatorServer/AutomatorServer.swift @@ -175,8 +175,8 @@ inApp: request.appId, dismissKeyboard: request.keyboardBehavior == .showAndDismiss, withTimeout: request.timeoutMillis.map { TimeInterval($0 / 1000) }, - dx: request.dx ?? 0.9, - dy: request.dy ?? 0.9 + dx: request.dx, + dy: request.dy ) } else { throw PatrolError.internal("enterText(): neither index nor selector are set")