Skip to content

Commit f37733d

Browse files
authored
add appium prefix in create session and fix find_elements for W3C (appium#196)
* add appium prefix in create session * fix find_elements by w3c for Appium * introduce forceMjsonwp * refine a bit * fix some tests * update the docset
1 parent 3da35ba commit f37733d

3 files changed

Lines changed: 167 additions & 8 deletions

File tree

appium/webdriver/webdriver.py

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,61 @@
2626
from selenium.webdriver.common.by import By
2727
from selenium.webdriver.remote.webelement import WebElement
2828
from selenium.webdriver.support.ui import WebDriverWait
29-
from selenium.common.exceptions import TimeoutException
30-
29+
from selenium.common.exceptions import TimeoutException, InvalidArgumentException
30+
31+
from selenium.webdriver.remote.command import Command as RemoteCommand
32+
import copy
33+
34+
# From remote/webdriver.py
35+
_W3C_CAPABILITY_NAMES = frozenset([
36+
'acceptInsecureCerts',
37+
'browserName',
38+
'browserVersion',
39+
'platformName',
40+
'pageLoadStrategy',
41+
'proxy',
42+
'setWindowRect',
43+
'timeouts',
44+
'unhandledPromptBehavior',
45+
])
46+
47+
# From remote/webdriver.py
48+
_OSS_W3C_CONVERSION = {
49+
'acceptSslCerts': 'acceptInsecureCerts',
50+
'version': 'browserVersion',
51+
'platform': 'platformName'
52+
}
53+
54+
_EXTENSION_CAPABILITY = ':'
55+
_FORCE_MJSONWP = 'forceMjsonwp'
56+
57+
# override
58+
# Add appium prefix for the non-W3C capabilities
59+
def _make_w3c_caps(caps):
60+
appium_prefix = 'appium:'
61+
62+
caps = copy.deepcopy(caps)
63+
profile = caps.get('firefox_profile')
64+
always_match = {}
65+
if caps.get('proxy') and caps['proxy'].get('proxyType'):
66+
caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower()
67+
for k, v in caps.items():
68+
if v and k in _OSS_W3C_CONVERSION:
69+
always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v
70+
if k in _W3C_CAPABILITY_NAMES or _EXTENSION_CAPABILITY in k:
71+
always_match[k] = v
72+
else:
73+
if not k.startswith(appium_prefix):
74+
always_match[appium_prefix + k] = v
75+
if profile:
76+
moz_opts = always_match.get('moz:firefoxOptions', {})
77+
# If it's already present, assume the caller did that intentionally.
78+
if 'profile' not in moz_opts:
79+
# Don't mutate the original capabilities.
80+
new_opts = copy.deepcopy(moz_opts)
81+
new_opts['profile'] = profile
82+
always_match['moz:firefoxOptions'] = new_opts
83+
return {'firstMatch': [{}], 'alwaysMatch': always_match}
3184

3285
class WebDriver(webdriver.Remote):
3386
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
@@ -48,6 +101,57 @@ def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
48101
By.ANDROID_UIAUTOMATOR = MobileBy.ANDROID_UIAUTOMATOR
49102
By.ACCESSIBILITY_ID = MobileBy.ACCESSIBILITY_ID
50103

104+
def start_session(self, capabilities, browser_profile=None):
105+
"""
106+
Override for Appium
107+
Creates a new session with the desired capabilities.
108+
109+
:Args:
110+
- automation_name - The name of automation engine to use.
111+
- platform_name - The name of target platform.
112+
- platform_version - The kind of mobile device or emulator to use
113+
- app - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these.
114+
115+
Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md for more details.
116+
"""
117+
if not isinstance(capabilities, dict):
118+
raise InvalidArgumentException('Capabilities must be a dictionary')
119+
if browser_profile:
120+
if 'moz:firefoxOptions' in capabilities:
121+
capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded
122+
else:
123+
capabilities.update({'firefox_profile': browser_profile.encoded})
124+
125+
parameters = self._merge_capabilities(capabilities)
126+
127+
response = self.execute(RemoteCommand.NEW_SESSION, parameters)
128+
if 'sessionId' not in response:
129+
response = response['value']
130+
self.session_id = response['sessionId']
131+
self.capabilities = response.get('value')
132+
133+
# if capabilities is none we are probably speaking to
134+
# a W3C endpoint
135+
if self.capabilities is None:
136+
self.capabilities = response.get('capabilities')
137+
138+
# Double check to see if we have a W3C Compliant browser
139+
self.w3c = response.get('status') is None
140+
141+
def _merge_capabilities(self, capabilities):
142+
"""
143+
Manage capabilities whether W3C format or MJSONWP format
144+
"""
145+
if _FORCE_MJSONWP in capabilities:
146+
force_mjsonwp = capabilities[_FORCE_MJSONWP]
147+
del capabilities[_FORCE_MJSONWP]
148+
149+
if force_mjsonwp != False:
150+
return {'desiredCapabilities': capabilities}
151+
152+
w3c_caps = _make_w3c_caps(capabilities)
153+
return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities}
154+
51155
@property
52156
def contexts(self):
53157
"""
@@ -78,6 +182,61 @@ def context(self):
78182
"""
79183
return self.current_context
80184

