Skip to content

Commit 2c92c04

Browse files
authored
feat: respect the given executor (#844)
* feat: can provide a custom connection * add tests * tweak tests * lint * tweak * add comment * fix lint * tweak * add test * tweak review
1 parent 6d558c0 commit 2c92c04

File tree

4 files changed

+172
-11
lines changed

4 files changed

+172
-11
lines changed

README.md

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ options = UiAutomator2Options()
115115
options.platformVersion = '10'
116116
options.udid = '123456789ABC'
117117
options.app = PATH('../../../apps/test-app.apk')
118-
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
118+
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
119119
self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options)
120120
el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item')
121121
el.click()
@@ -134,7 +134,7 @@ options = XCUITestOptions()
134134
options.platformVersion = '13.4'
135135
options.udid = '123456789ABC'
136136
options.app = PATH('../../apps/UICatalog.app.zip')
137-
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
137+
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
138138
self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options)
139139
el = self.driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='item')
140140
el.click()
@@ -158,7 +158,7 @@ from appium import webdriver
158158
# instead: https://github.com/appium/python-client/pull/720
159159
from appium.options.ios import XCUITestOptions
160160
161-
# load_capabilities API could be used to
161+
# load_capabilities API could be used to
162162
# load options mapping stored in a dictionary
163163
options = XCUITestOptions().load_capabilities({
164164
'platformVersion': '13.4',
@@ -167,9 +167,9 @@ options = XCUITestOptions().load_capabilities({
167167
})
168168
169169
self.driver = webdriver.Remote(
170-
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
171-
'http://127.0.0.1:4723',
172-
options=options,
170+
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
171+
'http://127.0.0.1:4723',
172+
options=options,
173173
direct_connection=True
174174
)
175175
```
@@ -192,10 +192,62 @@ options.automation_name = 'safari'
192192
# calls to it could be chained
193193
options.set_capability('browser_name', 'safari')
194194
195-
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
195+
# Appium1 points to http://127.0.0.1:4723/wd/hub by default
196196
self.driver = webdriver.Remote('http://127.0.0.1:4723', options=options, strict_ssl=False)
197197
```
198198

199+
## Set custom `AppiumConnection`
200+
201+
The first argument of `webdriver.Remote` can set an arbitrary command executor for you.
202+
203+
1. Set init arguments for the pool manager Appium Python client uses to manage http requests.
204+
205+
```python
206+
from appium import webdriver
207+
from appium.options.ios import XCUITestOptions
208+
209+
import urllib3
210+
from appium.webdriver.appium_connection import AppiumConnection
211+
212+
# Retry connection error up to 3 times.
213+
init_args_for_pool_manage = {
214+
'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False)
215+
}
216+
appium_executor = AppiumConnection(remote_server_addr='http://127.0.0.1:4723',
217+
init_args_for_pool_manage=init_args_for_pool_manage)
218+
219+
options = XCUITestOptions()
220+
options.platformVersion = '13.4'
221+
options.udid = '123456789ABC'
222+
options.app = PATH('../../apps/UICatalog.app.zip')
223+
driver = webdriver.Remote(appium_executor, options=options)
224+
```
225+
226+
227+
2. Define a subclass of `AppiumConnection`
228+
229+
```python
230+
from appium import webdriver
231+
from appium.options.ios import XCUITestOptions
232+
233+
from appium.webdriver.appium_connection import AppiumConnection
234+
235+
class CustomAppiumConnection(AppiumConnection):
236+
# Can add your own methods for the custom class
237+
pass
238+
239+
custom_executor = CustomAppiumConnection(remote_server_addr='http://127.0.0.1:4723')
240+
241+
options = XCUITestOptions().load_capabilities({
242+
'platformVersion': '13.4',
243+
'deviceName': 'iPhone Simulator',
244+
'app': PATH('../../apps/UICatalog.app.zip'),
245+
})
246+
driver = webdriver.Remote(custom_executor, options=options)
247+
248+
```
249+
250+
199251
## Documentation
200252

201253
- https://appium.github.io/python-client-sphinx/ is detailed documentation

appium/webdriver/appium_connection.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
import uuid
16-
from typing import TYPE_CHECKING, Any, Dict, Union
16+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
1717

1818
import urllib3
1919
from selenium.webdriver.remote.remote_connection import RemoteConnection
@@ -25,17 +25,32 @@
2525

2626

2727
class AppiumConnection(RemoteConnection):
28+
def __init__(
29+
self,
30+
remote_server_addr: str,
31+
keep_alive: bool = False,
32+
ignore_proxy: Optional[bool] = False,
33+
init_args_for_pool_manager: Union[Dict[str, Any], None] = None,
34+
):
35+
36+
# Need to call before super().__init__ in order to pass arguments for the pool manager in the super.
37+
self._init_args_for_pool_manager = init_args_for_pool_manager or {}
38+
39+
super().__init__(remote_server_addr, keep_alive=keep_alive, ignore_proxy=ignore_proxy)
40+
2841
def _get_connection_manager(self) -> Union[urllib3.PoolManager, urllib3.ProxyManager]:
2942
# https://github.com/SeleniumHQ/selenium/blob/0e0194b0e52a34e7df4b841f1ed74506beea5c3e/py/selenium/webdriver/remote/remote_connection.py#L134
3043
pool_manager_init_args = {'timeout': self._timeout}
31-
# pylint: disable=E1101
44+
3245
if self._ca_certs:
3346
pool_manager_init_args['cert_reqs'] = 'CERT_REQUIRED'
3447
pool_manager_init_args['ca_certs'] = self._ca_certs
3548
else:
3649
# This line is necessary to disable certificate verification
3750
pool_manager_init_args['cert_reqs'] = 'CERT_NONE'
3851

52+
pool_manager_init_args.update(self._init_args_for_pool_manager)
53+
3954
return (
4055
urllib3.PoolManager(**pool_manager_init_args)
4156
if self._proxy_url is None

appium/webdriver/webdriver.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class WebDriver(
204204
):
205205
def __init__(
206206
self,
207-
command_executor: str = 'http://127.0.0.1:4444/wd/hub',
207+
command_executor: Union[str, AppiumConnection] = 'http://127.0.0.1:4444/wd/hub',
208208
desired_capabilities: Optional[Dict] = None,
209209
browser_profile: Union[str, None] = None,
210210
proxy: Union[str, None] = None,
@@ -228,8 +228,11 @@ def __init__(
228228
AppiumConnection.set_certificate_bundle_path(None)
229229
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
230230

231+
if isinstance(command_executor, str):
232+
command_executor = AppiumConnection(command_executor, keep_alive=keep_alive)
233+
231234
super().__init__(
232-
command_executor=AppiumConnection(command_executor, keep_alive=keep_alive),
235+
command_executor=command_executor,
233236
desired_capabilities=desired_capabilities,
234237
browser_profile=browser_profile,
235238
proxy=proxy,

test/unit/webdriver/webdriver_test.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
import json
1616

1717
import httpretty
18+
import urllib3
1819
from mock import patch
1920

2021
from appium import version as appium_version
2122
from appium import webdriver
2223
from appium.options.android import UiAutomator2Options
24+
from appium.webdriver.appium_connection import AppiumConnection
2325
from appium.webdriver.webdriver import ExtensionBase, WebDriver
2426
from test.helpers.constants import SERVER_URL_BASE
2527
from test.unit.helper.test_helper import (
@@ -298,6 +300,95 @@ def add_command(self):
298300
assert result == {}
299301
driver.delete_extensions()
300302

303+
@httpretty.activate
304+
def test_create_session_with_custom_connection(self):
305+
httpretty.register_uri(
306+
httpretty.POST,
307+
f'{SERVER_URL_BASE}/session',
308+
body='{ "value": {"sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}} }',
309+
)
310+
311+
desired_caps = {
312+
'deviceName': 'Android Emulator',
313+
'app': 'path/to/app',
314+
}
315+
316+
class CustomAppiumConnection(AppiumConnection):
317+
# To explicitly check if the given executor is used
318+
pass
319+
320+
init_args_for_pool_manager = {'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False)}
321+
custom_appium_connection = CustomAppiumConnection(
322+
remote_server_addr=SERVER_URL_BASE, init_args_for_pool_manager=init_args_for_pool_manager
323+
)
324+
325+
driver = webdriver.Remote(
326+
custom_appium_connection, options=UiAutomator2Options().load_capabilities(desired_caps)
327+
)
328+
329+
request = httpretty.HTTPretty.latest_requests[0]
330+
assert request.headers['content-type'] == 'application/json;charset=UTF-8'
331+
assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent']
332+
333+
request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8'))
334+
assert request_json.get('capabilities') is not None
335+
assert request_json['capabilities']['alwaysMatch'] == {
336+
'platformName': 'Android',
337+
'appium:deviceName': 'Android Emulator',
338+
'appium:app': 'path/to/app',
339+
'appium:automationName': 'UIAutomator2',
340+
}
341+
assert request_json.get('desiredCapabilities') is None
342+
assert driver.session_id == 'session-id'
343+
344+
assert isinstance(driver.command_executor, CustomAppiumConnection)
345+
346+
@httpretty.activate
347+
def test_create_session_with_custom_connection_with_keepalive(self):
348+
httpretty.register_uri(
349+
httpretty.POST,
350+
f'{SERVER_URL_BASE}/session',
351+
body='{ "value": {"sessionId": "session-id", "capabilities": {"deviceName": "Android Emulator"}} }',
352+
)
353+
354+
desired_caps = {
355+
'deviceName': 'Android Emulator',
356+
'app': 'path/to/app',
357+
}
358+
359+
class CustomAppiumConnection(AppiumConnection):
360+
# To explicitly check if the given executor is used
361+
pass
362+
363+
init_args_for_pool_manager = {'retries': urllib3.util.retry.Retry(total=3, connect=3, read=False)}
364+
custom_appium_connection = CustomAppiumConnection(
365+
# keep alive has different route to set init args for the pool manager
366+
keep_alive=True,
367+
remote_server_addr=SERVER_URL_BASE,
368+
init_args_for_pool_manager=init_args_for_pool_manager,
369+
)
370+
371+
driver = webdriver.Remote(
372+
custom_appium_connection, options=UiAutomator2Options().load_capabilities(desired_caps)
373+
)
374+
375+
request = httpretty.HTTPretty.latest_requests[0]
376+
assert request.headers['content-type'] == 'application/json;charset=UTF-8'
377+
assert 'appium/python {} (selenium'.format(appium_version.version) in request.headers['user-agent']
378+
379+
request_json = json.loads(httpretty.HTTPretty.latest_requests[0].body.decode('utf-8'))
380+
assert request_json.get('capabilities') is not None
381+
assert request_json['capabilities']['alwaysMatch'] == {
382+
'platformName': 'Android',
383+
'appium:deviceName': 'Android Emulator',
384+
'appium:app': 'path/to/app',
385+
'appium:automationName': 'UIAutomator2',
386+
}
387+
assert request_json.get('desiredCapabilities') is None
388+
assert driver.session_id == 'session-id'
389+
390+
assert isinstance(driver.command_executor, CustomAppiumConnection)
391+
301392

302393
class SubWebDriver(WebDriver):
303394
def __init__(self, command_executor, desired_capabilities=None, direct_connection=False, options=None):

0 commit comments

Comments
 (0)