Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/native tap at coordinates #2053

Merged
merged 9 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions dev/e2e_app/integration_test/tap_at_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'dart:io' show Platform;

import 'common.dart';

void main() {
final String appId;
if (Platform.isIOS) {
appId = 'com.apple.Preferences';
} else if (Platform.isAndroid) {
appId = 'com.android.settings';
} else {
throw UnsupportedError('Unsupported platform');
}

patrol('taps at the lower middle of the screen in the Settings app',
($) async {
await createApp($);

await $.native.openApp(appId: appId);
await $.native.tapAt(
Offset(0.5, 0.8),
appId: appId,
);
});
}
50 changes: 26 additions & 24 deletions docs/native/feature-parity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,38 @@ implemented. We hope that it will help you evaluate Patrol.
We strive for high feature parity across iOS and Android, but in some cases it's
impossible to reach 100%. macOs support is still in alpha, so it has no native features yet.


### Table

| **Feature** | **Android** | **iOS** | **macOS (alpha)** |
| --------------------------- | -------------- | ------------- | ----------------- |
| [Press home] | ✅ | ✅ | ❌ |
| [Press back] | ✅ | ❌ (no API) | ❌ |
| [Open any app] | ✅ | ✅ | ✅ |
| [Open notifications] | ✅ | ✅ | ❌ |
| [Tap on notification] | ✅ | ✅ | ❌ |
| [Open quick settings] | ✅ | ✅ | ❌ |
| [Toggle dark mode] | ✅ | ✅ | ❌ |
| [Toggle airplane mode] | ✅ see [#1359] | ✅ | ❌ |
| [Toggle cellular] | ✅ | ✅ | ❌ |
| [Toggle Wi-Fi] | ✅ | ✅ | ❌ |
| [Toggle Bluetooth] | ✅ see [#282] | ✅ | ❌ |
| Toggle location | ✅ see [#283] | ✅ see [#326] | ❌ |
| [Tap] | ✅ | ✅ | ❌ |
| [Double tap] | ✅ | ✅ | ❌ |
| [Enter text] | ✅ | ✅ | ❌ |
| [Swipe] | ✅ | ✅ | ❌ |
| [Handle permission dialogs] | ✅ | ✅ | ❌ |
| Interact with WebView | ⚠️ see [#244] | ✅ | ❌ |
| [Press home] | ✅ | ✅ | ❌ |
| [Press back] | ✅ | ❌ (no API) | ❌ |
| [Open any app] | ✅ | ✅ | ✅ |
| [Open notifications] | ✅ | ✅ | ❌ |
| [Tap on notification] | ✅ | ✅ | ❌ |
| [Open quick settings] | ✅ | ✅ | ❌ |
| [Toggle dark mode] | ✅ | ✅ | ❌ |
| [Toggle airplane mode] | ✅ see [#1359] | ✅ | ❌ |
| [Toggle cellular] | ✅ | ✅ | ❌ |
| [Toggle Wi-Fi] | ✅ | ✅ | ❌ |
| [Toggle Bluetooth] | ✅ see [#282] | ✅ | ❌ |
| Toggle location | ✅ see [#283] | ✅ see [#326] | ❌ |
| [Tap] | ✅ | ✅ | ❌ |
| [Double tap] | ✅ | ✅ | ❌ |
| [Tap at coordinate] | ✅ | ✅ | ❌ |
| [Enter text] | ✅ | ✅ | ❌ |
| [Swipe] | ✅ | ✅ | ❌ |
| [Handle permission dialogs] | ✅ | ✅ | ❌ |
| Interact with WebView | ⚠️ see [#244] | ✅ | ❌ |

### Platfom support

Patrol works on:
- Android 5.0 (API 21) and newer,
- iOS 11 and newer,
- macOS 10.14 and newer.


- Android 5.0 (API 21) and newer,
- iOS 11 and newer,
- macOS 10.14 and newer.

On mobile platforms it works on both physical and virtual devices.

[#244]: https://github.com/leancodepl/patrol/issues/244
Expand All @@ -61,6 +62,7 @@ On mobile platforms it works on both physical and virtual devices.
[toggle bluetooth]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/enableBluetooth.html
[tap]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/tap.html
[double tap]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/doubleTap.html
[tap at coordinate]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/tapAt.html
[enter text]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/enterText.html
[enter text]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/swipe.html
[swipe]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/swipe.html
[handle permission dialogs]: https://pub.dev/documentation/patrol/latest/patrol/NativeAutomator/grantPermissionWhenInUse.html
1 change: 1 addition & 0 deletions packages/patrol/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Unreleased

- Add optional timeout parameter to native methods (#2042).
- Add `$.native.tapAt()` (#2053)

## 3.4.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,31 @@ class Automator private constructor() {
delay()
}

fun tapAt(x: Float, y: Float) {
Logger.d("tapAt(x: $x, y: $y)")

if (x !in 0f..1f) {
throw IllegalArgumentException("x represents a percentage and must be between 0 and 1")
}

if (y !in 0f..1f) {
throw IllegalArgumentException("y represents a percentage and must be between 0 and 1")
}

val displayX = (uiDevice.displayWidth * x).roundToInt()
val displayY = (uiDevice.displayHeight * y).roundToInt()

Logger.d("Clicking at display location (pixels) [$displayX, $displayY]")

val successful = uiDevice.click(displayX, displayY)

if (!successful) {
throw IllegalArgumentException("Clicking at location [$displayX, $displayY] failed")
}

delay()
}

fun enterText(text: String, index: Int, keyboardBehavior: KeyboardBehavior, timeout: Long? = null) {
Logger.d("enterText(text: $text, index: $index)")

Expand Down Expand Up @@ -263,6 +288,7 @@ class Automator private constructor() {
val eY = (uiDevice.displayHeight * endY).roundToInt()

val successful = uiDevice.swipe(sX, sY, eX, eY, steps)

if (!successful) {
throw IllegalArgumentException("Swipe failed")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package pl.leancode.patrol

import pl.leancode.patrol.contracts.Contracts
import pl.leancode.patrol.contracts.Contracts.ConfigureRequest
import pl.leancode.patrol.contracts.Contracts.DarkModeRequest
import pl.leancode.patrol.contracts.Contracts.EnterTextRequest
Expand Down Expand Up @@ -144,6 +145,13 @@ class AutomatorServer(private val automation: Automator) : NativeAutomatorServer
)
}

override fun tapAt(request: Contracts.TapAtRequest) {
automation.tapAt(
x = request.x.toFloat(),
y = request.y.toFloat()
)
}

override fun enterText(request: EnterTextRequest) {
if (request.index != null) {
automation.enterText(
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
inApp bundleId: String,
withTimeout timeout: TimeInterval?
) throws
func tapAt(coordinate vector: CGVector, inApp bundleId: String) throws
func enterText(
_ data: String,
byText text: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@
}
}

func tapAt(coordinate vector: CGVector, inApp bundleId: String) throws {
try runAction("tapping at coordinate \(vector) in app \(bundleId)") {
let app = try self.getApp(withBundleId: bundleId)

let coordinate = app.coordinate(withNormalizedOffset: vector)

coordinate.tap()
}
}

func enterText(
_ data: String,
byText text: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
}
}

func tapAt(coordinate vector: CGVector, inApp bundleId: String) throws {
try runAction("tapAt") {
throw PatrolError.methodNotImplemented("tapAt")
}
}

func enterText(
_ data: String,
byText text: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@
}
}

func tapAt(request: TapAtRequest) throws {
return try runCatching {
try automator.tapAt(
coordinate: CGVector(dx: request.x, dy: request.y),
inApp: request.appId
)
}
}

func enterText(request: EnterTextRequest) throws {
return try runCatching {
if let index = request.index {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ struct TapRequest: Codable {
var timeoutMillis: Int?
}

struct TapAtRequest: Codable {
var x: Double
var y: Double
var appId: String
}

struct EnterTextRequest: Codable {
var data: String
var appId: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ protocol NativeAutomatorServer {
func getNativeViews(request: GetNativeViewsRequest) throws -> GetNativeViewsResponse
func tap(request: TapRequest) throws
func doubleTap(request: TapRequest) throws
func tapAt(request: TapAtRequest) throws
func enterText(request: EnterTextRequest) throws
func swipe(request: SwipeRequest) throws
func waitUntilVisible(request: WaitUntilVisibleRequest) throws
Expand Down Expand Up @@ -113,6 +114,12 @@ extension NativeAutomatorServer {
return HTTPResponse(.ok)
}

private func tapAtHandler(request: HTTPRequest) throws -> HTTPResponse {
let requestArg = try JSONDecoder().decode(TapAtRequest.self, from: request.body)
try tapAt(request: requestArg)
return HTTPResponse(.ok)
}

private func enterTextHandler(request: HTTPRequest) throws -> HTTPResponse {
let requestArg = try JSONDecoder().decode(EnterTextRequest.self, from: request.body)
try enterText(request: requestArg)
Expand Down Expand Up @@ -303,6 +310,11 @@ extension NativeAutomatorServer {
request: request,
handler: doubleTapHandler)
}
server.route(.POST, "tapAt") {
request in handleRequest(
request: request,
handler: tapAtHandler)
}
server.route(.POST, "enterText") {
request in handleRequest(
request: request,
Expand Down
10 changes: 5 additions & 5 deletions packages/patrol/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ PODS:
- Firebase/Messaging (10.18.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 10.18.0)
- firebase_auth (4.15.3):
- firebase_auth (4.16.0):
- Firebase/Auth (= 10.18.0)
- firebase_core
- Flutter
- firebase_core (2.24.2):
- Firebase/CoreOnly (= 10.18.0)
- Flutter
- firebase_messaging (14.7.9):
- firebase_messaging (14.7.10):
- Firebase/Messaging (= 10.18.0)
- firebase_core
- Flutter
Expand Down Expand Up @@ -170,9 +170,9 @@ SPEC CHECKSUMS:
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
Firebase: 414ad272f8d02dfbf12662a9d43f4bba9bec2a06
firebase_auth: df44e14f8a93e8a9869d91695bd3f8e53d2c9f5a
firebase_auth: 8e9ec02991ca4659111cc671c84d0c010b6bfb26
firebase_core: 0af4a2b24f62071f9bf283691c0ee41556dcb3f5
firebase_messaging: 875385354f623750aa03204a028d640108bc3412
firebase_messaging: 90e8a6db84b6e1e876cebce4f30f01dc495e7014
FirebaseAppCheckInterop: 3cd914842ba46f4304050874cd284de82f154ffd
FirebaseAuth: 12314b438fa76048540c8fb86d6cfc9e08595176
FirebaseCore: 2322423314d92f946219c8791674d2f3345b598f
Expand All @@ -198,4 +198,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: a2f999e8fe2642046eaa22133617aca7cd25a681

COCOAPODS: 1.14.3
COCOAPODS: 1.10.1
25 changes: 25 additions & 0 deletions packages/patrol/lib/src/native/contracts/contracts.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions packages/patrol/lib/src/native/contracts/contracts.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading