gridfleet-testkit 0.7.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/CHANGELOG.md +33 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/PKG-INFO +91 -49
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/README.md +90 -48
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/__init__.py +7 -1
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/allocation.py +7 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/client.py +43 -1
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/pyproject.toml +1 -1
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_allocation.py +11 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_client.py +21 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin.py +27 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/uv.lock +128 -128
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/.gitignore +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/__init__.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/_example_helpers.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/assets/hello-world.zip +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_android_mobile_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_android_tv_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_firetv_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_ios_simulator_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_roku_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_roku_sideload_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/examples/test_tvos_screenshot.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/appium.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/py.typed +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/pytest_plugin.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/sessions.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_appium.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_client_test_data.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_docs_contract.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_driver_agnostic_guard.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_package_metadata.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin_grid_run_id_injection.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin_test_data.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_sessions.py +0 -0
- {gridfleet_testkit-0.7.0 → gridfleet_testkit-0.9.0}/tests/test_sessions_resolve_device_handle.py +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the GridFleet testkit (`gridfleet-testkit` on PyPI) are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.9.0](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.8.0...gridfleet-testkit-v0.9.0) (2026-05-12)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **testkit:** add run detail client helper ([c80a8cc](https://github.com/quidow/gridfleet/commit/c80a8cc95093c2f46a7e714c96ff0b33018af5ba))
|
|
11
|
+
* **testkit:** expose allocation device tags ([0bf5e2e](https://github.com/quidow/gridfleet/commit/0bf5e2e04b806fadd0afcd3c95073828d0c2414e))
|
|
12
|
+
* **testkit:** support tag-based device targeting ([db0d0e3](https://github.com/quidow/gridfleet/commit/db0d0e3d3d1231828bb22a707d3bdcab6c0ec717))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Documentation
|
|
16
|
+
|
|
17
|
+
* **testkit:** document tag-based device targeting ([096841b](https://github.com/quidow/gridfleet/commit/096841b737dec71524d0edfa4c538d9cc69e7c2c))
|
|
18
|
+
|
|
19
|
+
## [0.8.0](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.7.0...gridfleet-testkit-v0.8.0) (2026-05-12)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* **backend,testkit:** recreate run device cooldown api ([fccfbc7](https://github.com/quidow/gridfleet/commit/fccfbc7bcf694f8c59cbaa394bb075d20e1b34f0))
|
|
25
|
+
* **testkit:** add cooldown_device client helper and result types ([584f411](https://github.com/quidow/gridfleet/commit/584f411f3b72f815e6f4055667c0eaccb1926bc5))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Dependencies
|
|
29
|
+
|
|
30
|
+
* **deps:** bump mypy in /agent ([#195](https://github.com/quidow/gridfleet/issues/195)) ([1317e59](https://github.com/quidow/gridfleet/commit/1317e59bbd4ae6969ed3c717c24b43dbfefec722))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
* **testkit:** correct cooldown_device ttl error in readme ([f768b6b](https://github.com/quidow/gridfleet/commit/f768b6b11b87ad3c4aa369ee2bcdebfeed5c1f86))
|
|
36
|
+
* **testkit:** document cooldown_device api and result types ([0e2418b](https://github.com/quidow/gridfleet/commit/0e2418b0ea3b952d6151a08e3f75116de7edcdd8))
|
|
37
|
+
|
|
5
38
|
## [0.7.0](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.6.0...gridfleet-testkit-v0.7.0) (2026-05-11)
|
|
6
39
|
|
|
7
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridfleet-testkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Supported pytest and run-orchestration helpers for GridFleet integrations
|
|
5
5
|
Project-URL: Homepage, https://github.com/quidow/gridfleet
|
|
6
6
|
Project-URL: Repository, https://github.com/quidow/gridfleet
|
|
@@ -40,7 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
- Stable import root: `gridfleet_testkit`
|
|
42
42
|
- Supported pytest plugin: `gridfleet_testkit.pytest_plugin`
|
|
43
|
-
- Supported pytest fixtures: `appium_driver`, `gridfleet_client`, `device_config`, `device_test_data`, `gridfleet_worker_id`
|
|
43
|
+
- Supported pytest fixtures: `appium_driver`, `gridfleet_client`, `device_config`, `device_test_data`, `device_handle`, `gridfleet_worker_id`
|
|
44
44
|
- Supported public Appium helpers:
|
|
45
45
|
- `build_appium_options`
|
|
46
46
|
- `create_appium_driver`
|
|
@@ -54,12 +54,15 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
- Supported public allocation/session helpers:
|
|
55
55
|
- `AllocatedDevice`
|
|
56
56
|
- `UnavailableInclude`
|
|
57
|
-
- `CooldownResult`
|
|
58
57
|
- `build_error_session_payload`
|
|
59
58
|
- `hydrate_allocated_device`
|
|
60
59
|
- `hydrate_allocated_device_from_driver`
|
|
60
|
+
- `resolve_device_handle_from_driver`
|
|
61
|
+
- Supported public result types:
|
|
62
|
+
- `CooldownResult`
|
|
63
|
+
- `CooldownSetResult`
|
|
64
|
+
- `CooldownEscalatedResult`
|
|
61
65
|
- Supported public exceptions:
|
|
62
|
-
- `NoClaimableDevicesError`
|
|
63
66
|
- `UnknownIncludeError`
|
|
64
67
|
- `ReserveCapabilitiesUnsupportedError`
|
|
65
68
|
- Manual hardware examples under `testkit/examples/`
|
|
@@ -111,6 +114,7 @@ The package supports Python 3.10 and newer.
|
|
|
111
114
|
| `GRIDFLEET_TESTKIT_PASSWORD` | unset | Machine-auth password sent as HTTP Basic auth on every API call. Required when the manager runs with `GRIDFLEET_AUTH_ENABLED=true`. Use the same value as the manager's `GRIDFLEET_MACHINE_AUTH_PASSWORD`. |
|
|
112
115
|
| `GRIDFLEET_TESTKIT_PACK_ID` | unset | Optional default driver pack id for Appium option building |
|
|
113
116
|
| `GRIDFLEET_TESTKIT_PLATFORM_ID` | unset | Optional default platform id for Appium option building |
|
|
117
|
+
| `GRIDFLEET_RUN_ID` | `free` | Optional run id injected into Appium capabilities as `gridfleet:run_id`. The pytest plugin sets this automatically when sessions are created inside a reserved run. |
|
|
114
118
|
|
|
115
119
|
The package assumes a running GridFleet API, a reachable Selenium Grid hub, and platform-specific Appium driver setup on the registered hosts. When auth is disabled on the manager, leave `GRIDFLEET_TESTKIT_USERNAME` / `GRIDFLEET_TESTKIT_PASSWORD` unset and the testkit will send no `Authorization` header.
|
|
116
120
|
|
|
@@ -146,9 +150,11 @@ If you need raw Appium control instead, omit `pack_id` and `platform_id`, then p
|
|
|
146
150
|
|
|
147
151
|
- Creates an Appium session through `GRID_URL`
|
|
148
152
|
- Injects `gridfleet:testName` with the pytest test name
|
|
153
|
+
- Injects `gridfleet:run_id` when a `GRIDFLEET_RUN_ID` environment variable is present (for example, inside a reserved run)
|
|
149
154
|
- Reports final session status back to `GRIDFLEET_API_URL`
|
|
150
155
|
- Exposes `device_config` for post-session config lookup using the runtime connection target
|
|
151
156
|
- Exposes `device_test_data` for post-session operator-attached test data using the runtime connection target
|
|
157
|
+
- Exposes `device_handle` for fetching the canonical manager device row using the runtime connection target
|
|
152
158
|
- Exposes `gridfleet_worker_id` which returns the pytest-xdist worker id, or `"controller"` for non-worker processes
|
|
153
159
|
- Relies on manager-owned runtime isolation for Appium driver sub-ports and XCUITest build paths
|
|
154
160
|
|
|
@@ -183,40 +189,74 @@ finally:
|
|
|
183
189
|
| `GridFleetClient.list_devices(*, pack_id=None, status=None, host_id=None, ...)` | List devices using backend keyword filters (pack_id, platform_id, status, host_id, connection_target, tags, ...) |
|
|
184
190
|
| `GridFleetClient.get_device(device_id)` | Fetch one full device detail row by backend device id |
|
|
185
191
|
| `GridFleetClient.get_device_config(connection_target)` | Look up a device by runtime connection target and fetch its config |
|
|
192
|
+
| `GridFleetClient.get_device_by_connection_target(connection_target)` | Fetch one device detail row by runtime connection target |
|
|
186
193
|
| `GridFleetClient.get_device_capabilities(device_id)` | Fetch current Appium capability metadata for a device |
|
|
187
194
|
| `GridFleetClient.get_device_test_data(device_id)` | Fetch operator-attached free-form test_data for a device |
|
|
195
|
+
| `GridFleetClient.get_run(run_id)` | Fetch one run detail row by backend run id |
|
|
188
196
|
| `GridFleetClient.replace_device_test_data(device_id, body)` | Replace test_data with the supplied object |
|
|
189
197
|
| `GridFleetClient.merge_device_test_data(device_id, body)` | Deep-merge into device test_data |
|
|
190
198
|
| `GridFleetClient.resolve_device_id_by_connection_target(connection_target)` | Resolve the backend device id for a runtime connection target |
|
|
191
199
|
| `GridFleetClient.get_driver_pack_catalog()` | Fetch enabled driver-pack catalog data for Appium platform selection |
|
|
192
200
|
| `GridFleetClient.reserve_devices(...)` | Create a run/reservation and return the manager response |
|
|
193
|
-
| `GridFleetClient.
|
|
194
|
-
| `GridFleetClient.claim_device_with_retry(run_id, worker_id=..., max_wait_sec=300)` | Claim one reserved device, sleeping according to server `Retry-After` responses |
|
|
195
|
-
| `GridFleetClient.release_device(run_id, device_id=..., worker_id=...)` | Release a worker claim without cooldown |
|
|
196
|
-
| `GridFleetClient.release_device_safe(run_id, device_id=..., worker_id=...)` | Return `True` when release is accepted, `False` when the run is gone or the claim is already unclaimed, and raise on wrong-worker conflicts or unsafe errors |
|
|
197
|
-
| `GridFleetClient.release_device_with_cooldown(run_id, device_id=..., worker_id=..., reason=..., ttl_seconds=...)` | Release a worker claim and keep that run from reclaiming the device until cooldown expires |
|
|
198
|
-
| `GridFleetClient.signal_ready(run_id)` | Move a run to `ready` |
|
|
201
|
+
| `GridFleetClient.signal_ready(run_id)` | Signal that a run is ready |
|
|
199
202
|
| `GridFleetClient.signal_active(run_id)` | Move a run to `active` |
|
|
200
203
|
| `GridFleetClient.heartbeat(run_id)` | Send a run heartbeat and read current state |
|
|
201
204
|
| `GridFleetClient.report_preparation_failure(run_id, device_id, message, source="ci_preparation")` | Exclude one reserved device after setup fails |
|
|
202
205
|
| `GridFleetClient.register_session(fields)` | Register a Grid/Appium session with optional requested capability metadata |
|
|
203
206
|
| `GridFleetClient.register_session_from_driver(driver, fields)` | Extract session id and capabilities from an Appium driver and register the session |
|
|
207
|
+
| `GridFleetClient.notify_session_finished(session_id)` | Tell the manager the WebDriver session has ended |
|
|
204
208
|
| `GridFleetClient.update_session_status(session_id, status)` | Report final session status |
|
|
205
209
|
| `GridFleetClient.complete_run(run_id)` | Complete a run |
|
|
206
210
|
| `GridFleetClient.cancel_run(run_id)` | Cancel a run |
|
|
211
|
+
| `GridFleetClient.cooldown_device(run_id, device_id, reason=..., ttl_seconds=...)` | Exclude a reserved device from the run with a cooldown TTL |
|
|
207
212
|
| `GridFleetClient.start_heartbeat(run_id, interval=30)` | Start a background heartbeat thread |
|
|
208
213
|
| `build_error_session_payload(fields)` | Build a `/api/sessions` payload for driver-creation failures without importing pytest |
|
|
209
|
-
| `hydrate_allocated_device(
|
|
214
|
+
| `hydrate_allocated_device(device_handle, run_id, client)` | Combine a device handle with optional device config and live capabilities |
|
|
210
215
|
| `hydrate_allocated_device_from_driver(allocated, driver, client)` | Return a new allocated-device object with capabilities from a running driver |
|
|
216
|
+
| `resolve_device_handle_from_driver(driver, client)` | Resolve the assigned manager device row from a running Appium session |
|
|
211
217
|
| `get_device_test_data_for_driver(driver, gridfleet_client=None)` | Fetch test_data for a live Appium driver |
|
|
212
218
|
| `register_run_cleanup(client, run_id, heartbeat_thread=None)` | Register `atexit` cleanup callable and return it; stops the heartbeat thread on exit but does not complete or cancel the run by default |
|
|
213
219
|
|
|
220
|
+
### Targeting Devices by Tag
|
|
221
|
+
|
|
222
|
+
GridFleet injects device tags into Grid node stereotypes as `appium:gridfleet:tag:<key>` capabilities, so Selenium Grid can route sessions to devices matching specific tags.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
@pytest.mark.parametrize(
|
|
226
|
+
"appium_driver",
|
|
227
|
+
[
|
|
228
|
+
{
|
|
229
|
+
"pack_id": "appium-uiautomator2",
|
|
230
|
+
"platform_id": "android_mobile",
|
|
231
|
+
"appium:gridfleet:tag:screen_type": "4k",
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
indirect=True,
|
|
235
|
+
)
|
|
236
|
+
def test_4k_display(appium_driver):
|
|
237
|
+
...
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The same capability works for free sessions:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
driver = create_appium_driver(
|
|
244
|
+
pack_id="appium-uiautomator2",
|
|
245
|
+
platform_id="android_mobile",
|
|
246
|
+
capabilities={"appium:gridfleet:tag:screen_type": "4k"},
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
When an operator edits device tags, GridFleet marks the device for re-verification. The next verification restarts the Appium node and re-registers it with the updated Grid stereotype.
|
|
251
|
+
|
|
214
252
|
### Worker Identity
|
|
215
253
|
|
|
216
|
-
`worker_id` is an arbitrary string used for
|
|
254
|
+
`worker_id` is an arbitrary string used for reservation telemetry and run attribution. For pytest-xdist, pass `request.config.workerinput["workerid"]` from worker processes; values are normally `gw0`, `gw1`, and so on. For controller-only flows, use `"controller"` or a stable hostname. For custom schedulers, use a UUID or job-specific worker name.
|
|
217
255
|
|
|
218
256
|
### Reservation Flow
|
|
219
257
|
|
|
258
|
+
GridFleet runs are grid-routed: once devices are reserved, the manager tags matching Grid nodes with the run id, and Selenium Grid routes new Appium sessions to those nodes automatically via the `gridfleet:run_id` capability. There are no per-worker claim or release calls.
|
|
259
|
+
|
|
220
260
|
```python
|
|
221
261
|
from gridfleet_testkit import GridFleetClient, register_run_cleanup
|
|
222
262
|
|
|
@@ -258,53 +298,51 @@ client.signal_active(run_id)
|
|
|
258
298
|
|
|
259
299
|
Use `count` for exact reservations. Use `allocation: "all_available"` when CI should reserve every currently eligible matching device and size its worker pool from `len(run["devices"])`.
|
|
260
300
|
|
|
261
|
-
|
|
301
|
+
For pytest-xdist controller/worker orchestration, see [Testkit xdist recipe](../docs/guides/testkit-xdist-recipe.md). The recipe is copyable guidance, not a public testkit abstraction.
|
|
302
|
+
|
|
303
|
+
### Cooling Down an Unstable Device
|
|
304
|
+
|
|
305
|
+
If a reserved device becomes unstable during a test, you can put it on cooldown so it is excluded from the run for a TTL. If the same device is cooled down too many times in the same run, it is escalated to maintenance automatically.
|
|
262
306
|
|
|
263
307
|
```python
|
|
264
308
|
from gridfleet_testkit import GridFleetClient
|
|
265
309
|
|
|
266
|
-
client = GridFleetClient(
|
|
310
|
+
client = GridFleetClient()
|
|
267
311
|
|
|
268
|
-
|
|
269
|
-
|
|
312
|
+
result = client.cooldown_device(
|
|
313
|
+
run_id="run-123",
|
|
314
|
+
device_id="device-456",
|
|
315
|
+
reason="Connection dropped mid-test",
|
|
316
|
+
ttl_seconds=120,
|
|
317
|
+
)
|
|
270
318
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
client.release_device_with_cooldown(
|
|
276
|
-
run_id,
|
|
277
|
-
device_id=device_id,
|
|
278
|
-
worker_id="gw0",
|
|
279
|
-
reason=str(exc),
|
|
280
|
-
ttl_seconds=60,
|
|
281
|
-
)
|
|
282
|
-
raise
|
|
283
|
-
else:
|
|
284
|
-
client.release_device(run_id, device_id=device_id, worker_id="gw0")
|
|
319
|
+
if result["status"] == "cooldown_set":
|
|
320
|
+
print(f"Device on cooldown until {result['excluded_until']}")
|
|
321
|
+
elif result["status"] == "maintenance_escalated":
|
|
322
|
+
print(f"Escalated after {result['cooldown_count']} cooldowns")
|
|
285
323
|
```
|
|
286
324
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
For pytest-xdist controller/worker orchestration, see [Testkit xdist recipe](../docs/guides/testkit-xdist-recipe.md). The recipe is copyable guidance, not a public testkit abstraction.
|
|
325
|
+
The manager enforces a maximum TTL via the `general.device_cooldown_max_sec` setting. The default is 3600 seconds. An `httpx.HTTPStatusError` with status 422 is raised if `ttl_seconds` exceeds the maximum.
|
|
290
326
|
|
|
291
327
|
### Allocated Device Hydration
|
|
292
328
|
|
|
293
|
-
Use `hydrate_allocated_device(
|
|
329
|
+
Use `hydrate_allocated_device(...)` immediately after reserving a run when a custom plugin needs a stable object instead of raw device-handle JSON.
|
|
294
330
|
|
|
295
331
|
```python
|
|
296
|
-
from gridfleet_testkit import GridFleetClient, hydrate_allocated_device
|
|
332
|
+
from gridfleet_testkit import GridFleetClient, hydrate_allocated_device, resolve_device_handle_from_driver
|
|
297
333
|
|
|
298
|
-
client = GridFleetClient(
|
|
334
|
+
client = GridFleetClient()
|
|
299
335
|
run_id = "run-123"
|
|
300
|
-
claim = client.claim_device_with_retry(run_id, worker_id="gw0", max_wait_sec=300)
|
|
301
|
-
allocated = hydrate_allocated_device(claim, run_id=run_id, client=client)
|
|
302
336
|
|
|
303
|
-
|
|
337
|
+
# After creating an Appium session, resolve the assigned device handle
|
|
338
|
+
device_handle = resolve_device_handle_from_driver(driver, client=client)
|
|
339
|
+
allocated = hydrate_allocated_device(device_handle, run_id=run_id, client=client)
|
|
340
|
+
|
|
341
|
+
assert allocated.device_id == device_handle["device_id"]
|
|
304
342
|
assert allocated.platform_name in {"Android", "iOS", "tvOS", "Roku"}
|
|
305
343
|
```
|
|
306
344
|
|
|
307
|
-
The helper fetches static device config by default when `connection_target` is present. It fetches live capabilities only when `fetch_capabilities=True`. Pass `fetch_test_data=True` to also populate `allocated.test_data`. The `test_data` field is also available directly from the
|
|
345
|
+
The helper fetches static device config by default when `connection_target` is present. It fetches live capabilities only when `fetch_capabilities=True`. Pass `fetch_test_data=True` to also populate `allocated.test_data`. The `test_data` field is also available directly from the reserve response when the manager inlines it.
|
|
308
346
|
|
|
309
347
|
### Run Cleanup Policy
|
|
310
348
|
|
|
@@ -335,29 +373,33 @@ test_data = get_device_test_data_for_driver(driver)
|
|
|
335
373
|
|
|
336
374
|
### Errors and Result Types
|
|
337
375
|
|
|
338
|
-
- `NoClaimableDevicesError(RuntimeError)`: raised when the manager reports no run devices are claimable yet. Exposes `run_id`, `retry_after_sec`, and `next_available_at`. The `RuntimeError` base is part of the contract — consumers can rely on it.
|
|
339
376
|
- `UnknownIncludeError(ValueError)`: raised when the backend rejects one or more `?include=` keys. Exposes `values` with the rejected key names. The `ValueError` base is part of the contract.
|
|
340
377
|
- `ReserveCapabilitiesUnsupportedError(ValueError)`: raised when a reserve-time `include` request contains `"capabilities"`, which is not supported at reserve time. The `ValueError` base is part of the contract.
|
|
341
|
-
- `CooldownResult`: union response type from `
|
|
378
|
+
- `CooldownResult`: union response type from `cooldown_device`, with `status` equal to `"cooldown_set"` or `"maintenance_escalated"`. `CooldownSetResult` and `CooldownEscalatedResult` are the concrete TypedDict variants.
|
|
342
379
|
|
|
343
|
-
### Reduced HTTP
|
|
380
|
+
### Reduced HTTP Round-trips on Reserve
|
|
344
381
|
|
|
345
|
-
The manager can inline device config and live capabilities into the
|
|
382
|
+
The manager can inline device config and live capabilities into the `reserve_devices` response, eliminating per-worker follow-up GETs.
|
|
346
383
|
|
|
347
384
|
```python
|
|
348
385
|
client = GridFleetClient()
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
386
|
+
run = client.reserve_devices(
|
|
387
|
+
name="my-test-run",
|
|
388
|
+
requirements=[...],
|
|
389
|
+
include=("config", "capabilities"),
|
|
390
|
+
)
|
|
391
|
+
for device_handle in run["devices"]:
|
|
392
|
+
allocated = hydrate_allocated_device(device_handle, run_id=run["id"], client=client)
|
|
393
|
+
# zero follow-up GETs; allocated.config / allocated.live_capabilities populated inline
|
|
352
394
|
```
|
|
353
395
|
|
|
354
396
|
`device_config` and inline `config` payloads are returned verbatim from the manager. The testkit does not perform client-side secret masking or reveal toggles. Protect device config with manager authentication, operator access control, and your lab's secret-handling policy.
|
|
355
397
|
|
|
356
|
-
`reserve_devices` accepts `include=("config",)` only — `include=("capabilities",)` raises `ReserveCapabilitiesUnsupportedError` client-side because reserve-time capabilities are not yet device-bound. Pass `include=` on the per-worker `
|
|
398
|
+
`reserve_devices` accepts `include=("config",)` only — `include=("capabilities",)` raises `ReserveCapabilitiesUnsupportedError` client-side because reserve-time capabilities are not yet device-bound. Pass `include=` on the per-worker `hydrate_allocated_device` call instead if you need capabilities after sessions are running.
|
|
357
399
|
|
|
358
400
|
`include=` must be a sequence of strings (tuple or list) — order is preserved in the emitted query parameter. Passing a bare string like `include="config"` raises `TypeError` to avoid silently splitting the value into characters.
|
|
359
401
|
|
|
360
|
-
`hydrate_allocated_device` accepts
|
|
402
|
+
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries or rows returned by `get_device_by_connection_target`.
|
|
361
403
|
|
|
362
404
|
## Examples
|
|
363
405
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- Stable import root: `gridfleet_testkit`
|
|
8
8
|
- Supported pytest plugin: `gridfleet_testkit.pytest_plugin`
|
|
9
|
-
- Supported pytest fixtures: `appium_driver`, `gridfleet_client`, `device_config`, `device_test_data`, `gridfleet_worker_id`
|
|
9
|
+
- Supported pytest fixtures: `appium_driver`, `gridfleet_client`, `device_config`, `device_test_data`, `device_handle`, `gridfleet_worker_id`
|
|
10
10
|
- Supported public Appium helpers:
|
|
11
11
|
- `build_appium_options`
|
|
12
12
|
- `create_appium_driver`
|
|
@@ -20,12 +20,15 @@
|
|
|
20
20
|
- Supported public allocation/session helpers:
|
|
21
21
|
- `AllocatedDevice`
|
|
22
22
|
- `UnavailableInclude`
|
|
23
|
-
- `CooldownResult`
|
|
24
23
|
- `build_error_session_payload`
|
|
25
24
|
- `hydrate_allocated_device`
|
|
26
25
|
- `hydrate_allocated_device_from_driver`
|
|
26
|
+
- `resolve_device_handle_from_driver`
|
|
27
|
+
- Supported public result types:
|
|
28
|
+
- `CooldownResult`
|
|
29
|
+
- `CooldownSetResult`
|
|
30
|
+
- `CooldownEscalatedResult`
|
|
27
31
|
- Supported public exceptions:
|
|
28
|
-
- `NoClaimableDevicesError`
|
|
29
32
|
- `UnknownIncludeError`
|
|
30
33
|
- `ReserveCapabilitiesUnsupportedError`
|
|
31
34
|
- Manual hardware examples under `testkit/examples/`
|
|
@@ -77,6 +80,7 @@ The package supports Python 3.10 and newer.
|
|
|
77
80
|
| `GRIDFLEET_TESTKIT_PASSWORD` | unset | Machine-auth password sent as HTTP Basic auth on every API call. Required when the manager runs with `GRIDFLEET_AUTH_ENABLED=true`. Use the same value as the manager's `GRIDFLEET_MACHINE_AUTH_PASSWORD`. |
|
|
78
81
|
| `GRIDFLEET_TESTKIT_PACK_ID` | unset | Optional default driver pack id for Appium option building |
|
|
79
82
|
| `GRIDFLEET_TESTKIT_PLATFORM_ID` | unset | Optional default platform id for Appium option building |
|
|
83
|
+
| `GRIDFLEET_RUN_ID` | `free` | Optional run id injected into Appium capabilities as `gridfleet:run_id`. The pytest plugin sets this automatically when sessions are created inside a reserved run. |
|
|
80
84
|
|
|
81
85
|
The package assumes a running GridFleet API, a reachable Selenium Grid hub, and platform-specific Appium driver setup on the registered hosts. When auth is disabled on the manager, leave `GRIDFLEET_TESTKIT_USERNAME` / `GRIDFLEET_TESTKIT_PASSWORD` unset and the testkit will send no `Authorization` header.
|
|
82
86
|
|
|
@@ -112,9 +116,11 @@ If you need raw Appium control instead, omit `pack_id` and `platform_id`, then p
|
|
|
112
116
|
|
|
113
117
|
- Creates an Appium session through `GRID_URL`
|
|
114
118
|
- Injects `gridfleet:testName` with the pytest test name
|
|
119
|
+
- Injects `gridfleet:run_id` when a `GRIDFLEET_RUN_ID` environment variable is present (for example, inside a reserved run)
|
|
115
120
|
- Reports final session status back to `GRIDFLEET_API_URL`
|
|
116
121
|
- Exposes `device_config` for post-session config lookup using the runtime connection target
|
|
117
122
|
- Exposes `device_test_data` for post-session operator-attached test data using the runtime connection target
|
|
123
|
+
- Exposes `device_handle` for fetching the canonical manager device row using the runtime connection target
|
|
118
124
|
- Exposes `gridfleet_worker_id` which returns the pytest-xdist worker id, or `"controller"` for non-worker processes
|
|
119
125
|
- Relies on manager-owned runtime isolation for Appium driver sub-ports and XCUITest build paths
|
|
120
126
|
|
|
@@ -149,40 +155,74 @@ finally:
|
|
|
149
155
|
| `GridFleetClient.list_devices(*, pack_id=None, status=None, host_id=None, ...)` | List devices using backend keyword filters (pack_id, platform_id, status, host_id, connection_target, tags, ...) |
|
|
150
156
|
| `GridFleetClient.get_device(device_id)` | Fetch one full device detail row by backend device id |
|
|
151
157
|
| `GridFleetClient.get_device_config(connection_target)` | Look up a device by runtime connection target and fetch its config |
|
|
158
|
+
| `GridFleetClient.get_device_by_connection_target(connection_target)` | Fetch one device detail row by runtime connection target |
|
|
152
159
|
| `GridFleetClient.get_device_capabilities(device_id)` | Fetch current Appium capability metadata for a device |
|
|
153
160
|
| `GridFleetClient.get_device_test_data(device_id)` | Fetch operator-attached free-form test_data for a device |
|
|
161
|
+
| `GridFleetClient.get_run(run_id)` | Fetch one run detail row by backend run id |
|
|
154
162
|
| `GridFleetClient.replace_device_test_data(device_id, body)` | Replace test_data with the supplied object |
|
|
155
163
|
| `GridFleetClient.merge_device_test_data(device_id, body)` | Deep-merge into device test_data |
|
|
156
164
|
| `GridFleetClient.resolve_device_id_by_connection_target(connection_target)` | Resolve the backend device id for a runtime connection target |
|
|
157
165
|
| `GridFleetClient.get_driver_pack_catalog()` | Fetch enabled driver-pack catalog data for Appium platform selection |
|
|
158
166
|
| `GridFleetClient.reserve_devices(...)` | Create a run/reservation and return the manager response |
|
|
159
|
-
| `GridFleetClient.
|
|
160
|
-
| `GridFleetClient.claim_device_with_retry(run_id, worker_id=..., max_wait_sec=300)` | Claim one reserved device, sleeping according to server `Retry-After` responses |
|
|
161
|
-
| `GridFleetClient.release_device(run_id, device_id=..., worker_id=...)` | Release a worker claim without cooldown |
|
|
162
|
-
| `GridFleetClient.release_device_safe(run_id, device_id=..., worker_id=...)` | Return `True` when release is accepted, `False` when the run is gone or the claim is already unclaimed, and raise on wrong-worker conflicts or unsafe errors |
|
|
163
|
-
| `GridFleetClient.release_device_with_cooldown(run_id, device_id=..., worker_id=..., reason=..., ttl_seconds=...)` | Release a worker claim and keep that run from reclaiming the device until cooldown expires |
|
|
164
|
-
| `GridFleetClient.signal_ready(run_id)` | Move a run to `ready` |
|
|
167
|
+
| `GridFleetClient.signal_ready(run_id)` | Signal that a run is ready |
|
|
165
168
|
| `GridFleetClient.signal_active(run_id)` | Move a run to `active` |
|
|
166
169
|
| `GridFleetClient.heartbeat(run_id)` | Send a run heartbeat and read current state |
|
|
167
170
|
| `GridFleetClient.report_preparation_failure(run_id, device_id, message, source="ci_preparation")` | Exclude one reserved device after setup fails |
|
|
168
171
|
| `GridFleetClient.register_session(fields)` | Register a Grid/Appium session with optional requested capability metadata |
|
|
169
172
|
| `GridFleetClient.register_session_from_driver(driver, fields)` | Extract session id and capabilities from an Appium driver and register the session |
|
|
173
|
+
| `GridFleetClient.notify_session_finished(session_id)` | Tell the manager the WebDriver session has ended |
|
|
170
174
|
| `GridFleetClient.update_session_status(session_id, status)` | Report final session status |
|
|
171
175
|
| `GridFleetClient.complete_run(run_id)` | Complete a run |
|
|
172
176
|
| `GridFleetClient.cancel_run(run_id)` | Cancel a run |
|
|
177
|
+
| `GridFleetClient.cooldown_device(run_id, device_id, reason=..., ttl_seconds=...)` | Exclude a reserved device from the run with a cooldown TTL |
|
|
173
178
|
| `GridFleetClient.start_heartbeat(run_id, interval=30)` | Start a background heartbeat thread |
|
|
174
179
|
| `build_error_session_payload(fields)` | Build a `/api/sessions` payload for driver-creation failures without importing pytest |
|
|
175
|
-
| `hydrate_allocated_device(
|
|
180
|
+
| `hydrate_allocated_device(device_handle, run_id, client)` | Combine a device handle with optional device config and live capabilities |
|
|
176
181
|
| `hydrate_allocated_device_from_driver(allocated, driver, client)` | Return a new allocated-device object with capabilities from a running driver |
|
|
182
|
+
| `resolve_device_handle_from_driver(driver, client)` | Resolve the assigned manager device row from a running Appium session |
|
|
177
183
|
| `get_device_test_data_for_driver(driver, gridfleet_client=None)` | Fetch test_data for a live Appium driver |
|
|
178
184
|
| `register_run_cleanup(client, run_id, heartbeat_thread=None)` | Register `atexit` cleanup callable and return it; stops the heartbeat thread on exit but does not complete or cancel the run by default |
|
|
179
185
|
|
|
186
|
+
### Targeting Devices by Tag
|
|
187
|
+
|
|
188
|
+
GridFleet injects device tags into Grid node stereotypes as `appium:gridfleet:tag:<key>` capabilities, so Selenium Grid can route sessions to devices matching specific tags.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
@pytest.mark.parametrize(
|
|
192
|
+
"appium_driver",
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
"pack_id": "appium-uiautomator2",
|
|
196
|
+
"platform_id": "android_mobile",
|
|
197
|
+
"appium:gridfleet:tag:screen_type": "4k",
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
indirect=True,
|
|
201
|
+
)
|
|
202
|
+
def test_4k_display(appium_driver):
|
|
203
|
+
...
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The same capability works for free sessions:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
driver = create_appium_driver(
|
|
210
|
+
pack_id="appium-uiautomator2",
|
|
211
|
+
platform_id="android_mobile",
|
|
212
|
+
capabilities={"appium:gridfleet:tag:screen_type": "4k"},
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
When an operator edits device tags, GridFleet marks the device for re-verification. The next verification restarts the Appium node and re-registers it with the updated Grid stereotype.
|
|
217
|
+
|
|
180
218
|
### Worker Identity
|
|
181
219
|
|
|
182
|
-
`worker_id` is an arbitrary string used for
|
|
220
|
+
`worker_id` is an arbitrary string used for reservation telemetry and run attribution. For pytest-xdist, pass `request.config.workerinput["workerid"]` from worker processes; values are normally `gw0`, `gw1`, and so on. For controller-only flows, use `"controller"` or a stable hostname. For custom schedulers, use a UUID or job-specific worker name.
|
|
183
221
|
|
|
184
222
|
### Reservation Flow
|
|
185
223
|
|
|
224
|
+
GridFleet runs are grid-routed: once devices are reserved, the manager tags matching Grid nodes with the run id, and Selenium Grid routes new Appium sessions to those nodes automatically via the `gridfleet:run_id` capability. There are no per-worker claim or release calls.
|
|
225
|
+
|
|
186
226
|
```python
|
|
187
227
|
from gridfleet_testkit import GridFleetClient, register_run_cleanup
|
|
188
228
|
|
|
@@ -224,53 +264,51 @@ client.signal_active(run_id)
|
|
|
224
264
|
|
|
225
265
|
Use `count` for exact reservations. Use `allocation: "all_available"` when CI should reserve every currently eligible matching device and size its worker pool from `len(run["devices"])`.
|
|
226
266
|
|
|
227
|
-
|
|
267
|
+
For pytest-xdist controller/worker orchestration, see [Testkit xdist recipe](../docs/guides/testkit-xdist-recipe.md). The recipe is copyable guidance, not a public testkit abstraction.
|
|
268
|
+
|
|
269
|
+
### Cooling Down an Unstable Device
|
|
270
|
+
|
|
271
|
+
If a reserved device becomes unstable during a test, you can put it on cooldown so it is excluded from the run for a TTL. If the same device is cooled down too many times in the same run, it is escalated to maintenance automatically.
|
|
228
272
|
|
|
229
273
|
```python
|
|
230
274
|
from gridfleet_testkit import GridFleetClient
|
|
231
275
|
|
|
232
|
-
client = GridFleetClient(
|
|
276
|
+
client = GridFleetClient()
|
|
233
277
|
|
|
234
|
-
|
|
235
|
-
|
|
278
|
+
result = client.cooldown_device(
|
|
279
|
+
run_id="run-123",
|
|
280
|
+
device_id="device-456",
|
|
281
|
+
reason="Connection dropped mid-test",
|
|
282
|
+
ttl_seconds=120,
|
|
283
|
+
)
|
|
236
284
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
client.release_device_with_cooldown(
|
|
242
|
-
run_id,
|
|
243
|
-
device_id=device_id,
|
|
244
|
-
worker_id="gw0",
|
|
245
|
-
reason=str(exc),
|
|
246
|
-
ttl_seconds=60,
|
|
247
|
-
)
|
|
248
|
-
raise
|
|
249
|
-
else:
|
|
250
|
-
client.release_device(run_id, device_id=device_id, worker_id="gw0")
|
|
285
|
+
if result["status"] == "cooldown_set":
|
|
286
|
+
print(f"Device on cooldown until {result['excluded_until']}")
|
|
287
|
+
elif result["status"] == "maintenance_escalated":
|
|
288
|
+
print(f"Escalated after {result['cooldown_count']} cooldowns")
|
|
251
289
|
```
|
|
252
290
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
For pytest-xdist controller/worker orchestration, see [Testkit xdist recipe](../docs/guides/testkit-xdist-recipe.md). The recipe is copyable guidance, not a public testkit abstraction.
|
|
291
|
+
The manager enforces a maximum TTL via the `general.device_cooldown_max_sec` setting. The default is 3600 seconds. An `httpx.HTTPStatusError` with status 422 is raised if `ttl_seconds` exceeds the maximum.
|
|
256
292
|
|
|
257
293
|
### Allocated Device Hydration
|
|
258
294
|
|
|
259
|
-
Use `hydrate_allocated_device(
|
|
295
|
+
Use `hydrate_allocated_device(...)` immediately after reserving a run when a custom plugin needs a stable object instead of raw device-handle JSON.
|
|
260
296
|
|
|
261
297
|
```python
|
|
262
|
-
from gridfleet_testkit import GridFleetClient, hydrate_allocated_device
|
|
298
|
+
from gridfleet_testkit import GridFleetClient, hydrate_allocated_device, resolve_device_handle_from_driver
|
|
263
299
|
|
|
264
|
-
client = GridFleetClient(
|
|
300
|
+
client = GridFleetClient()
|
|
265
301
|
run_id = "run-123"
|
|
266
|
-
claim = client.claim_device_with_retry(run_id, worker_id="gw0", max_wait_sec=300)
|
|
267
|
-
allocated = hydrate_allocated_device(claim, run_id=run_id, client=client)
|
|
268
302
|
|
|
269
|
-
|
|
303
|
+
# After creating an Appium session, resolve the assigned device handle
|
|
304
|
+
device_handle = resolve_device_handle_from_driver(driver, client=client)
|
|
305
|
+
allocated = hydrate_allocated_device(device_handle, run_id=run_id, client=client)
|
|
306
|
+
|
|
307
|
+
assert allocated.device_id == device_handle["device_id"]
|
|
270
308
|
assert allocated.platform_name in {"Android", "iOS", "tvOS", "Roku"}
|
|
271
309
|
```
|
|
272
310
|
|
|
273
|
-
The helper fetches static device config by default when `connection_target` is present. It fetches live capabilities only when `fetch_capabilities=True`. Pass `fetch_test_data=True` to also populate `allocated.test_data`. The `test_data` field is also available directly from the
|
|
311
|
+
The helper fetches static device config by default when `connection_target` is present. It fetches live capabilities only when `fetch_capabilities=True`. Pass `fetch_test_data=True` to also populate `allocated.test_data`. The `test_data` field is also available directly from the reserve response when the manager inlines it.
|
|
274
312
|
|
|
275
313
|
### Run Cleanup Policy
|
|
276
314
|
|
|
@@ -301,29 +339,33 @@ test_data = get_device_test_data_for_driver(driver)
|
|
|
301
339
|
|
|
302
340
|
### Errors and Result Types
|
|
303
341
|
|
|
304
|
-
- `NoClaimableDevicesError(RuntimeError)`: raised when the manager reports no run devices are claimable yet. Exposes `run_id`, `retry_after_sec`, and `next_available_at`. The `RuntimeError` base is part of the contract — consumers can rely on it.
|
|
305
342
|
- `UnknownIncludeError(ValueError)`: raised when the backend rejects one or more `?include=` keys. Exposes `values` with the rejected key names. The `ValueError` base is part of the contract.
|
|
306
343
|
- `ReserveCapabilitiesUnsupportedError(ValueError)`: raised when a reserve-time `include` request contains `"capabilities"`, which is not supported at reserve time. The `ValueError` base is part of the contract.
|
|
307
|
-
- `CooldownResult`: union response type from `
|
|
344
|
+
- `CooldownResult`: union response type from `cooldown_device`, with `status` equal to `"cooldown_set"` or `"maintenance_escalated"`. `CooldownSetResult` and `CooldownEscalatedResult` are the concrete TypedDict variants.
|
|
308
345
|
|
|
309
|
-
### Reduced HTTP
|
|
346
|
+
### Reduced HTTP Round-trips on Reserve
|
|
310
347
|
|
|
311
|
-
The manager can inline device config and live capabilities into the
|
|
348
|
+
The manager can inline device config and live capabilities into the `reserve_devices` response, eliminating per-worker follow-up GETs.
|
|
312
349
|
|
|
313
350
|
```python
|
|
314
351
|
client = GridFleetClient()
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
352
|
+
run = client.reserve_devices(
|
|
353
|
+
name="my-test-run",
|
|
354
|
+
requirements=[...],
|
|
355
|
+
include=("config", "capabilities"),
|
|
356
|
+
)
|
|
357
|
+
for device_handle in run["devices"]:
|
|
358
|
+
allocated = hydrate_allocated_device(device_handle, run_id=run["id"], client=client)
|
|
359
|
+
# zero follow-up GETs; allocated.config / allocated.live_capabilities populated inline
|
|
318
360
|
```
|
|
319
361
|
|
|
320
362
|
`device_config` and inline `config` payloads are returned verbatim from the manager. The testkit does not perform client-side secret masking or reveal toggles. Protect device config with manager authentication, operator access control, and your lab's secret-handling policy.
|
|
321
363
|
|
|
322
|
-
`reserve_devices` accepts `include=("config",)` only — `include=("capabilities",)` raises `ReserveCapabilitiesUnsupportedError` client-side because reserve-time capabilities are not yet device-bound. Pass `include=` on the per-worker `
|
|
364
|
+
`reserve_devices` accepts `include=("config",)` only — `include=("capabilities",)` raises `ReserveCapabilitiesUnsupportedError` client-side because reserve-time capabilities are not yet device-bound. Pass `include=` on the per-worker `hydrate_allocated_device` call instead if you need capabilities after sessions are running.
|
|
323
365
|
|
|
324
366
|
`include=` must be a sequence of strings (tuple or list) — order is preserved in the emitted query parameter. Passing a bare string like `include="config"` raises `TypeError` to avoid silently splitting the value into characters.
|
|
325
367
|
|
|
326
|
-
`hydrate_allocated_device` accepts
|
|
368
|
+
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries or rows returned by `get_device_by_connection_target`.
|
|
327
369
|
|
|
328
370
|
## Examples
|
|
329
371
|
|
|
@@ -31,6 +31,9 @@ from .appium import (
|
|
|
31
31
|
get_device_test_data_for_driver,
|
|
32
32
|
)
|
|
33
33
|
from .client import (
|
|
34
|
+
CooldownEscalatedResult,
|
|
35
|
+
CooldownResult,
|
|
36
|
+
CooldownSetResult,
|
|
34
37
|
GridFleetClient,
|
|
35
38
|
HeartbeatThread,
|
|
36
39
|
ReserveCapabilitiesUnsupportedError,
|
|
@@ -44,12 +47,15 @@ from .sessions import build_error_session_payload, resolve_device_handle_from_dr
|
|
|
44
47
|
try:
|
|
45
48
|
__version__ = version("gridfleet-testkit")
|
|
46
49
|
except PackageNotFoundError:
|
|
47
|
-
__version__ = "0.
|
|
50
|
+
__version__ = "0.9.0"
|
|
48
51
|
|
|
49
52
|
__all__ = [
|
|
50
53
|
"GRIDFLEET_API_URL",
|
|
51
54
|
"GRID_URL",
|
|
52
55
|
"AllocatedDevice",
|
|
56
|
+
"CooldownEscalatedResult",
|
|
57
|
+
"CooldownResult",
|
|
58
|
+
"CooldownSetResult",
|
|
53
59
|
"GridFleetClient",
|
|
54
60
|
"HeartbeatThread",
|
|
55
61
|
"ReserveCapabilitiesUnsupportedError",
|
|
@@ -39,6 +39,7 @@ class AllocatedDevice:
|
|
|
39
39
|
live_capabilities: dict[str, Any] | None
|
|
40
40
|
test_data: dict[str, Any] | None = None
|
|
41
41
|
unavailable_includes: tuple[UnavailableInclude, ...] = ()
|
|
42
|
+
tags: dict[str, str] | None = None
|
|
42
43
|
|
|
43
44
|
@property
|
|
44
45
|
def is_real_device(self) -> bool:
|
|
@@ -153,6 +154,11 @@ def hydrate_allocated_device(
|
|
|
153
154
|
test_data = client.get_device_test_data(device_id)
|
|
154
155
|
else:
|
|
155
156
|
test_data = None
|
|
157
|
+
inline_tags = payload.get("tags")
|
|
158
|
+
if isinstance(inline_tags, dict):
|
|
159
|
+
tags: dict[str, str] | None = inline_tags
|
|
160
|
+
else:
|
|
161
|
+
tags = None
|
|
156
162
|
|
|
157
163
|
return AllocatedDevice(
|
|
158
164
|
run_id=run_id,
|
|
@@ -173,6 +179,7 @@ def hydrate_allocated_device(
|
|
|
173
179
|
live_capabilities=live_capabilities,
|
|
174
180
|
test_data=test_data,
|
|
175
181
|
unavailable_includes=unavailable_includes,
|
|
182
|
+
tags=tags,
|
|
176
183
|
)
|
|
177
184
|
|
|
178
185
|
|