diff --git a/docs/Advanced-By.md b/docs/Advanced-By.md new file mode 100644 index 000000000..4226ed9f6 --- /dev/null +++ b/docs/Advanced-By.md @@ -0,0 +1,160 @@ +# Standard Selectors + +## AndroidFindBy / iOSXCUITFindBy / WindowsFindBy + +# Advanced Selectors + +## iOS's String Predicates + +You can specify a [predicate](https://developer.apple.com/documentation/foundation/nspredicate) +in your Class Chain to limit the number of matched items. There's +[a detailed guide to that](https://appium.io/docs/en/writing-running-appium/ios/ios-predicate/index.html) +on the Appium Docs website with some Appium-specific considerations. + +## iOS's Class Chain Queries + +Our XCUiTest integration has full support for the 'Class Chain' concept. This +can do much of what XPath does...but faster. Note that many Class Chains leverage +String Predicates too. + +### String Predicates in Class Chains + +There's a special array-style syntax for defining predicates in a Class Chain: + +``` +// Start with [ +// Followed by ` +// Followed by some text +// Followed by ` +// End with ] +[`label != 'a'`] +[`isWDVisible == 1`] +[`value < 0`] +``` + +#### Searching Descendents + +If you replace the backticks (`` ` ``) around a predicate with dollar signs (`$`), +then the Class Chain will match against the children, grandchildren, and other +descendants of each element. + +``` +// Find a cell element with the label 'here' +XCUIElementTypeCell[`label == 'here'`] +// Find a cell element which contains SOMETHING ELSE that has the label 'here' +XCUIElementTypeCell[$label == 'here'$] +``` + +#### Handling Quote Marks + +Most of the time, you can treat pairs of single quotes or double quotes +interchangably. If you're searching with a string that contains quote marks, +though, you [need to be careful](https://stackoverflow.com/q/14116217). + +```c +// Make sure to escape each quote mark that matches your delimiter +"text with \"some\" 'quote' marks" +// To NSPredicate, the line above and the line below are equivalent +'text with "some" \'quote\' marks' +``` +```java +// When defining a iOSXCUITFindBy annotation, you'll be constrained by the +// Java string-quoting rules too. +@iOSXCUITFindBy(iOSClassChain = "**/SomeElement[`'text with \"some\" \\\'quote\\\' marks'`]") +``` + +### External References + +Refer to [the official WebDriverAgent query docs](https://github.com/facebookarchive/WebDriverAgent/wiki/Class-Chain-Queries-Construction-Rules) +to learn more about the general concept. + +### Sample usage: + +```java +// Selector for image elements +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") + +// Selector for the first image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[1]") +// Selector for the second image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[2]") +// Selector for the last image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[-1]") +// Selector for the penultimate image element on screen +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage[-2]") + +// Selector for every cell with the name 'Foo' (single quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == 'Foo'`]") +// Selector for every cell with the name "Foo" (double quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == \"Foo\"`]") + +// Selector for every cell with a name that starts with 'Foo' (single quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH 'Foo'`]") +// Selector for every cell with a name that starts with "Foo" (double quote style) +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH \"Foo\"`]") + +// Selector for every cell with a name that starts with "it's not" +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH \"it's not\"`]") + +// Selector that'll match every top-level element on screen +@iOSXCUITFindBy(iOSClassChain = "*") +// Selector that'll match every leaf element on screen (watch out: this can be SLOW) +@iOSXCUITFindBy(iOSClassChain = "**/*") + +// You can place an index after a predicate: the following finds the last image element with name 'Foo' +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == 'Foo'`][-1]") +``` + +## Android's uiAutomator String + +Available when using [AndroidBy](AndroidBy) and [AndroidFindBy](AndroidFindBy) with +[appium-uiautomator2-server](https://github.com/appium/appium-uiautomator2-server). This +string will be used by the server to construct a UiSelector or UiScrollable object. + +### External References + +For an overview of what the backend is capable of, please check out the + +* [Main UI Automator Guide](https://developer.android.com/training/testing/ui-automator) +* [UiScrollable API docs](https://developer.android.com/reference/androidx/test/uiautomator/UiScrollable) +and +* [UiSelector API docs](https://developer.android.com/reference/androidx/test/uiautomator/UiSelector) + +### Sample Strings + +Here are some ways you could configure a UiSelector in your project: + +```java +// Create a selector that looks for the text "Hello World": +@AndroidFindBy(uiAutomator = "new UiSelector().text(\"Hello World\")") + +// Create a selector that tries to find an ImageView: +@AndroidFindBy(uiAutomator = "new UiSelector().className(\"android.widget.ImageView\")") + +// Create a selector that matches resource ids against a regular expression: +private static final String looksLikeAPage = "page_number_\d*"; +@AndroidFindBy(uiAutomator = "new UiSelector().resourceIdMatches(\"" + looksLikeAPage + "\")") + +// The agent also supports some abbreviated forms - all 3 of the below +// strings are equivalent. +@AndroidFindBy(uiAutomator = "new UiSelector().className(\"android.widget.EditText\")") +@AndroidFindBy(uiAutomator = "UiSelector().className(\"android.widget.EditText\")") +@AndroidFindBy(uiAutomator = ".className(\"android.widget.EditText\")") + +// You can connect up conditions to search for multiple things at once +@AndroidFindBy(uiAutomator = ".resourceId(\"android:id/list\").classNameMatches(\"\.*RecyclerView\").index(3)") +``` + +..and here are some that create UiScrollable objects: + +```java +private static final String ourImageSelector = ".className(\"android.widget.ImageView\")"; +private static final String ourListSelector = ".className(\"android.widget.ListView\")"; + +// Create a scrollable associated with a list (by itself, this doesn't do anything useful...) +@AndroidFindBy(uiAutomator = "new UiScrollable(" + ourListSelector + ")") + +// Create a scrollable that scrolls forward along a list until it finds an ImageView: +@AndroidFindBy(uiAutomator = "new UiScrollable(" + ourListSelector + ").scrollIntoView(" + ourImageSelector + ")") + +``` diff --git a/src/main/java/io/appium/java_client/MobileBy.java b/src/main/java/io/appium/java_client/MobileBy.java index c76542da9..b8fd3d5db 100644 --- a/src/main/java/io/appium/java_client/MobileBy.java +++ b/src/main/java/io/appium/java_client/MobileBy.java @@ -62,8 +62,7 @@ protected MobileBy(MobileSelector selector, String locatorString) { } /** - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * Refer to https://developer.android.com/training/testing/ui-automator * @param uiautomatorText is Android UIAutomator string * @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidUIAutomator} */ @@ -87,7 +86,7 @@ public static By AccessibilityId(final String accessibilityId) { /** * This locator strategy is available in XCUITest Driver mode. * @param iOSClassChainString is a valid class chain locator string. - * See + * See * the documentation for more details * @return an instance of {@link io.appium.java_client.MobileBy.ByIosClassChain} */ diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java index 298956fd7..b40acdd07 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidBy.java @@ -21,9 +21,8 @@ */ public @interface AndroidBy { /** - * It is an Android UIAutomator string. - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * A String that can build an Android UiSelector or UiScrollable object. + * Refer to https://developer.android.com/training/testing/ui-automator * * @return an Android UIAutomator string */ diff --git a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java index d789f20ec..25b37c0b6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/AndroidFindBy.java @@ -36,9 +36,8 @@ @Repeatable(AndroidFindBySet.class) public @interface AndroidFindBy { /** - * It is an Android UIAutomator string. - * Read http://developer.android.com/intl/ru/tools/testing-support-library/ - * index.html#uia-apis + * A String that can build an Android UiSelector or UiScrollable object. + * Refer to https://developer.android.com/training/testing/ui-automator * * @return an Android UIAutomator string */ diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java index 02eb4da5f..c59a1559d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITBy.java @@ -24,7 +24,7 @@ /** * The Class Chain locator is similar to xpath, but it's faster and can only * search direct children elements. See the - * + * * documentation for more details. * * @return iOS class chain diff --git a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java index cee419e39..5194e4094 100644 --- a/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java +++ b/src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java @@ -31,7 +31,7 @@ /** * The Class Chain locator is similar to xpath, but it's faster and can only * search direct children elements. See the - * + * * documentation for more details. * * @return iOS class chain