185+
def find_element(self, by=By.ID, value=None):
186+
"""
187+
Override for Appium
188+
'Private' method used by the find_element_by_* methods.
189+
190+
:Usage:
191+
Use the corresponding find_element_by_* instead of this.
192+
193+
:rtype: WebElement
194+
"""
195+
# if self.w3c:
196+
# if by == By.ID:
197+
# by = By.CSS_SELECTOR
198+
# value = '[id="%s"]' % value
199+
# elif by == By.TAG_NAME:
200+
# by = By.CSS_SELECTOR
201+
# elif by == By.CLASS_NAME:
202+
# by = By.CSS_SELECTOR
203+
# value = ".%s" % value
204+
# elif by == By.NAME:
205+
# by = By.CSS_SELECTOR
206+
# value = '[name="%s"]' % value
207+
return self.execute(RemoteCommand.FIND_ELEMENT, {
208+
'using': by,
209+
'value': value})['value']
210+
211+
def find_elements(self, by=By.ID, value=None):
212+
"""
213+
Override for Appium
214+
'Private' method used by the find_elements_by_* methods.
215+
216+
:Usage:
217+
Use the corresponding find_elements_by_* instead of this.
218+
219+
:rtype: list of WebElement
220+
"""
221+
# if self.w3c:
222+
# if by == By.ID:
223+
# by = By.CSS_SELECTOR
224+
# value = '[id="%s"]' % value
225+
# elif by == By.TAG_NAME:
226+
# by = By.CSS_SELECTOR
227+
# elif by == By.CLASS_NAME:
228+
# by = By.CSS_SELECTOR
229+
# value = ".%s" % value
230+
# elif by == By.NAME:
231+
# by = By.CSS_SELECTOR
232+
# value = '[name="%s"]' % value
233+
234+
# Return empty list if driver returns null
235+
# See https://github.com/SeleniumHQ/selenium/issues/4555
236+
return self.execute(RemoteCommand.FIND_ELEMENTS, {
237+
'using': by,
238+
'value': value})['value'] or []
239+
81240
def find_element_by_ios_uiautomation(self, uia_string):
82241
"""Finds an element by uiautomation in iOS.
83242

test/functional/ios/appium_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_toggle_touch_id_enrollment(self):
5151
self.driver.toggle_touch_id_enrollment()
5252

5353
def test_hide_keyboard(self):
54-
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
54+
el = self.driver.find_element_by_name('Uses of UITextField')
5555
el.click()
5656

5757
# get focus on text field, so keyboard comes up
@@ -66,7 +66,7 @@ def test_hide_keyboard(self):
6666
self.assertFalse(el.is_displayed())
6767

6868
def test_hide_keyboard_presskey_strategy(self):
69-
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
69+
el = self.driver.find_element_by_name('Uses of UITextField')
7070
el.click()
7171

7272
# get focus on text field, so keyboard comes up
@@ -81,7 +81,7 @@ def test_hide_keyboard_presskey_strategy(self):
8181
self.assertFalse(el.is_displayed())
8282

8383
def test_hide_keyboard_no_key_name(self):
84-
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
84+
el = self.driver.find_element_by_name('Uses of UITextField')
8585
el.click()
8686

8787
# get focus on text field, so keyboard comes up

test/functional/ios/find_by_ios_class_chain_tests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ def tearDownClass(self):
2828
self.driver.quit()
2929

3030
def test_find_element_by_path(self):
31-
el = self.driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/*/*/XCUIElementTypeStaticText')
31+
el = self.driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/**/XCUIElementTypeStaticText')
3232
self.assertEqual('UICatalog', el.get_attribute('name'))
3333

3434
def test_find_multiple_elements_by_path(self):
35-
el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*')
36-
self.assertEqual(len(el), 2)
35+
el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*/*')
36+
self.assertEqual(6, len(el))
3737
self.assertEqual('UICatalog', el[0].get_attribute('name'))
3838
self.assertEqual(None, el[1].get_attribute('name'))
3939

0 commit comments

Comments
 (0)