Skip to content

Commit 3573e23

Browse files
ccharnkijmykola-mokhnach
authored andcommitted
feat: Add support for viewmatcher (#1293)
1 parent 03172d8 commit 3573e23

File tree

11 files changed

+280
-4
lines changed

11 files changed

+280
-4
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 FindsByAndroidViewMatcher<T extends WebElement> extends FindsByFluentSelector<T> {
24+
25+
default T findElementByAndroidViewMatcher(String using) {
26+
return findElement(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using);
27+
}
28+
29+
default List<T> findElementsByAndroidViewMatcher(String using) {
30+
return findElements(MobileSelector.ANDROID_VIEW_MATCHER.toString(), using);
31+
}
32+
}

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

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,26 @@ public static By iOSClassChain(final String iOSClassChainString) {
9797

9898
/**
9999
* This locator strategy is only available in Espresso Driver mode.
100-
* @param dataMatcherString is a valid class chain locator string.
101-
* See <a href="https://github.com/appium/appium-espresso-driver/pull/386">
100+
* @param dataMatcherString is a valid json string detailing hamcrest matcher for Espresso onData().
101+
* See <a href="http://appium.io/docs/en/writing-running-appium/android/espresso-datamatcher-selector/">
102102
* the documentation</a> for more details
103103
* @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidDataMatcher}
104104
*/
105105
public static By androidDataMatcher(final String dataMatcherString) {
106106
return new ByAndroidDataMatcher(dataMatcherString);
107107
}
108108

109+
/**
110+
* This locator strategy is only available in Espresso Driver mode.
111+
* @param viewMatcherString is a valid json string detailing hamcrest matcher for Espresso onView().
112+
* See <a href="http://appium.io/docs/en/writing-running-appium/android/espresso-datamatcher-selector/">
113+
* the documentation</a> for more details
114+
* @return an instance of {@link io.appium.java_client.MobileBy.ByAndroidViewMatcher}
115+
*/
116+
public static By androidViewMatcher(final String viewMatcherString) {
117+
return new ByAndroidViewMatcher(viewMatcherString);
118+
}
119+
109120
/**
110121
* This locator strategy is available in XCUITest Driver mode.
111122
* @param iOSNsPredicateString is an an iOS NsPredicate String
@@ -407,6 +418,66 @@ protected ByAndroidDataMatcher(String locatorString) {
407418
}
408419
}
409420

421+
public static class ByAndroidViewMatcher extends MobileBy implements Serializable {
422+
423+
protected ByAndroidViewMatcher(String locatorString) {
424+
super(MobileSelector.ANDROID_VIEW_MATCHER, locatorString);
425+
}
426+
427+
/**
428+
* {@inheritDoc}
429+
*
430+
* @throws WebDriverException when current session doesn't support the given selector or when
431+
* value of the selector is not consistent.
432+
* @throws IllegalArgumentException when it is impossible to find something on the given
433+
* {@link SearchContext} instance
434+
*/
435+
@SuppressWarnings("unchecked")
436+
@Override public List<WebElement> findElements(SearchContext context) {
437+
Class<?> contextClass = context.getClass();
438+
439+
if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) {
440+
return FindsByAndroidViewMatcher.class.cast(context)
441+
.findElementsByAndroidViewMatcher(getLocatorString());
442+
}
443+
444+
if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) {
445+
return super.findElements(context);
446+
}
447+
448+
throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class,
449+
FindsByFluentSelector.class);
450+
}
451+
452+
/**
453+
* {@inheritDoc}
454+
*
455+
* @throws WebDriverException when current session doesn't support the given selector or when
456+
* value of the selector is not consistent.
457+
* @throws IllegalArgumentException when it is impossible to find something on the given
458+
* {@link SearchContext} instance
459+
*/
460+
@Override public WebElement findElement(SearchContext context) {
461+
Class<?> contextClass = context.getClass();
462+
463+
if (FindsByAndroidViewMatcher.class.isAssignableFrom(contextClass)) {
464+
return FindsByAndroidViewMatcher.class.cast(context)
465+
.findElementByAndroidViewMatcher(getLocatorString());
466+
}
467+
468+
if (FindsByFluentSelector.class.isAssignableFrom(contextClass)) {
469+
return super.findElement(context);
470+
}
471+
472+
throw formIllegalArgumentException(contextClass, FindsByAndroidViewMatcher.class,
473+
FindsByFluentSelector.class);
474+
}
475+
476+
@Override public String toString() {
477+
return "By.FindsByAndroidViewMatcher: " + getLocatorString();
478+
}
479+
}
480+
410481
public static class ByIosNsPredicate extends MobileBy implements Serializable {
411482

412483
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
@@ -26,6 +26,7 @@ public enum MobileSelector {
2626
IMAGE("-image"),
2727
ANDROID_VIEWTAG("-android viewtag"),
2828
ANDROID_DATA_MATCHER("-android datamatcher"),
29+
ANDROID_VIEW_MATCHER("-android viewmatcher"),
2930
CUSTOM("-custom");
3031

3132
private final String selector;

src/main/java/io/appium/java_client/android/AndroidDriver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.appium.java_client.AppiumDriver;
2727
import io.appium.java_client.CommandExecutionHelper;
2828
import io.appium.java_client.FindsByAndroidDataMatcher;
29+
import io.appium.java_client.FindsByAndroidViewMatcher;
2930
import io.appium.java_client.FindsByAndroidUIAutomator;
3031
import io.appium.java_client.FindsByAndroidViewTag;
3132
import io.appium.java_client.HasOnScreenKeyboard;
@@ -62,7 +63,7 @@ public class AndroidDriver<T extends WebElement>
6263
extends AppiumDriver<T>
6364
implements PressesKey, HasNetworkConnection, PushesFiles, StartsActivity,
6465
FindsByAndroidUIAutomator<T>, FindsByAndroidViewTag<T>, FindsByAndroidDataMatcher<T>,
65-
LocksDevice, HasAndroidSettings, HasAndroidDeviceDetails,
66+
FindsByAndroidViewMatcher<T>, LocksDevice, HasAndroidSettings, HasAndroidDeviceDetails,
6667
HasSupportedPerformanceDataType, AuthenticatesByFinger, HasOnScreenKeyboard,
6768
CanRecordScreen, SupportsSpecialEmulatorCommands,
6869
SupportsNetworkStateManagement, ListensToLogcatMessages, HasAndroidClipboard,

src/main/java/io/appium/java_client/android/AndroidElement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020

2121
import io.appium.java_client.CommandExecutionHelper;
2222
import io.appium.java_client.FindsByAndroidDataMatcher;
23+
import io.appium.java_client.FindsByAndroidViewMatcher;
2324
import io.appium.java_client.FindsByAndroidUIAutomator;
2425
import io.appium.java_client.FindsByAndroidViewTag;
2526
import io.appium.java_client.MobileElement;
2627

2728
public class AndroidElement extends MobileElement
2829
implements FindsByAndroidUIAutomator<MobileElement>, FindsByAndroidDataMatcher<MobileElement>,
29-
FindsByAndroidViewTag<MobileElement> {
30+
FindsByAndroidViewMatcher<MobileElement>, FindsByAndroidViewTag<MobileElement> {
3031
/**
3132
* This method replace current text value.
3233
* @param value a new value

src/main/java/io/appium/java_client/events/DefaultAspect.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class DefaultAspect {
9999
+ "execution(* org.openqa.selenium.ContextAware.*(..)) || "
100100
+ "execution(* io.appium.java_client.FindsByAccessibilityId.*(..)) || "
101101
+ "execution(* io.appium.java_client.FindsByAndroidUIAutomator.*(..)) || "
102+
+ "execution(* io.appium.java_client.FindsByAndroidDataMatcher.*(..)) || "
103+
+ "execution(* io.appium.java_client.FindsByAndroidViewMatcher.*(..)) || "
102104
+ "execution(* io.appium.java_client.FindsByWindowsAutomation.*(..)) || "
103105
+ "execution(* io.appium.java_client.FindsByIosNSPredicate.*(..)) || "
104106
+ "execution(* org.openqa.selenium.internal.FindsByClassName.*(..)) || "

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@
8181
*/
8282
String androidDataMatcher() default "";
8383

84+
/**
85+
* It is a desired view matcher expression.
86+
*
87+
* @return a desired view matcher expression
88+
*/
89+
String androidViewMatcher() default "";
90+
8491
/**
8592
* It is a xpath to the target element.
8693
*

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
@@ -102,6 +102,12 @@ enum Strategies {
102102
.androidDataMatcher(getValue(annotation, this));
103103
}
104104
},
105+
BY_VIEW_MATCHER("androidViewMatcher") {
106+
@Override By getBy(Annotation annotation) {
107+
return MobileBy
108+
.androidViewMatcher(getValue(annotation, this));
109+
}
110+
},
105111
BY_NS_PREDICATE("iOSNsPredicate") {
106112
@Override By getBy(Annotation annotation) {
107113
return MobileBy
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.android;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableMap;
21+
import io.appium.java_client.MobileBy;
22+
import org.junit.Test;
23+
import org.openqa.selenium.json.Json;
24+
import org.openqa.selenium.support.ui.ExpectedConditions;
25+
import org.openqa.selenium.support.ui.WebDriverWait;
26+
27+
import static org.junit.Assert.assertNotNull;
28+
29+
public class AndroidDataMatcherTest extends BaseEspressoTest {
30+
31+
@Test
32+
public void testFindByDataMatcher() {
33+
final WebDriverWait wait = new WebDriverWait(driver, 10);
34+
wait.until(ExpectedConditions
35+
.elementToBeClickable(MobileBy.AccessibilityId("Graphics")));
36+
driver.findElement(MobileBy.AccessibilityId("Graphics")).click();
37+
38+
String selector = new Json().toJson(ImmutableMap.of(
39+
"name", "hasEntry",
40+
"args", ImmutableList.of("title", "Sweep")
41+
));
42+
43+
assertNotNull(wait.until(ExpectedConditions
44+
.presenceOfElementLocated(MobileBy.androidDataMatcher(selector))));
45+
}
46+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.android;
18+
19+
import com.google.common.collect.ImmutableList;
20+
import com.google.common.collect.ImmutableMap;
21+
import io.appium.java_client.MobileBy;
22+
import org.junit.Test;
23+
import org.openqa.selenium.json.Json;
24+
import org.openqa.selenium.support.ui.ExpectedConditions;
25+
import org.openqa.selenium.support.ui.WebDriverWait;
26+
27+
import static org.junit.Assert.assertNotNull;
28+
29+
public class AndroidViewMatcherTest extends BaseEspressoTest {
30+
31+
@Test
32+
public void testFindByViewMatcher() {
33+
String selector = new Json().toJson(ImmutableMap.of(
34+
"name", "withText",
35+
"args", ImmutableList.of("Animation"),
36+
"class", "androidx.test.espresso.matcher.ViewMatchers"
37+
));
38+
final WebDriverWait wait = new WebDriverWait(driver, 10);
39+
assertNotNull(wait.until(ExpectedConditions
40+
.presenceOfElementLocated(MobileBy.androidViewMatcher(selector))));
41+
}
42+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.android;
18+
19+
import io.appium.java_client.remote.AutomationName;
20+
import io.appium.java_client.remote.MobileCapabilityType;
21+
import io.appium.java_client.service.local.AppiumDriverLocalService;
22+
import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException;
23+
import org.junit.AfterClass;
24+
import org.junit.BeforeClass;
25+
import org.openqa.selenium.remote.DesiredCapabilities;
26+
27+
import java.io.File;
28+
29+
public class BaseEspressoTest {
30+
31+
private static AppiumDriverLocalService service;
32+
protected static AndroidDriver<AndroidElement> driver;
33+
34+
/**
35+
* initialization.
36+
*/
37+
@BeforeClass public static void beforeClass() {
38+
service = AppiumDriverLocalService.buildDefaultService();
39+
service.start();
40+
41+
if (service == null || !service.isRunning()) {
42+
throw new AppiumServerHasNotBeenStartedLocallyException(
43+
"An appium server node is not started!");
44+
}
45+
46+
File appDir = new File("src/test/java/io/appium/java_client");
47+
File app = new File(appDir, "ApiDemos-debug.apk");
48+
DesiredCapabilities capabilities = new DesiredCapabilities();
49+
capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.ESPRESSO);
50+
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "Android Emulator");
51+
capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
52+
capabilities.setCapability("eventTimings", true);
53+
driver = new AndroidDriver<>(service.getUrl(), capabilities);
54+
}
55+
56+
/**
57+
* finishing.
58+
*/
59+
@AfterClass public static void afterClass() {
60+
if (driver != null) {
61+
driver.quit();
62+
}
63+
if (service != null) {
64+
service.stop();
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)