Skip to content

Commit f2bf259

Browse files
authored
feat: Add viewmatcher (appium#480)
* Add android view matcher as strategy locator * Add docstring * Add functional test * Remove find_elements_by_android_data_matcher * Fix docstring * tweak docstring
1 parent b95776d commit f2bf259

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

appium/webdriver/common/mobileby.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class MobileBy(By):
2222
ANDROID_UIAUTOMATOR = '-android uiautomator'
2323
ANDROID_VIEWTAG = '-android viewtag'
2424
ANDROID_DATA_MATCHER = '-android datamatcher'
25+
ANDROID_VIEW_MATCHER = '-android viewmatcher'
2526
WINDOWS_UI_AUTOMATION = '-windows uiautomation'
2627
ACCESSIBILITY_ID = 'accessibility id'
2728
IMAGE = '-image'

appium/webdriver/extensions/search_context/android.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,39 @@
2424
class AndroidSearchContext(BaseSearchContext):
2525
"""Define search context for Android"""
2626

27+
def find_element_by_android_view_matcher(self, name=None, args=None, className=None):
28+
"""Finds element by [onView](https://developer.android.com/training/testing/espresso/basics) in Android
29+
30+
It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver).
31+
32+
Args:
33+
name (:obj:`str`, optional): The name of a method to invoke.
34+
The method must return a Hamcrest
35+
[Matcher](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html)
36+
args (:obj:`str`, optional): The args provided to the method
37+
className (:obj:`str`, optional): The class name that the method is part of (defaults to `org.hamcrest.Matchers`).
38+
Can be fully qualified by having the androidx.test.espresso.matcher. prefix.
39+
If the prefix is not provided then it is going to be added implicitly.
40+
(e.g.: `class=CursorMatchers` fully qualified is `class=androidx.test.espresso.matcher.CursorMatchers`
41+
42+
Returns:
43+
`appium.webdriver.webelement.WebElement`: The found element
44+
45+
Raises:
46+
TypeError - Raises a TypeError if the arguments are not validated for JSON format
47+
48+
Usage:
49+
driver.find_element_by_android_view_matcher(name='withText', args=['Accessibility'], className='ViewMatchers')
50+
51+
# To enable auto completion in PyCharm(IDE)
52+
:rtype: `appium.webdriver.webelement.WebElement`
53+
"""
54+
55+
return self.find_element(
56+
by=MobileBy.ANDROID_VIEW_MATCHER,
57+
value=self._build_data_matcher(name=name, args=args, className=className)
58+
)
59+
2760
def find_element_by_android_data_matcher(self, name=None, args=None, className=None):
2861
"""Finds element by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android
2962
@@ -58,6 +91,7 @@ def find_element_by_android_data_matcher(self, name=None, args=None, className=N
5891

5992
def find_elements_by_android_data_matcher(self, name=None, args=None, className=None):
6093
"""Finds elements by [onData](https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf) in Android
94+
6195
It works with [Espresso Driver](https://github.com/appium/appium-espresso-driver).
6296
6397
Args:
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env python
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import unittest
17+
18+
import pytest
19+
from selenium.common.exceptions import WebDriverException
20+
21+
from appium import webdriver
22+
from appium.webdriver.common.mobileby import MobileBy
23+
from appium.webdriver.extensions.search_context.android import (
24+
AndroidSearchContext
25+
)
26+
from test.functional.android.helper.test_helper import (
27+
desired_capabilities,
28+
is_ci
29+
)
30+
31+
32+
class FindByViewMatcherTests(unittest.TestCase):
33+
34+
def setUp(self):
35+
desired_caps = desired_capabilities.get_desired_capabilities('ApiDemos-debug.apk.zip')
36+
desired_caps['automationName'] = 'Espresso'
37+
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
38+
39+
def tearDown(self):
40+
if is_ci():
41+
# Take the screenshot to investigate when tests failed only on CI
42+
img_path = os.path.join(os.getcwd(), self._testMethodName + '.png')
43+
self.driver.get_screenshot_as_file(img_path)
44+
self.driver.quit()
45+
46+
def test_find_single_element(self):
47+
el = self.driver.find_element_by_android_view_matcher(
48+
name='withText', args=['Accessibility'], className='ViewMatchers')
49+
assert el.text == 'Accessibility'
50+
51+
def test_find_single_element_ful_class_name(self):
52+
el = self.driver.find_element_by_android_view_matcher(
53+
name='withText', args=['Accessibility'], className='androidx.test.espresso.matcher.ViewMatchers')
54+
assert el.text == 'Accessibility'
55+
56+
def test_find_single_element_using_hamcrest_matcher(self):
57+
el = self.driver.find_element_by_android_view_matcher(
58+
name='withText',
59+
args={
60+
'name': 'containsString',
61+
'args': 'Animati',
62+
'class': 'org.hamcrest.Matchers'},
63+
className='ViewMatchers')
64+
assert el.text == 'Animation'
65+
66+
# androidx.test.espresso.AmbiguousViewMatcherException:
67+
# 'with text: a string containing "Access"' matches multiple views in the hierarchy.
68+
def test_find_multiple_elements(self):
69+
value = AndroidSearchContext()._build_data_matcher(
70+
name='withSubstring', args=['Access'], className='ViewMatchers')
71+
with pytest.raises(WebDriverException):
72+
self.driver.find_elements(by=MobileBy.ANDROID_VIEW_MATCHER, value=value)
73+
74+
75+
if __name__ == '__main__':
76+
suite = unittest.TestLoader().loadTestsFromTestCase(FindByViewMatcherTests)
77+
unittest.TextTestRunner(verbosity=2).run(suite)

0 commit comments

Comments
 (0)