Skip to content

Commit ba2ecd6

Browse files
authored
fix(webdriver): preserve direct connect root paths and custom executor state (#1365)
1 parent 9d60a60 commit ba2ecd6

2 files changed

Lines changed: 94 additions & 13 deletions

File tree

‎appium/webdriver/webdriver.py‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def _get_remote_connection_and_client_config(
216216
def _get_direct_connect_value(caps: dict[str, Any], key: str) -> Any:
217217
for candidate in (f'{APPIUM_PREFIX}{key}', key):
218218
value = caps.get(candidate)
219-
if value is not None and value != '':
219+
if value is not None and (value != '' or key == 'directConnectPath'):
220220
return value
221221
return None
222222

@@ -281,7 +281,7 @@ def __init__(
281281

282282
self.error_handler = MobileErrorHandler()
283283

284-
effective_client_config = client_config or getattr(self.command_executor, 'client_config', None)
284+
effective_client_config = self.command_executor.client_config
285285
if isinstance(effective_client_config, AppiumClientConfig) and effective_client_config.direct_connection:
286286
self._update_command_executor(keep_alive=effective_client_config.keep_alive)
287287

@@ -339,19 +339,21 @@ def _update_command_executor(self, keep_alive: bool) -> None:
339339

340340
logger.debug('Updated request endpoint to %s', executor)
341341

342-
# Override command executor.
342+
# Keep the existing executor, including custom constructor arguments and state.
343343
# The client configuration given by a user, e.g. the read timeout, the proxy or
344344
# the authentication credentials, must be kept as-is. Only the endpoint the client
345345
# talks to changes, thus a copy of the current configuration is reused instead of
346346
# building a brand-new one out of the endpoint URL.
347347
client_config = copy.copy(self.command_executor.client_config)
348348
client_config.remote_server_addr = executor
349349
client_config.keep_alive = keep_alive
350-
if isinstance(self.command_executor, AppiumConnection):
351-
self.command_executor = type(self.command_executor)(client_config=client_config)
352-
else:
353-
self.command_executor = RemoteConnection(client_config=client_config)
354-
self._add_commands()
350+
if hasattr(self.command_executor, '_conn'):
351+
self.command_executor._conn.clear()
352+
self.command_executor._client_config = client_config
353+
# Proxy selection (including no_proxy) can change with the endpoint.
354+
self.command_executor._proxy_url = client_config.get_proxy_url()
355+
if keep_alive:
356+
self.command_executor._conn = self.command_executor._get_connection_manager()
355357

356358
# https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277
357359
# noinspection PyAttributeOutsideInit

‎test/unit/webdriver/webdriver_test.py‎

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,71 @@ def test_create_session_register_uridirect_ignores_empty_values(self):
349349

350350
assert driver.command_executor._client_config.remote_server_addr == SERVER_URL_BASE
351351

352+
@pytest.mark.parametrize('prefix', ['', 'appium:'])
353+
@pytest.mark.parametrize('path', ['', '/', None])
352354
@httpretty.activate
353-
def test_create_session_register_uridirect_with_custom_connection(self):
355+
def test_direct_connect_root_path(self, prefix, path):
356+
caps = {
357+
f'{prefix}directConnectProtocol': 'http',
358+
f'{prefix}directConnectHost': 'localhost2',
359+
f'{prefix}directConnectPort': 4800,
360+
f'{prefix}directConnectPath': path,
361+
}
362+
# An explicit empty prefixed path must win over a legacy path.
363+
if prefix:
364+
caps['directConnectPath'] = '/legacy' if path is not None else None
365+
httpretty.register_uri(
366+
httpretty.POST,
367+
f'{SERVER_URL_BASE}/session',
368+
body=json.dumps({'sessionId': 'session-id', 'capabilities': caps}),
369+
)
370+
endpoint = SERVER_URL_BASE if path is None else f'http://localhost2:4800{path}'
371+
httpretty.register_uri(
372+
httpretty.GET,
373+
f'{endpoint}/session/session-id/contexts',
374+
body=json.dumps({'value': ['NATIVE_APP']}),
375+
)
376+
driver = webdriver.Remote(
377+
options=UiAutomator2Options(),
378+
client_config=AppiumClientConfig(remote_server_addr=SERVER_URL_BASE, direct_connection=True),
379+
)
380+
assert driver.command_executor.client_config.remote_server_addr == endpoint
381+
assert driver.contexts == ['NATIVE_APP']
382+
383+
@pytest.mark.parametrize('keep_alive', [True, False])
384+
def test_direct_connect_rebuilds_proxy_pool(self, monkeypatch, keep_alive):
385+
monkeypatch.setenv('http_proxy', 'http://proxy:8080')
386+
monkeypatch.setenv('no_proxy', 'grid')
387+
config = AppiumClientConfig(remote_server_addr='http://grid:4444', keep_alive=keep_alive)
388+
connection = AppiumConnection(client_config=config)
389+
assert connection._proxy_url is None
390+
driver = WebDriver.__new__(WebDriver)
391+
driver.command_executor = connection
392+
driver.caps = {
393+
'directConnectProtocol': 'http',
394+
'directConnectHost': 'node',
395+
'directConnectPort': 4723,
396+
'directConnectPath': '',
397+
}
398+
with patch.object(connection, '_get_connection_manager', wraps=connection._get_connection_manager) as manager:
399+
if keep_alive:
400+
old_pool = connection._conn
401+
with patch.object(old_pool, 'clear', wraps=old_pool.clear) as clear:
402+
driver._update_command_executor(keep_alive)
403+
clear.assert_called_once()
404+
assert connection._conn is not old_pool
405+
assert isinstance(connection._conn, urllib3.ProxyManager)
406+
manager.assert_called_once()
407+
else:
408+
driver._update_command_executor(keep_alive)
409+
manager.assert_not_called()
410+
assert connection._proxy_url == 'http://proxy:8080'
411+
assert config.remote_server_addr == 'http://grid:4444'
412+
connection.close()
413+
414+
@pytest.mark.parametrize('keep_alive', [True, False])
415+
@httpretty.activate
416+
def test_create_session_register_uridirect_with_custom_connection(self, keep_alive):
354417
httpretty.register_uri(
355418
httpretty.POST,
356419
f'{SERVER_URL_BASE}/session',
@@ -369,22 +432,38 @@ def test_create_session_register_uridirect_with_custom_connection(self):
369432
)
370433

371434
class CustomAppiumConnection(AppiumConnection):
372-
pass
435+
def __init__(self, token, client_config):
436+
super().__init__(client_config=client_config)
437+
self.token = token
373438

374439
desired_caps = {
375440
'platformName': 'Android',
376441
'deviceName': 'Android Emulator',
377442
'app': 'path/to/app',
378443
'automationName': 'UIAutomator2',
379444
}
380-
client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE, direct_connection=True)
381-
custom_connection = CustomAppiumConnection(client_config=client_config)
445+
client_config = AppiumClientConfig(remote_server_addr=SERVER_URL_BASE, direct_connection=True, keep_alive=keep_alive)
446+
custom_connection = CustomAppiumConnection('custom-token', client_config=client_config)
447+
state = object()
448+
custom_connection.state = state
449+
custom_connection._commands = custom_connection._commands.copy()
450+
custom_connection.add_command('custom_command', 'GET', '/session/$sessionId/custom')
451+
httpretty.register_uri(
452+
httpretty.GET,
453+
'http://localhost2:4800/special/path/wd/hub/session/session-id/custom',
454+
body=json.dumps({'value': 'custom-result'}),
455+
)
382456
driver = webdriver.Remote(
383457
custom_connection,
384458
options=UiAutomator2Options().load_capabilities(desired_caps),
385459
)
386460

387-
assert isinstance(driver.command_executor, CustomAppiumConnection)
461+
assert driver.command_executor is custom_connection
462+
assert custom_connection.token == 'custom-token'
463+
assert custom_connection.state is state
464+
assert client_config.remote_server_addr == SERVER_URL_BASE
465+
assert driver.command_executor.client_config.keep_alive is keep_alive
466+
assert driver.execute('custom_command')['value'] == 'custom-result'
388467
assert driver.command_executor._client_config.remote_server_addr == 'http://localhost2:4800/special/path/wd/hub'
389468

390469
@httpretty.activate

0 commit comments

Comments
 (0)