From 3d9da73eb9a100ae40d97227a7b51f1dd4f6aec9 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Thu, 5 Mar 2020 01:30:00 -0500 Subject: [PATCH 1/9] feat: separate README file for advanced By usage --- .../appium/java_client/pagefactory/README.md | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/main/java/io/appium/java_client/pagefactory/README.md diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md new file mode 100644 index 000000000..6ef440bfe --- /dev/null +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -0,0 +1,69 @@ +# Standard Selectors + +## AndroidFindBy / iOSXCUITFindBy / WindowsFindBy + +# Advanced Selectors + +## 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. + +### 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: + + // Selector for image elements + @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") + + // Selector for every cell with the name 'Foo' + @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == "Foo"`]") + // Selector for every cell with a name that starts with 'Foo' + @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH "Foo"`]") + + // 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 = "**/*") + +## 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: + + // Create a selector that looks for the text "Hello World": + @AndroidFindBy(uiAutomator = "new UiSelector().text(\"Hello World\")") + + // 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: + + // TODO: Provide samples From 60247c942d99bb4f18d7d3892238175618484a14 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Thu, 5 Mar 2020 01:42:04 -0500 Subject: [PATCH 2/9] fix: update some Android/iOS external URLs in javadoc --- src/main/java/io/appium/java_client/MobileBy.java | 5 ++--- .../java/io/appium/java_client/pagefactory/AndroidBy.java | 5 ++--- .../io/appium/java_client/pagefactory/AndroidFindBy.java | 5 ++--- .../java/io/appium/java_client/pagefactory/iOSXCUITBy.java | 2 +- .../io/appium/java_client/pagefactory/iOSXCUITFindBy.java | 2 +- 5 files changed, 8 insertions(+), 11 deletions(-) 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 From 55899293469a4541f92344a98ac1d0a7a7738a57 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Mon, 16 Mar 2020 02:42:47 -0400 Subject: [PATCH 3/9] docs: modify advanced By README to use code blocks As a nice bonus, this also enables syntax highlighting in compatible clients, such as the GitHub website. --- .../appium/java_client/pagefactory/README.md | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index 6ef440bfe..c62396f22 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -16,18 +16,20 @@ to learn more about the general concept. ### Sample usage: - // Selector for image elements - @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") +```java +// Selector for image elements +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") - // Selector for every cell with the name 'Foo' - @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == "Foo"`]") - // Selector for every cell with a name that starts with 'Foo' - @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH "Foo"`]") +// Selector for every cell with the name 'Foo' +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name == \"Foo\"`]") +// Selector for every cell with a name that starts with 'Foo' +@iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeCell[`name BEGINSWITH \"Foo\"`]") - // 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 = "**/*") +// 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 = "**/*") +``` ## Android's uiAutomator String @@ -48,22 +50,26 @@ and Here are some ways you could configure a UiSelector in your project: - // Create a selector that looks for the text "Hello World": - @AndroidFindBy(uiAutomator = "new UiSelector().text(\"Hello World\")") +```java +// Create a selector that looks for the text "Hello World": +@AndroidFindBy(uiAutomator = "new UiSelector().text(\"Hello World\")") - // 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 + "\")") +// 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\")") +// 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)") +// 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: - // TODO: Provide samples +```java +// TODO: Provide samples +``` From 26b3ddcdb498b7c2ac14487b6d7ba5c8eb72cfa3 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Tue, 17 Mar 2020 01:08:41 -0400 Subject: [PATCH 4/9] docs: describe predicate etiquette in the advanced By README --- .../appium/java_client/pagefactory/README.md | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index c62396f22..2a5e725f0 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -4,10 +4,49 @@ # 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. +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`] +``` + +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'll [want 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 @@ -20,11 +59,19 @@ to learn more about the general concept. // Selector for image elements @iOSXCUITFindBy(iOSClassChain = "**/XCUIElementTypeImage") -// Selector for every cell with the name 'Foo' +// 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' + +// 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) From 26719eed2b4218c28e1fa5d2cd05bfb675739a61 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Tue, 17 Mar 2020 01:26:42 -0400 Subject: [PATCH 5/9] docs: add a basic UiScrollable example to the advanced By README --- .../io/appium/java_client/pagefactory/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index 2a5e725f0..3b221d466 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -101,6 +101,9 @@ Here are some ways you could configure a UiSelector in your project: // 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 + "\")") @@ -118,5 +121,13 @@ private static final String looksLikeAPage = "page_number_\d*"; ..and here are some that create UiScrollable objects: ```java -// TODO: Provide samples +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 + ")") + ``` From e0dfbd87b6fa5a6787ff27f97768bbb8e46a7a23 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Fri, 20 Mar 2020 19:01:58 -0400 Subject: [PATCH 6/9] docs: describe descendent-search syntax in advanced By README --- .../io/appium/java_client/pagefactory/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index 3b221d466..175dc866d 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -32,6 +32,21 @@ There's a special array-style syntax for defining predicates in a Class Chain: [`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 +descendents 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'll [want to be careful](https://stackoverflow.com/q/14116217). From b660c2807e07a2a6fe525dc916a901e04bfb1982 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Fri, 20 Mar 2020 20:10:15 -0400 Subject: [PATCH 7/9] docs: describe index behavior in the advanced By README --- .../java/io/appium/java_client/pagefactory/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index 175dc866d..62b70fff4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -74,6 +74,15 @@ to learn more about the general concept. // 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) @@ -91,6 +100,9 @@ to learn more about the general concept. @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 From 67ba60b454268d75ed10edc07abcb5cb844bfd75 Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Mon, 23 Mar 2020 21:16:25 -0400 Subject: [PATCH 8/9] docs: correct some phrasing in advanced By README --- src/main/java/io/appium/java_client/pagefactory/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/src/main/java/io/appium/java_client/pagefactory/README.md index 62b70fff4..4226ed9f6 100644 --- a/src/main/java/io/appium/java_client/pagefactory/README.md +++ b/src/main/java/io/appium/java_client/pagefactory/README.md @@ -34,9 +34,9 @@ There's a special array-style syntax for defining predicates in a Class Chain: #### 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 -descendents of each element. +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' @@ -49,7 +49,7 @@ XCUIElementTypeCell[$label == 'here'$] 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'll [want to be careful](https://stackoverflow.com/q/14116217). +though, you [need to be careful](https://stackoverflow.com/q/14116217). ```c // Make sure to escape each quote mark that matches your delimiter From 0d3ff4aab392c1afa69518d0b079a233ddb6738a Mon Sep 17 00:00:00 2001 From: Philip Cohn-Cort Date: Sat, 28 Mar 2020 21:24:13 -0400 Subject: [PATCH 9/9] docs: move advanced By README to docs/ folder --- .../java_client/pagefactory/README.md => docs/Advanced-By.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/java/io/appium/java_client/pagefactory/README.md => docs/Advanced-By.md (100%) diff --git a/src/main/java/io/appium/java_client/pagefactory/README.md b/docs/Advanced-By.md similarity index 100% rename from src/main/java/io/appium/java_client/pagefactory/README.md rename to docs/Advanced-By.md