Skip to content

Commit ff26070

Browse files
Mykola MokhnachSrinivasanTarget
Mykola Mokhnach
authored andcommitted
Add class chain locator type for iOS XCUITest-based driver (#599)
* Add class chain locator type for iOS XCUITest-based driver * Add several functional tests for class chain locator type
1 parent 800e03a commit ff26070

File tree

8 files changed

+144
-2
lines changed

8 files changed

+144
-2
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* See the NOTICE file distributed with this work for additional
5+
* information regarding copyright ownership.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.appium.java_client;
18+
19+
import org.openqa.selenium.WebElement;
20+
21+
import java.util.List;
22+
23+
public interface FindsByIosClassChain<T extends WebElement> extends FindsByFluentSelector<T> {
24+
25+
default T findElementByIosClassChain(String using) {
26+
return findElement(MobileSelector.IOS_CLASS_CHAIN.toString(), using);
27+
}
28+
29+
default List<T> findElementsByIosClassChain(String using) {
30+
return findElements(MobileSelector.IOS_CLASS_CHAIN.toString(), using);
31+
}
32+
}

src/main/java/io/appium/java_client/MobileBy.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ public static By AccessibilityId(final String accessibilityId) {
9797
return new ByAccessibilityId(accessibilityId);
9898
}
9999

100+
/**
101+
* This locator strategy is available in XCUITest Driver mode
102+
* @param iOSClassChainString is a valid class chain locator string.
103+
* See <a href="https://github.com/facebook/WebDriverAgent/wiki/Queries">
104+
* the documentation</a> for more details
105+
* @return an instance of {@link io.appium.java_client.MobileBy.ByIosClassChain}
106+
*/
107+
public static By iOSClassChain(final String iOSClassChainString) {
108+
return new ByIosClassChain(iOSClassChainString);
109+
}
110+
100111
/**
101112
* This locator strategy is available in XCUITest Driver mode
102113
* @param iOSNsPredicateString is an an iOS NsPredicate String
@@ -290,6 +301,62 @@ public List<WebElement> findElements(SearchContext context) throws WebDriverExce
290301
}
291302
}
292303

304+
public static class ByIosClassChain extends MobileBy implements Serializable {
305+
306+
protected ByIosClassChain(String locatorString) {
307+
super(MobileSelector.IOS_CLASS_CHAIN, locatorString);
308+
}
309+
310+
/**
311+
* @throws WebDriverException when current session doesn't support the given selector or when
312+
* value of the selector is not consistent.
313+
* @throws IllegalArgumentException when it is impossible to find something on the given
314+
* {@link SearchContext} instance
315+
*/
316+
@SuppressWarnings("unchecked")
317+
@Override public List<WebElement> findElements(SearchContext context) {
318+
Class<?> contextClass = context.getClass();
319+
320+
if (FindsByIosClassChain.class.isAssignableFrom(contextClass)) {
321+
return FindsByIosClassChain.class.cast(context)
322+
.findElementsByIosClassChain(getLocatorString());
323+
}
324+
325+
if (FindsByFluentSelector.class.isAssignableFrom(context.getClass())) {
326+
return super.findElements(context);
327+
}
328+
329+
throw formIllegalArgumentException(contextClass, FindsByIosClassChain.class,
330+
FindsByFluentSelector.class);
331+
}
332+
333+
/**
334+
* @throws WebDriverException when current session doesn't support the given selector or when
335+
* value of the selector is not consistent.
336+
* @throws IllegalArgumentException when it is impossible to find something on the given
337+
* {@link SearchContext} instance
338+
*/
339+
@Override public WebElement findElement(SearchContext context) {
340+
Class<?> contextClass = context.getClass();
341+
342+
if (FindsByIosClassChain.class.isAssignableFrom(contextClass)) {
343+
return FindsByIosClassChain.class.cast(context)
344+
.findElementByIosClassChain(getLocatorString());
345+
}
346+
347+
if (FindsByFluentSelector.class.isAssignableFrom(context.getClass())) {
348+
return super.findElement(context);
349+
}
350+
351+
throw formIllegalArgumentException(contextClass, FindsByIosClassChain.class,
352+
FindsByFluentSelector.class);
353+
}
354+
355+
@Override public String toString() {
356+
return "By.IosClassChain: " + getLocatorString();
357+
}
358+
}
359+
293360
public static class ByIosNsPredicate extends MobileBy implements Serializable {
294361

295362
protected ByIosNsPredicate(String locatorString) {

src/main/java/io/appium/java_client/MobileSelector.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public enum MobileSelector {
2121
ANDROID_UI_AUTOMATOR("-android uiautomator"),
2222
IOS_UI_AUTOMATION("-ios uiautomation"),
2323
IOS_PREDICATE_STRING("-ios predicate string"),
24+
IOS_CLASS_CHAIN("-ios class chain"),
2425
WINDOWS_UI_AUTOMATION("-windows uiautomation");
2526

2627
private final String selector;

src/main/java/io/appium/java_client/ios/IOSDriver.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static io.appium.java_client.MobileCommand.prepareArguments;
2121

2222
import io.appium.java_client.AppiumDriver;
23+
import io.appium.java_client.FindsByIosClassChain;
2324
import io.appium.java_client.FindsByIosNSPredicate;
2425
import io.appium.java_client.FindsByIosUIAutomation;
2526
import io.appium.java_client.HidesKeyboardWithKeyName;
@@ -51,7 +52,8 @@
5152
public class IOSDriver<T extends WebElement>
5253
extends AppiumDriver<T>
5354
implements HidesKeyboardWithKeyName, ShakesDevice,
54-
FindsByIosUIAutomation<T>, LocksIOSDevice, PerformsTouchID, FindsByIosNSPredicate<T> {
55+
FindsByIosUIAutomation<T>, LocksIOSDevice, PerformsTouchID, FindsByIosNSPredicate<T>,
56+
FindsByIosClassChain<T> {
5557

5658
private static final String IOS_PLATFORM = MobilePlatform.IOS;
5759

src/main/java/io/appium/java_client/ios/IOSElement.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package io.appium.java_client.ios;
1818

19+
import io.appium.java_client.FindsByIosClassChain;
1920
import io.appium.java_client.FindsByIosNSPredicate;
2021
import io.appium.java_client.FindsByIosUIAutomation;
2122
import io.appium.java_client.MobileElement;
2223

2324
public class IOSElement extends MobileElement
24-
implements FindsByIosUIAutomation<MobileElement>, FindsByIosNSPredicate<MobileElement> {
25+
implements FindsByIosUIAutomation<MobileElement>, FindsByIosNSPredicate<MobileElement>,
26+
FindsByIosClassChain<MobileElement> {
2527
}

src/main/java/io/appium/java_client/pagefactory/bys/builder/Strategies.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ enum Strategies {
9191
.windowsAutomation(getValue(annotation, this));
9292
}
9393
},
94+
BY_CLASS_CHAIN("iOSClassChain") {
95+
@Override By getBy(Annotation annotation) {
96+
return MobileBy
97+
.iOSClassChain(getValue(annotation, this));
98+
}
99+
},
94100
BY_NS_PREDICATE("iOSNsPredicate") {
95101
@Override By getBy(Annotation annotation) {
96102
return MobileBy

src/main/java/io/appium/java_client/pagefactory/iOSXCUITFindBy.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@
2626
@Repeatable(iOSXCUITFindBySet.class)
2727
public @interface iOSXCUITFindBy {
2828

29+
/**
30+
* The Class Chain locator is similar to xpath, but it's faster and can only
31+
* search direct children elements. See the
32+
* <a href="https://github.com/facebook/WebDriverAgent/wiki/Queries">
33+
* documentation</a> for more details.
34+
*/
35+
String iOSClassChain() default "";
36+
2937
/**
3038
* The NSPredicate class is used to define logical conditions used to constrain
3139
* a search either for a fetch or for in-memory filtering.

src/test/java/io/appium/java_client/pagefactory_tests/XCUITModeTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919

2020
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.ALL_POSSIBLE;
2121
import static io.appium.java_client.pagefactory.LocatorGroupStrategy.CHAIN;
22+
import static org.hamcrest.MatcherAssert.assertThat;
23+
import static org.hamcrest.Matchers.equalTo;
24+
import static org.hamcrest.Matchers.greaterThan;
25+
import static org.hamcrest.Matchers.is;
2226
import static org.junit.Assert.assertEquals;
2327
import static org.junit.Assert.assertNotEquals;
2428
import static org.junit.Assert.assertNotNull;
@@ -41,6 +45,7 @@
4145
import org.openqa.selenium.support.PageFactory;
4246
import org.openqa.selenium.support.ui.WebDriverWait;
4347

48+
import java.util.List;
4449
import java.util.function.Supplier;
4550

4651
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@@ -85,7 +90,14 @@ public class XCUITModeTest extends AppXCUITTest {
8590
@iOSXCUITFindBy(iOSNsPredicate = "name BEGINSWITH 'location'")
8691
private MobileElement locationAlert;
8792

93+
@iOSXCUITFindBy(iOSClassChain = "XCUIElementTypeWindow/*/XCUIElementTypeTextField[2]")
94+
private MobileElement secondTextField;
8895

96+
@iOSXCUITFindBy(iOSClassChain = "XCUIElementTypeWindow/*/XCUIElementTypeButton[-1]")
97+
private MobileElement lastButton;
98+
99+
@iOSXCUITFindBy(iOSClassChain = "XCUIElementTypeWindow/*/XCUIElementTypeButton")
100+
private List<MobileElement> allButtons;
89101

90102
/**
91103
* The setting up.
@@ -133,6 +145,18 @@ public class XCUITModeTest extends AppXCUITTest {
133145
assertTrue(locationAlert.isDisplayed());
134146
}
135147

148+
@Test public void findElementByClassChain() {
149+
assertThat(secondTextField.getAttribute("name"), equalTo("IntegerB"));
150+
}
151+
152+
@Test public void findElementByClassChainWithNegativeIndex() {
153+
assertThat(lastButton.getAttribute("name"), equalTo("Test Gesture"));
154+
}
155+
156+
@Test public void findMultipleElementsByClassChain() {
157+
assertThat(allButtons.size(), is(greaterThan(1)));
158+
}
159+
136160
@Test public void findElementByXUISelectorTest() {
137161
assertNotNull(gesture.getText());
138162
}

0 commit comments

Comments
 (0)