2626from selenium .webdriver .common .by import By
2727from selenium .webdriver .remote .webelement import WebElement
2828from 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
3285class 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
0 commit comments