Skip to content

Commit 9bfcc07

Browse files
claudeDor-bl
authored andcommitted
fix: keep the user client config when switching to a direct connect endpoint
When the server decorates the new session response with the directConnect* capabilities, the client rebuilt its command executor out of the endpoint URL alone. That silently dropped every setting of the AppiumClientConfig given by the user, including the read timeout, the pool manager arguments, the proxy, the CA bundle and the authentication credentials, and downgraded the config instance to a plain selenium ClientConfig. The most visible consequence was that the documented way of configuring a read timeout had no effect on such sessions, so a hanging server call never timed out. Reuse a copy of the current configuration and only replace the endpoint it points to instead. Copying keeps the instance given by the user untouched. It also stops the deprecated remote_server_addr/keep_alive constructor arguments of RemoteConnection from being used. Closes appium#855 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PjZRpyoEfSCLjTx5ctzQmJ
1 parent 6d775af commit 9bfcc07

3 files changed

Lines changed: 72 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ driver = webdriver.Remote(
327327
)
328328
```
329329
330+
Only the endpoint the client talks to is replaced. The rest of the given `client_config`,
331+
such as `timeout`, `init_args_for_pool_manager`, proxy, certificates and authentication
332+
settings, keeps being applied to the requests sent to the direct connect endpoint.
333+
330334
## Relax SSL validation
331335
332336
`strict_ssl` option allows you to send commands to an invalid certificate host like a self-signed one.

appium/webdriver/webdriver.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import copy
1516
from collections.abc import Callable
1617
from typing import TYPE_CHECKING, Any
1718

@@ -318,11 +319,19 @@ def _update_command_executor(self, keep_alive: bool) -> None:
318319
executor = f'{protocol}://{hostname}:{port}{path}'
319320

320321
logger.debug('Updated request endpoint to %s', executor)
322+
321323
# Override command executor.
324+
# The client configuration given by a user, e.g. the read timeout, the proxy or
325+
# the authentication credentials, must be kept as-is. Only the endpoint the client
326+
# talks to changes, thus a copy of the current configuration is reused instead of
327+
# building a brand-new one out of the endpoint URL.
328+
client_config = copy.copy(self.command_executor.client_config)
329+
client_config.remote_server_addr = executor
330+
client_config.keep_alive = keep_alive
322331
if isinstance(self.command_executor, AppiumConnection): # type: ignore
323-
self.command_executor = AppiumConnection(executor, keep_alive=keep_alive)
332+
self.command_executor = AppiumConnection(client_config=client_config)
324333
else:
325-
self.command_executor = RemoteConnection(executor, keep_alive=keep_alive)
334+
self.command_executor = RemoteConnection(client_config=client_config)
326335
self._add_commands()
327336

328337
# https://github.com/SeleniumHQ/selenium/blob/06fdf2966df6bca47c0ae45e8201cd30db9b9a49/py/selenium/webdriver/remote/webdriver.py#L277

test/unit/webdriver/webdriver_test.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,63 @@ def test_create_session_register_uridirect(self):
136136
assert driver.contexts == ['NATIVE_APP', 'CHROMIUM']
137137
assert isinstance(driver.command_executor, AppiumConnection)
138138

139+
@httpretty.activate
140+
def test_create_session_register_uridirect_keeps_client_config(self):
141+
"""The client configuration given by a user must survive the direct connect switch.
142+
https://github.com/appium/python-client/issues/855
143+
"""
144+
httpretty.register_uri(
145+
httpretty.POST,
146+
f'{SERVER_URL_BASE}/session',
147+
body=json.dumps(
148+
{
149+
'sessionId': 'session-id',
150+
'capabilities': {
151+
'deviceName': 'Android Emulator',
152+
'directConnectProtocol': 'http',
153+
'directConnectHost': 'localhost2',
154+
'directConnectPort': 4800,
155+
'directConnectPath': '/special/path/wd/hub',
156+
},
157+
}
158+
),
159+
)
160+
161+
desired_caps = {
162+
'platformName': 'Android',
163+
'deviceName': 'Android Emulator',
164+
'app': 'path/to/app',
165+
'automationName': 'UIAutomator2',
166+
}
167+
client_config = AppiumClientConfig(
168+
remote_server_addr=SERVER_URL_BASE,
169+
direct_connection=True,
170+
timeout=5,
171+
username='user',
172+
password='pass',
173+
user_agent='custom-agent',
174+
init_args_for_pool_manager={'init_args_for_pool_manager': {'retries': 3}},
175+
)
176+
driver = webdriver.Remote(
177+
SERVER_URL_BASE,
178+
options=UiAutomator2Options().load_capabilities(desired_caps),
179+
client_config=client_config,
180+
)
181+
182+
new_client_config = driver.command_executor.client_config
183+
assert isinstance(new_client_config, AppiumClientConfig)
184+
assert new_client_config.remote_server_addr == 'http://localhost2:4800/special/path/wd/hub'
185+
assert new_client_config.timeout == 5
186+
assert new_client_config.username == 'user'
187+
assert new_client_config.password == 'pass'
188+
assert new_client_config.user_agent == 'custom-agent'
189+
assert new_client_config.init_args_for_pool_manager == {'init_args_for_pool_manager': {'retries': 3}}
190+
assert new_client_config.direct_connection
191+
assert new_client_config.keep_alive == client_config.keep_alive
192+
193+
# the configuration instance given by a user must not be modified in-place
194+
assert client_config.remote_server_addr == SERVER_URL_BASE
195+
139196
@httpretty.activate
140197
def test_create_session_register_uridirect_no_direct_connect_path(self):
141198
httpretty.register_uri(

0 commit comments

Comments
 (0)