Skip to content

Commit

Permalink
Make Selector.instance work on iOS (#1569)
Browse files Browse the repository at this point in the history
* make Selector.instance work with `tap()`

* make `Selector.instance` work for `enterText()`

* add 1 sec wait after entering text
  • Loading branch information
bartekpacia committed Jul 21, 2023
1 parent 658cd5a commit 22a0045
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,9 @@ void main() {

await $.pump(Duration(seconds: 2));

// bug: using `Email` and `Password` selectors doesn't work
await $.native.enterTextByIndex(
'test@leancode.pl',
index: 0,
keyboardBehavior: KeyboardBehavior.showAndDismiss,
);
await $.native.enterTextByIndex(
'ny4ncat',
index: 1,
keyboardBehavior: KeyboardBehavior.showAndDismiss,
);
// bug: using `Email` and `Password` selectors doesn't work (#1554)
await $.native.enterTextByIndex('test@leancode.pl', index: 0);
await $.native.enterTextByIndex('ny4ncat', index: 1);
await $.native.tap(Selector(text: 'Log in'));
},
);
Expand Down
96 changes: 70 additions & 26 deletions packages/patrol/ios/Classes/AutomatorServer/Automator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,28 @@

// MARK: General UI interaction

func tap(onText text: String, inApp bundleId: String) async throws {
try await runAction("tapping on view with text \(format: text) in app \(bundleId)") {
func tap(onText text: String, inApp bundleId: String, atIndex index: Int) async throws {
let view = "view with text \(format: text) at index \(index) in app \(bundleId)"

try await runAction("tapping on \(view)") {
let app = try self.getApp(withBundleId: bundleId)
let element = app.descendants(matching: .any)[text]

Logger.shared.i("waiting for existence of view with text \(format: text)")
let exists = element.waitForExistence(timeout: self.timeout)
guard exists else {
throw PatrolError.viewNotExists(
"view with text \(format: text) in app \(format: bundleId)")
// The below selector is an equivalent of `app.descendants(matching: .any)[text]`
// TODO: We should consider more view properties. See #1554
let format = """
label == %@ OR \
title == %@ OR \
identifier == %@
"""
let predicate = NSPredicate(format: format, text, text, text)
let query = app.descendants(matching: .any).matching(predicate)

Logger.shared.i("waiting for existence of \(view)")
guard let element = self.waitFor(query: query, index: index, timeout: self.timeout) else {
throw PatrolError.viewNotExists(view)
}
Logger.shared.i("found view with text \(format: text), will tap on it")

element.firstMatch.forceTap()
element.forceTap()
}
}

Expand All @@ -88,26 +96,57 @@
func enterText(
_ data: String,
byText text: String,
atIndex index: Int,
inApp bundleId: String
) async throws {
let view = "text field with text \(format: text) at index \(index) in app \(bundleId)"
var data = "\(data)\n"
try await runAction(
"entering text \(format: data) into text field with text \(text) in app \(bundleId)"
) {

try await runAction("entering text \(format: data) into \(view)") {
let app = try self.getApp(withBundleId: bundleId)

// elementType must be specified as integer
// See:
// * https://developer.apple.com/documentation/xctest/xcuielementtype/xcuielementtypetextfield
// * https://developer.apple.com/documentation/xctest/xcuielementtype/xcuielementtypesecuretextfield
// The below selector is an equivalent of `app.descendants(matching: .any)[text]`
// TODO: We should consider more view properties. See #1554
let format = """
label == %@ OR \
title == %@ OR \
identifier == %@ OR \
value == %@ OR \
placeholderValue == %@
"""
let contentPredicate = NSPredicate(format: format, text, text, text, text, text)
let textFieldPredicate = NSPredicate(format: "elementType == 49")
let secureTextFieldPredicate = NSPredicate(format: "elementType == 50")

let finalPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
contentPredicate,
NSCompoundPredicate(orPredicateWithSubpredicates: [
textFieldPredicate, secureTextFieldPredicate,
]
),
])

let query = app.descendants(matching: .any).matching(finalPredicate)
guard
let element = self.waitForAnyElement(
elements: [app.textFields[text], app.secureTextFields[text]],
let element = self.waitFor(
query: query,
index: index,
timeout: self.timeout
)
else {
throw PatrolError.viewNotExists(
"text field with text \(format: text) in app \(format: bundleId)")
throw PatrolError.viewNotExists(view)
}

element.firstMatch.typeText(data)
element.forceTap()
element.typeText(data)
}

// Prevent keyboard dismissal from happening too fast
try await Task.sleep(nanoseconds: UInt64(1 * Double(NSEC_PER_SEC)))
}

func enterText(
Expand All @@ -134,7 +173,7 @@
guard
let element = self.waitFor(
query: textFieldsQuery,
byIndex: index,
index: index,
timeout: self.timeout
)
else {
Expand All @@ -144,6 +183,9 @@
element.forceTap()
element.typeText(data)
}

// Prevent keyboard dismissal from happening too fast
try await Task.sleep(nanoseconds: UInt64(1 * Double(NSEC_PER_SEC)))
}

func waitUntilVisible(onText text: String, inApp bundleId: String) async throws {
Expand Down Expand Up @@ -325,13 +367,16 @@
try await runAction("getting native views matching \(text)") {
let app = try self.getApp(withBundleId: bundleId)

// TODO: We should also consider title, identifier, etc. See #1554
// The below selector is an equivalent of `app.descendants(matching: .any)[text]`
// TODO: We should consider more view properties. See #1554
let format = """
label == %@
label == %@ OR \
title == %@ OR \
identifier == %@
"""

let predicate = NSPredicate(format: format, text)
let elements = app.descendants(matching: .any).matching(predicate).allElementsBoundByIndex
let predicate = NSPredicate(format: format, text, text, text)
let query = app.descendants(matching: .any).matching(predicate)
let elements = query.allElementsBoundByIndex

let views = elements.map { xcuielement in
return Patrol_NativeView.fromXCUIElement(xcuielement, bundleId)
Expand Down Expand Up @@ -630,8 +675,7 @@
}

@discardableResult
func waitFor(query: XCUIElementQuery, byIndex index: Int, timeout: TimeInterval) -> XCUIElement?
{
func waitFor(query: XCUIElementQuery, index: Int, timeout: TimeInterval) -> XCUIElement? {
var foundElement: XCUIElement?
let startTime = Date()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
return try await runCatching {
try await automator.tap(
onText: request.selector.text,
inApp: request.appID
inApp: request.appID,
atIndex: Int(request.selector.instance)
)
return DefaultResponse()
}
Expand Down Expand Up @@ -150,6 +151,7 @@
try await automator.enterText(
request.data,
byText: selector.text,
atIndex: Int(selector.instance),
inApp: request.appID
)
default:
Expand Down

0 comments on commit 22a0045

Please sign in to comment.