Skip to content

Commit

Permalink
add appium prefix in create session and fix find_elements for W3C (#196)
Browse files Browse the repository at this point in the history
* add appium prefix in create session

* fix find_elements by w3c for Appium

* introduce forceMjsonwp

* refine a bit

* fix some tests

* update the docset
  • Loading branch information
KazuCocoa authored Jan 23, 2018
1 parent 3da35ba commit f37733d
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 8 deletions.
163 changes: 161 additions & 2 deletions appium/webdriver/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,61 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

from selenium.common.exceptions import TimeoutException, InvalidArgumentException

from selenium.webdriver.remote.command import Command as RemoteCommand
import copy

# From remote/webdriver.py
_W3C_CAPABILITY_NAMES = frozenset([
'acceptInsecureCerts',
'browserName',
'browserVersion',
'platformName',
'pageLoadStrategy',
'proxy',
'setWindowRect',
'timeouts',
'unhandledPromptBehavior',
])

# From remote/webdriver.py
_OSS_W3C_CONVERSION = {
'acceptSslCerts': 'acceptInsecureCerts',
'version': 'browserVersion',
'platform': 'platformName'
}

_EXTENSION_CAPABILITY = ':'
_FORCE_MJSONWP = 'forceMjsonwp'

# override
# Add appium prefix for the non-W3C capabilities
def _make_w3c_caps(caps):
appium_prefix = 'appium:'

caps = copy.deepcopy(caps)
profile = caps.get('firefox_profile')
always_match = {}
if caps.get('proxy') and caps['proxy'].get('proxyType'):
caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower()
for k, v in caps.items():
if v and k in _OSS_W3C_CONVERSION:
always_match[_OSS_W3C_CONVERSION[k]] = v.lower() if k == 'platform' else v
if k in _W3C_CAPABILITY_NAMES or _EXTENSION_CAPABILITY in k:
always_match[k] = v
else:
if not k.startswith(appium_prefix):
always_match[appium_prefix + k] = v
if profile:
moz_opts = always_match.get('moz:firefoxOptions', {})
# If it's already present, assume the caller did that intentionally.
if 'profile' not in moz_opts:
# Don't mutate the original capabilities.
new_opts = copy.deepcopy(moz_opts)
new_opts['profile'] = profile
always_match['moz:firefoxOptions'] = new_opts
return {'firstMatch': [{}], 'alwaysMatch': always_match}

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

def start_session(self, capabilities, browser_profile=None):
"""
Override for Appium
Creates a new session with the desired capabilities.
:Args:
- automation_name - The name of automation engine to use.
- platform_name - The name of target platform.
- platform_version - The kind of mobile device or emulator to use
- app - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these.
Read https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md for more details.
"""
if not isinstance(capabilities, dict):
raise InvalidArgumentException('Capabilities must be a dictionary')
if browser_profile:
if 'moz:firefoxOptions' in capabilities:
capabilities['moz:firefoxOptions']['profile'] = browser_profile.encoded
else:
capabilities.update({'firefox_profile': browser_profile.encoded})

parameters = self._merge_capabilities(capabilities)

response = self.execute(RemoteCommand.NEW_SESSION, parameters)
if 'sessionId' not in response:
response = response['value']
self.session_id = response['sessionId']
self.capabilities = response.get('value')

# if capabilities is none we are probably speaking to
# a W3C endpoint
if self.capabilities is None:
self.capabilities = response.get('capabilities')

# Double check to see if we have a W3C Compliant browser
self.w3c = response.get('status') is None

def _merge_capabilities(self, capabilities):
"""
Manage capabilities whether W3C format or MJSONWP format
"""
if _FORCE_MJSONWP in capabilities:
force_mjsonwp = capabilities[_FORCE_MJSONWP]
del capabilities[_FORCE_MJSONWP]

if force_mjsonwp != False:
return {'desiredCapabilities': capabilities}

w3c_caps = _make_w3c_caps(capabilities)
return {'capabilities': w3c_caps, 'desiredCapabilities': capabilities}

@property
def contexts(self):
"""
Expand Down Expand Up @@ -78,6 +182,61 @@ def context(self):
"""
return self.current_context

def find_element(self, by=By.ID, value=None):
"""
Override for Appium
'Private' method used by the find_element_by_* methods.
:Usage:
Use the corresponding find_element_by_* instead of this.
:rtype: WebElement
"""
# if self.w3c:
# if by == By.ID:
# by = By.CSS_SELECTOR
# value = '[id="%s"]' % value
# elif by == By.TAG_NAME:
# by = By.CSS_SELECTOR
# elif by == By.CLASS_NAME:
# by = By.CSS_SELECTOR
# value = ".%s" % value
# elif by == By.NAME:
# by = By.CSS_SELECTOR
# value = '[name="%s"]' % value
return self.execute(RemoteCommand.FIND_ELEMENT, {
'using': by,
'value': value})['value']

def find_elements(self, by=By.ID, value=None):
"""
Override for Appium
'Private' method used by the find_elements_by_* methods.
:Usage:
Use the corresponding find_elements_by_* instead of this.
:rtype: list of WebElement
"""
# if self.w3c:
# if by == By.ID:
# by = By.CSS_SELECTOR
# value = '[id="%s"]' % value
# elif by == By.TAG_NAME:
# by = By.CSS_SELECTOR
# elif by == By.CLASS_NAME:
# by = By.CSS_SELECTOR
# value = ".%s" % value
# elif by == By.NAME:
# by = By.CSS_SELECTOR
# value = '[name="%s"]' % value

# Return empty list if driver returns null
# See https://github.com/SeleniumHQ/selenium/issues/4555
return self.execute(RemoteCommand.FIND_ELEMENTS, {
'using': by,
'value': value})['value'] or []

def find_element_by_ios_uiautomation(self, uia_string):
"""Finds an element by uiautomation in iOS.
Expand Down
6 changes: 3 additions & 3 deletions test/functional/ios/appium_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_toggle_touch_id_enrollment(self):
self.driver.toggle_touch_id_enrollment()

def test_hide_keyboard(self):
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
el = self.driver.find_element_by_name('Uses of UITextField')
el.click()

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

def test_hide_keyboard_presskey_strategy(self):
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
el = self.driver.find_element_by_name('Uses of UITextField')
el.click()

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

def test_hide_keyboard_no_key_name(self):
el = self.driver.find_element_by_name('TextFields, Uses of UITextField')
el = self.driver.find_element_by_name('Uses of UITextField')
el.click()

# get focus on text field, so keyboard comes up
Expand Down
6 changes: 3 additions & 3 deletions test/functional/ios/find_by_ios_class_chain_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ def tearDownClass(self):
self.driver.quit()

def test_find_element_by_path(self):
el = self.driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/*/*/XCUIElementTypeStaticText')
el = self.driver.find_element_by_ios_class_chain('XCUIElementTypeWindow/**/XCUIElementTypeStaticText')
self.assertEqual('UICatalog', el.get_attribute('name'))

def test_find_multiple_elements_by_path(self):
el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*')
self.assertEqual(len(el), 2)
el = self.driver.find_elements_by_ios_class_chain('XCUIElementTypeWindow/*/*/*')
self.assertEqual(6, len(el))
self.assertEqual('UICatalog', el[0].get_attribute('name'))
self.assertEqual(None, el[1].get_attribute('name'))

Expand Down

0 comments on commit f37733d

Please sign in to comment.