gridfleet-testkit 0.9.4__tar.gz → 0.10.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.9.4 → gridfleet_testkit-0.10.0}/.gitignore +10 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/CHANGELOG.md +38 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/PKG-INFO +15 -15
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/README.md +14 -13
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_android_mobile_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_android_tv_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_firetv_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_ios_simulator_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_roku_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_roku_sideload_screenshot.py +1 -1
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_tvos_screenshot.py +2 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/__init__.py +4 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/appium.py +15 -4
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/client.py +11 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/pytest_plugin.py +3 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/pyproject.toml +1 -2
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_appium.py +0 -1
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_pytest_plugin.py +0 -1
- gridfleet_testkit-0.10.0/tests/test_run_scoped_url.py +131 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/uv.lock +22 -24
- gridfleet_testkit-0.9.4/tests/test_pytest_plugin_grid_run_id_injection.py +0 -29
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/__init__.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/_example_helpers.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/assets/hello-world.zip +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/allocation.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/py.typed +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/sessions.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/gridfleet_testkit/types.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_allocation.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_client.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_client_test_data.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_docs_contract.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_driver_agnostic_guard.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_package_metadata.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_pytest_plugin_test_data.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_sessions.py +0 -0
- {gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_sessions_resolve_device_handle.py +0 -0
|
@@ -42,8 +42,6 @@ dist/
|
|
|
42
42
|
|
|
43
43
|
# Misc
|
|
44
44
|
*.log
|
|
45
|
-
.idea
|
|
46
|
-
testing/screenshots
|
|
47
45
|
frontend/test-results
|
|
48
46
|
|
|
49
47
|
# Worktrees
|
|
@@ -51,3 +49,13 @@ frontend/test-results
|
|
|
51
49
|
|
|
52
50
|
# Superpowers working docs
|
|
53
51
|
.superpowers/
|
|
52
|
+
docs/superpowers/
|
|
53
|
+
|
|
54
|
+
# Local tooling
|
|
55
|
+
.claude/
|
|
56
|
+
.repowise/
|
|
57
|
+
.mcp.json
|
|
58
|
+
graphify-out/
|
|
59
|
+
|
|
60
|
+
# Local-only device-state live test harness (not committed)
|
|
61
|
+
scripts/state-testing/
|
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the GridFleet testkit (`gridfleet-testkit` on PyPI) are documented here.
|
|
4
4
|
|
|
5
|
+
## [0.10.0](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.5...gridfleet-testkit-v0.10.0) (2026-06-07)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
* **testkit:** bind sessions to runs via the run-scoped grid url
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* **testkit:** bind sessions to runs via the run-scoped grid url ([da34f10](https://github.com/quidow/gridfleet/commit/da34f10f095ed74b2733c0746f11632039bd109e))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* **testkit:** route the pytest plugin driver fixture through the run-scoped url ([d976c8a](https://github.com/quidow/gridfleet/commit/d976c8afb7c10ade85b3b03fb1c7d40756e952ca))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Dependencies
|
|
23
|
+
|
|
24
|
+
* **deps:** bump ruff in /testkit in the python-dependencies group ([#513](https://github.com/quidow/gridfleet/issues/513)) ([f7e8fb9](https://github.com/quidow/gridfleet/commit/f7e8fb92da993155852e328dd2e8e174fe93bba7))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
* **docs:** sweep selenium grid references for router architecture ([d086bcb](https://github.com/quidow/gridfleet/commit/d086bcb7c1619fb21cdf5e59499ab8221b18a0e4))
|
|
30
|
+
|
|
31
|
+
## [0.9.5](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.4...gridfleet-testkit-v0.9.5) (2026-06-04)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Dependencies
|
|
35
|
+
|
|
36
|
+
* **deps:** bump ruff in /testkit in the python-dependencies group ([#428](https://github.com/quidow/gridfleet/issues/428)) ([06ba6a7](https://github.com/quidow/gridfleet/commit/06ba6a7936769768436499d7e34f1053f3bf4710))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Documentation
|
|
40
|
+
|
|
41
|
+
* **docs:** align all docs with the actual implementation state ([#499](https://github.com/quidow/gridfleet/issues/499)) ([1d7a4ea](https://github.com/quidow/gridfleet/commit/1d7a4ea2afafbd5872856a01a9f73792c9ce5f7f))
|
|
42
|
+
|
|
5
43
|
## [0.9.4](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.3...gridfleet-testkit-v0.9.4) (2026-05-24)
|
|
6
44
|
|
|
7
45
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gridfleet-testkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -27,7 +27,6 @@ Requires-Dist: httpx<1,>=0.27
|
|
|
27
27
|
Requires-Dist: pytest<10,>=9.0.3
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: mypy<3,>=1.20.2; extra == 'dev'
|
|
30
|
-
Requires-Dist: pytest<10,>=9.0.3; extra == 'dev'
|
|
31
30
|
Requires-Dist: ruff<1,>=0.15.12; extra == 'dev'
|
|
32
31
|
Description-Content-Type: text/markdown
|
|
33
32
|
|
|
@@ -69,7 +68,7 @@ Description-Content-Type: text/markdown
|
|
|
69
68
|
## What It Does Not Own
|
|
70
69
|
|
|
71
70
|
- Appium server installation or host-level driver setup
|
|
72
|
-
-
|
|
71
|
+
- WebDriver router lifecycle
|
|
73
72
|
- Device registration, verification, or readiness setup
|
|
74
73
|
- CI orchestration beyond the documented client helpers
|
|
75
74
|
|
|
@@ -101,22 +100,22 @@ From a Git checkout or VCS URL that contains this package:
|
|
|
101
100
|
uv pip install "git+https://github.com/<org>/<repo>.git#subdirectory=testkit"
|
|
102
101
|
```
|
|
103
102
|
|
|
104
|
-
The package supports Python 3.10
|
|
103
|
+
The package supports Python 3.10 through 3.14.
|
|
105
104
|
`Appium-Python-Client` is installed as a runtime dependency because the pytest fixtures create real Appium sessions.
|
|
106
105
|
|
|
107
106
|
## Environment
|
|
108
107
|
|
|
109
108
|
| Variable | Default | Meaning |
|
|
110
109
|
| --- | --- | --- |
|
|
111
|
-
| `GRID_URL` | `http://localhost:4444` |
|
|
110
|
+
| `GRID_URL` | `http://localhost:4444` | WebDriver router URL used by the pytest Appium fixture |
|
|
112
111
|
| `GRIDFLEET_API_URL` | `http://localhost:8000/api` | GridFleet API base used for session reporting, config lookup, run helpers, and driver-pack catalog lookup |
|
|
113
112
|
| `GRIDFLEET_TESTKIT_USERNAME` | unset | Machine-auth username 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_USERNAME`. |
|
|
114
113
|
| `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`. |
|
|
115
114
|
| `GRIDFLEET_TESTKIT_PACK_ID` | unset | Optional default driver pack id for Appium option building |
|
|
116
115
|
| `GRIDFLEET_TESTKIT_PLATFORM_ID` | unset | Optional default platform id for Appium option building |
|
|
117
|
-
| `GRIDFLEET_RUN_ID` |
|
|
116
|
+
| `GRIDFLEET_RUN_ID` | unset | Optional run id. When set, drivers are created through the run-scoped grid endpoint `GRID_URL/run/{id}` so sessions land only on devices reserved for the run. Unset = free session on unreserved devices. Set this in the environment that launches pytest (e.g. the run launcher or CI step); the testkit reads it but does not set it. |
|
|
118
117
|
|
|
119
|
-
The package assumes a running GridFleet API, a reachable
|
|
118
|
+
The package assumes a running GridFleet API, a reachable WebDriver router, 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.
|
|
120
119
|
|
|
121
120
|
## Pytest Plugin
|
|
122
121
|
|
|
@@ -150,7 +149,7 @@ If you need raw Appium control instead, omit `pack_id` and `platform_id`, then p
|
|
|
150
149
|
|
|
151
150
|
- Creates an Appium session through `GRID_URL`
|
|
152
151
|
- Injects `gridfleet:testName` with the pytest test name
|
|
153
|
-
-
|
|
152
|
+
- Resolves the WebDriver endpoint from `GRIDFLEET_RUN_ID`: run-scoped URL inside a reserved run, bare grid URL otherwise. No GridFleet identity is injected into capabilities.
|
|
154
153
|
- Reports final session status back to `GRIDFLEET_API_URL`
|
|
155
154
|
- Exposes `device_config` for post-session config lookup using the runtime connection target
|
|
156
155
|
- Exposes `device_test_data` for post-session operator-attached test data using the runtime connection target
|
|
@@ -219,7 +218,7 @@ finally:
|
|
|
219
218
|
|
|
220
219
|
### Targeting Devices by Tag
|
|
221
220
|
|
|
222
|
-
GridFleet injects device tags into
|
|
221
|
+
GridFleet injects device tags into node stereotypes as `appium:gridfleet:tag:<key>` capabilities, so the router's backend allocation can route sessions to devices matching specific tags.
|
|
223
222
|
|
|
224
223
|
```python
|
|
225
224
|
@pytest.mark.parametrize(
|
|
@@ -251,11 +250,11 @@ When an operator edits device tags, GridFleet marks the device for re-verificati
|
|
|
251
250
|
|
|
252
251
|
### Worker Identity
|
|
253
252
|
|
|
254
|
-
`
|
|
253
|
+
The `gridfleet_worker_id` fixture is informational only: it returns the pytest-xdist worker id (normally `gw0`, `gw1`, and so on), or `"controller"` for non-worker processes. It is never transmitted to the manager; use it client-side for local sharding or log correlation. For run attribution, pass the `created_by` argument to `GridFleetClient.reserve_devices` — that is the only run-attribution field the reservation request carries.
|
|
255
254
|
|
|
256
255
|
### Reservation Flow
|
|
257
256
|
|
|
258
|
-
GridFleet runs are
|
|
257
|
+
GridFleet runs are router-routed: once devices are reserved, the manager tags matching nodes with the run id, and the router routes new Appium sessions to those nodes automatically when they arrive through the run-scoped endpoint (`GRID_URL/run/{run_id}`). There are no per-worker claim or release calls.
|
|
259
258
|
|
|
260
259
|
```python
|
|
261
260
|
from gridfleet_testkit import GridFleetClient, register_run_cleanup
|
|
@@ -386,11 +385,12 @@ client = GridFleetClient()
|
|
|
386
385
|
run = client.reserve_devices(
|
|
387
386
|
name="my-test-run",
|
|
388
387
|
requirements=[...],
|
|
389
|
-
include=("config",
|
|
388
|
+
include=("config",),
|
|
390
389
|
)
|
|
391
390
|
for device_handle in run["devices"]:
|
|
392
391
|
allocated = hydrate_allocated_device(device_handle, run_id=run["id"], client=client)
|
|
393
|
-
#
|
|
392
|
+
# no config follow-up GET; allocated.config populated inline.
|
|
393
|
+
# For capabilities, pass include= on this per-worker hydrate_allocated_device call.
|
|
394
394
|
```
|
|
395
395
|
|
|
396
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.
|
|
@@ -399,7 +399,7 @@ for device_handle in run["devices"]:
|
|
|
399
399
|
|
|
400
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.
|
|
401
401
|
|
|
402
|
-
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries
|
|
402
|
+
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries, which carry a top-level `device_id`. A `get_device_by_connection_target` row keys its primary id as `id`, so it must have that field remapped to `device_id` before being passed in.
|
|
403
403
|
|
|
404
404
|
## Examples
|
|
405
405
|
|
|
@@ -418,7 +418,7 @@ Advanced example:
|
|
|
418
418
|
|
|
419
419
|
The baseline examples share the same flow:
|
|
420
420
|
|
|
421
|
-
1. Create a session through
|
|
421
|
+
1. Create a session through the WebDriver router
|
|
422
422
|
2. Print the resolved connection context
|
|
423
423
|
3. Save a screenshot
|
|
424
424
|
4. Assert that the screenshot file exists and is non-empty
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
## What It Does Not Own
|
|
37
37
|
|
|
38
38
|
- Appium server installation or host-level driver setup
|
|
39
|
-
-
|
|
39
|
+
- WebDriver router lifecycle
|
|
40
40
|
- Device registration, verification, or readiness setup
|
|
41
41
|
- CI orchestration beyond the documented client helpers
|
|
42
42
|
|
|
@@ -68,22 +68,22 @@ From a Git checkout or VCS URL that contains this package:
|
|
|
68
68
|
uv pip install "git+https://github.com/<org>/<repo>.git#subdirectory=testkit"
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The package supports Python 3.10
|
|
71
|
+
The package supports Python 3.10 through 3.14.
|
|
72
72
|
`Appium-Python-Client` is installed as a runtime dependency because the pytest fixtures create real Appium sessions.
|
|
73
73
|
|
|
74
74
|
## Environment
|
|
75
75
|
|
|
76
76
|
| Variable | Default | Meaning |
|
|
77
77
|
| --- | --- | --- |
|
|
78
|
-
| `GRID_URL` | `http://localhost:4444` |
|
|
78
|
+
| `GRID_URL` | `http://localhost:4444` | WebDriver router URL used by the pytest Appium fixture |
|
|
79
79
|
| `GRIDFLEET_API_URL` | `http://localhost:8000/api` | GridFleet API base used for session reporting, config lookup, run helpers, and driver-pack catalog lookup |
|
|
80
80
|
| `GRIDFLEET_TESTKIT_USERNAME` | unset | Machine-auth username 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_USERNAME`. |
|
|
81
81
|
| `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`. |
|
|
82
82
|
| `GRIDFLEET_TESTKIT_PACK_ID` | unset | Optional default driver pack id for Appium option building |
|
|
83
83
|
| `GRIDFLEET_TESTKIT_PLATFORM_ID` | unset | Optional default platform id for Appium option building |
|
|
84
|
-
| `GRIDFLEET_RUN_ID` |
|
|
84
|
+
| `GRIDFLEET_RUN_ID` | unset | Optional run id. When set, drivers are created through the run-scoped grid endpoint `GRID_URL/run/{id}` so sessions land only on devices reserved for the run. Unset = free session on unreserved devices. Set this in the environment that launches pytest (e.g. the run launcher or CI step); the testkit reads it but does not set it. |
|
|
85
85
|
|
|
86
|
-
The package assumes a running GridFleet API, a reachable
|
|
86
|
+
The package assumes a running GridFleet API, a reachable WebDriver router, 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.
|
|
87
87
|
|
|
88
88
|
## Pytest Plugin
|
|
89
89
|
|
|
@@ -117,7 +117,7 @@ If you need raw Appium control instead, omit `pack_id` and `platform_id`, then p
|
|
|
117
117
|
|
|
118
118
|
- Creates an Appium session through `GRID_URL`
|
|
119
119
|
- Injects `gridfleet:testName` with the pytest test name
|
|
120
|
-
-
|
|
120
|
+
- Resolves the WebDriver endpoint from `GRIDFLEET_RUN_ID`: run-scoped URL inside a reserved run, bare grid URL otherwise. No GridFleet identity is injected into capabilities.
|
|
121
121
|
- Reports final session status back to `GRIDFLEET_API_URL`
|
|
122
122
|
- Exposes `device_config` for post-session config lookup using the runtime connection target
|
|
123
123
|
- Exposes `device_test_data` for post-session operator-attached test data using the runtime connection target
|
|
@@ -186,7 +186,7 @@ finally:
|
|
|
186
186
|
|
|
187
187
|
### Targeting Devices by Tag
|
|
188
188
|
|
|
189
|
-
GridFleet injects device tags into
|
|
189
|
+
GridFleet injects device tags into node stereotypes as `appium:gridfleet:tag:<key>` capabilities, so the router's backend allocation can route sessions to devices matching specific tags.
|
|
190
190
|
|
|
191
191
|
```python
|
|
192
192
|
@pytest.mark.parametrize(
|
|
@@ -218,11 +218,11 @@ When an operator edits device tags, GridFleet marks the device for re-verificati
|
|
|
218
218
|
|
|
219
219
|
### Worker Identity
|
|
220
220
|
|
|
221
|
-
`
|
|
221
|
+
The `gridfleet_worker_id` fixture is informational only: it returns the pytest-xdist worker id (normally `gw0`, `gw1`, and so on), or `"controller"` for non-worker processes. It is never transmitted to the manager; use it client-side for local sharding or log correlation. For run attribution, pass the `created_by` argument to `GridFleetClient.reserve_devices` — that is the only run-attribution field the reservation request carries.
|
|
222
222
|
|
|
223
223
|
### Reservation Flow
|
|
224
224
|
|
|
225
|
-
GridFleet runs are
|
|
225
|
+
GridFleet runs are router-routed: once devices are reserved, the manager tags matching nodes with the run id, and the router routes new Appium sessions to those nodes automatically when they arrive through the run-scoped endpoint (`GRID_URL/run/{run_id}`). There are no per-worker claim or release calls.
|
|
226
226
|
|
|
227
227
|
```python
|
|
228
228
|
from gridfleet_testkit import GridFleetClient, register_run_cleanup
|
|
@@ -353,11 +353,12 @@ client = GridFleetClient()
|
|
|
353
353
|
run = client.reserve_devices(
|
|
354
354
|
name="my-test-run",
|
|
355
355
|
requirements=[...],
|
|
356
|
-
include=("config",
|
|
356
|
+
include=("config",),
|
|
357
357
|
)
|
|
358
358
|
for device_handle in run["devices"]:
|
|
359
359
|
allocated = hydrate_allocated_device(device_handle, run_id=run["id"], client=client)
|
|
360
|
-
#
|
|
360
|
+
# no config follow-up GET; allocated.config populated inline.
|
|
361
|
+
# For capabilities, pass include= on this per-worker hydrate_allocated_device call.
|
|
361
362
|
```
|
|
362
363
|
|
|
363
364
|
`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.
|
|
@@ -366,7 +367,7 @@ for device_handle in run["devices"]:
|
|
|
366
367
|
|
|
367
368
|
`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.
|
|
368
369
|
|
|
369
|
-
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries
|
|
370
|
+
`hydrate_allocated_device` accepts device-handle payloads such as `reserve_response["devices"]` entries, which carry a top-level `device_id`. A `get_device_by_connection_target` row keys its primary id as `id`, so it must have that field remapped to `device_id` before being passed in.
|
|
370
371
|
|
|
371
372
|
## Examples
|
|
372
373
|
|
|
@@ -385,7 +386,7 @@ Advanced example:
|
|
|
385
386
|
|
|
386
387
|
The baseline examples share the same flow:
|
|
387
388
|
|
|
388
|
-
1. Create a session through
|
|
389
|
+
1. Create a session through the WebDriver router
|
|
389
390
|
2. Print the resolved connection context
|
|
390
391
|
3. Save a screenshot
|
|
391
392
|
4. Assert that the screenshot file exists and is non-empty
|
{gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_android_mobile_screenshot.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to an Android mobile device through
|
|
2
|
+
Manual baseline example: connect to an Android mobile device through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- An Android mobile device registered and its Appium node running
|
|
7
7
|
- The supported GridFleet testkit installed
|
|
8
8
|
- Appium-Python-Client installed (`uv pip install -e ./testkit`)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to an Android TV device through
|
|
2
|
+
Manual baseline example: connect to an Android TV device through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- An Android TV device registered and its Appium node running
|
|
7
7
|
- The supported GridFleet testkit installed
|
|
8
8
|
- Appium-Python-Client installed (`uv pip install -e ./testkit`)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to a Fire TV device through
|
|
2
|
+
Manual baseline example: connect to a Fire TV device through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- A Fire TV device registered and its Appium node running
|
|
7
7
|
- The supported GridFleet testkit installed
|
|
8
8
|
- Appium-Python-Client installed (`uv pip install -e ./testkit`)
|
{gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_ios_simulator_screenshot.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to an iOS simulator through
|
|
2
|
+
Manual baseline example: connect to an iOS simulator through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- An iOS simulator registered and its Appium node running
|
|
7
7
|
- The supported GridFleet testkit installed
|
|
8
8
|
- Appium with the XCUITest driver installed (`appium driver install xcuitest`)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to a Roku device through
|
|
2
|
+
Manual baseline example: connect to a Roku device through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- A Roku device registered with Roku dev credentials in device config
|
|
7
7
|
- Appium with the Roku driver installed (`appium driver install roku`)
|
|
8
8
|
- The supported GridFleet testkit installed
|
{gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/examples/test_roku_sideload_screenshot.py
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Manual advanced example: connect to a Roku device, sideload a sample app, and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- A Roku device registered with Roku dev credentials in device config
|
|
7
7
|
- Appium with the Roku driver installed (`appium driver install roku`)
|
|
8
8
|
- The supported GridFleet testkit installed
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Manual baseline example: connect to a tvOS real device through
|
|
2
|
+
Manual baseline example: connect to a tvOS real device through the WebDriver router and take a screenshot.
|
|
3
3
|
|
|
4
4
|
Requires:
|
|
5
|
-
-
|
|
5
|
+
- WebDriver router running on localhost:4444
|
|
6
6
|
- A tvOS real device registered and its Appium node running
|
|
7
7
|
- The supported GridFleet testkit installed
|
|
8
8
|
- Appium with the XCUITest driver installed (`appium driver install xcuitest`)
|
|
@@ -6,7 +6,7 @@ the live Appium-side config can use `client.get_device_config(connection_target)
|
|
|
6
6
|
|
|
7
7
|
Environment variables read by the client:
|
|
8
8
|
|
|
9
|
-
- GRID_URL:
|
|
9
|
+
- GRID_URL: WebDriver router URL used by Appium helper defaults.
|
|
10
10
|
- GRIDFLEET_API_URL: GridFleet manager API base URL.
|
|
11
11
|
- GRIDFLEET_TESTKIT_USERNAME: optional Basic auth username.
|
|
12
12
|
- GRIDFLEET_TESTKIT_PASSWORD: optional Basic auth password.
|
|
@@ -41,13 +41,14 @@ from .client import (
|
|
|
41
41
|
_default_api_url,
|
|
42
42
|
_default_grid_url,
|
|
43
43
|
register_run_cleanup,
|
|
44
|
+
run_grid_url,
|
|
44
45
|
)
|
|
45
46
|
from .sessions import build_error_session_payload, resolve_device_handle_from_driver
|
|
46
47
|
|
|
47
48
|
try:
|
|
48
49
|
__version__ = version("gridfleet-testkit")
|
|
49
50
|
except PackageNotFoundError:
|
|
50
|
-
__version__ = "0.
|
|
51
|
+
__version__ = "0.10.0"
|
|
51
52
|
|
|
52
53
|
__all__ = [
|
|
53
54
|
"GRIDFLEET_API_URL",
|
|
@@ -72,6 +73,7 @@ __all__ = [
|
|
|
72
73
|
"hydrate_allocated_device_from_driver",
|
|
73
74
|
"register_run_cleanup",
|
|
74
75
|
"resolve_device_handle_from_driver",
|
|
76
|
+
"run_grid_url",
|
|
75
77
|
]
|
|
76
78
|
|
|
77
79
|
|
|
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, cast
|
|
|
8
8
|
from appium import webdriver
|
|
9
9
|
from appium.options.common import AppiumOptions
|
|
10
10
|
|
|
11
|
-
from .client import GridFleetClient, _default_grid_url
|
|
11
|
+
from .client import GridFleetClient, _default_grid_url, run_grid_url
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from collections.abc import Callable, Mapping
|
|
@@ -102,7 +102,6 @@ def build_appium_options(
|
|
|
102
102
|
) -> AppiumOptions:
|
|
103
103
|
"""Build Appium options from driver-pack catalog platform metadata."""
|
|
104
104
|
params = dict(capabilities or {})
|
|
105
|
-
params.setdefault("gridfleet:run_id", os.environ.get("GRIDFLEET_RUN_ID", "free"))
|
|
106
105
|
explicit_platform_name = params.get("platformName")
|
|
107
106
|
if explicit_platform_name is not None and (pack_id is not None or platform_id is not None):
|
|
108
107
|
raise ValueError("Use either pack_id/platform_id or the raw platformName capability, not both.")
|
|
@@ -126,6 +125,18 @@ def build_appium_options(
|
|
|
126
125
|
return options
|
|
127
126
|
|
|
128
127
|
|
|
128
|
+
def _resolve_grid_url(grid_url: str | None) -> str:
|
|
129
|
+
"""Executor resolution: explicit URL wins; GRIDFLEET_RUN_ID (set externally
|
|
130
|
+
by the run launcher or CI before pytest starts) composes the run-scoped
|
|
131
|
+
endpoint; otherwise the bare grid URL — an explicit free session."""
|
|
132
|
+
if grid_url is not None:
|
|
133
|
+
return grid_url
|
|
134
|
+
run_id = os.environ.get("GRIDFLEET_RUN_ID")
|
|
135
|
+
if run_id:
|
|
136
|
+
return run_grid_url(run_id)
|
|
137
|
+
return _default_grid_url()
|
|
138
|
+
|
|
139
|
+
|
|
129
140
|
def create_appium_driver(
|
|
130
141
|
*,
|
|
131
142
|
pack_id: str | None = None,
|
|
@@ -135,7 +146,7 @@ def create_appium_driver(
|
|
|
135
146
|
grid_url: str | None = None,
|
|
136
147
|
catalog_client: object | None = None,
|
|
137
148
|
) -> WebDriver:
|
|
138
|
-
"""Create an Appium remote driver through
|
|
149
|
+
"""Create an Appium remote driver through the WebDriver router."""
|
|
139
150
|
options = build_appium_options(
|
|
140
151
|
pack_id=pack_id,
|
|
141
152
|
platform_id=platform_id,
|
|
@@ -143,7 +154,7 @@ def create_appium_driver(
|
|
|
143
154
|
test_name=test_name,
|
|
144
155
|
catalog_client=catalog_client,
|
|
145
156
|
)
|
|
146
|
-
return webdriver.Remote(grid_url
|
|
157
|
+
return webdriver.Remote(_resolve_grid_url(grid_url), options=options)
|
|
147
158
|
|
|
148
159
|
|
|
149
160
|
def get_connection_target_from_driver(driver: WebDriver) -> str:
|
|
@@ -31,6 +31,17 @@ def _default_grid_url() -> str:
|
|
|
31
31
|
return os.getenv("GRID_URL", DEFAULT_GRID_URL)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def run_grid_url(run_id: str, *, base: str | None = None) -> str:
|
|
35
|
+
"""Run-scoped WebDriver endpoint for *run_id* (``{base}/run/{run_id}``).
|
|
36
|
+
|
|
37
|
+
Sessions created through it are admitted only to devices reserved for the
|
|
38
|
+
run; free sessions use the bare grid URL. Replaces the retired
|
|
39
|
+
``gridfleet:run_id`` capability.
|
|
40
|
+
"""
|
|
41
|
+
root = (base or _default_grid_url()).rstrip("/")
|
|
42
|
+
return f"{root}/run/{run_id}"
|
|
43
|
+
|
|
44
|
+
|
|
34
45
|
def _default_api_url() -> str:
|
|
35
46
|
return os.getenv("GRIDFLEET_API_URL", DEFAULT_GRIDFLEET_API_URL)
|
|
36
47
|
|
|
@@ -9,6 +9,7 @@ import pytest
|
|
|
9
9
|
from appium import webdriver
|
|
10
10
|
|
|
11
11
|
from .appium import (
|
|
12
|
+
_resolve_grid_url,
|
|
12
13
|
build_appium_options,
|
|
13
14
|
get_device_config_for_driver,
|
|
14
15
|
get_device_test_data_for_driver,
|
|
@@ -72,7 +73,7 @@ def appium_driver(
|
|
|
72
73
|
gridfleet_client: GridFleetClient,
|
|
73
74
|
) -> Generator[WebDriver, None, None]:
|
|
74
75
|
"""
|
|
75
|
-
Create an Appium Remote driver through the
|
|
76
|
+
Create an Appium Remote driver through the WebDriver router.
|
|
76
77
|
|
|
77
78
|
Parametrize with a dict of pack/catalog selection plus capabilities:
|
|
78
79
|
@pytest.mark.parametrize(
|
|
@@ -84,7 +85,7 @@ def appium_driver(
|
|
|
84
85
|
options = _build_driver_options(request, gridfleet_client)
|
|
85
86
|
|
|
86
87
|
try:
|
|
87
|
-
driver = webdriver.Remote(
|
|
88
|
+
driver = webdriver.Remote(_resolve_grid_url(None), options=options)
|
|
88
89
|
except Exception as exc:
|
|
89
90
|
# Driver creation failed before a Grid session was established (e.g.
|
|
90
91
|
# SessionNotCreatedException). Register a device-less error session so the
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gridfleet-testkit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
8
8
|
description = "Supported pytest and run-orchestration helpers for GridFleet integrations"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -39,7 +39,6 @@ Security = "https://github.com/quidow/gridfleet/security/advisories/new"
|
|
|
39
39
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
41
|
dev = [
|
|
42
|
-
"pytest>=9.0.3,<10",
|
|
43
42
|
"mypy>=1.20.2,<3",
|
|
44
43
|
"ruff>=0.15.12,<1",
|
|
45
44
|
]
|
|
@@ -166,7 +166,6 @@ def test_create_appium_driver_uses_factory_options(monkeypatch: pytest.MonkeyPat
|
|
|
166
166
|
"platformName": "Android",
|
|
167
167
|
"appium:platform": "firetv_real",
|
|
168
168
|
"appium:automationName": "UiAutomator2",
|
|
169
|
-
"gridfleet:run_id": "free",
|
|
170
169
|
"gridfleet:testName": "manual-smoke",
|
|
171
170
|
},
|
|
172
171
|
)
|
|
@@ -286,7 +286,6 @@ def test_appium_driver_setup_failure_registers_device_less_error_session(monkeyp
|
|
|
286
286
|
"appium:connection_type": "network",
|
|
287
287
|
"appium:appPackage": "io.appium.android.apis",
|
|
288
288
|
"platformName": "Android",
|
|
289
|
-
"gridfleet:run_id": "free",
|
|
290
289
|
"gridfleet:testName": "test_broken",
|
|
291
290
|
"appium:platform": "android_mobile",
|
|
292
291
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import types
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import gridfleet_testkit.appium as appium_mod
|
|
7
|
+
from gridfleet_testkit import pytest_plugin, run_grid_url
|
|
8
|
+
from gridfleet_testkit.appium import _resolve_grid_url, build_appium_options
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
RID = "0c8c057f-3ec1-4b9c-9d2e-9f3a86a2c001"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_run_grid_url_composes_from_explicit_base() -> None:
|
|
19
|
+
assert run_grid_url(RID, base="http://router:4444/") == f"http://router:4444/run/{RID}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_run_grid_url_defaults_base_from_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
23
|
+
monkeypatch.setenv("GRID_URL", "http://lab-router:4444")
|
|
24
|
+
assert run_grid_url(RID) == f"http://lab-router:4444/run/{RID}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_resolve_grid_url_explicit_wins(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
28
|
+
monkeypatch.setenv("GRIDFLEET_RUN_ID", RID)
|
|
29
|
+
assert _resolve_grid_url("http://explicit:4444") == "http://explicit:4444"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_resolve_grid_url_uses_run_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
33
|
+
monkeypatch.setenv("GRID_URL", "http://router:4444")
|
|
34
|
+
monkeypatch.setenv("GRIDFLEET_RUN_ID", RID)
|
|
35
|
+
assert _resolve_grid_url(None) == f"http://router:4444/run/{RID}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_resolve_grid_url_unset_env_is_free_session(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
39
|
+
monkeypatch.setenv("GRID_URL", "http://router:4444")
|
|
40
|
+
monkeypatch.delenv("GRIDFLEET_RUN_ID", raising=False)
|
|
41
|
+
assert _resolve_grid_url(None) == "http://router:4444"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_no_run_id_capability_injected(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
45
|
+
"""The cap-era contract is dead: no gridfleet:run_id regardless of env."""
|
|
46
|
+
monkeypatch.setenv("GRIDFLEET_RUN_ID", RID)
|
|
47
|
+
options = build_appium_options(capabilities={"platformName": "Android"})
|
|
48
|
+
assert "gridfleet:run_id" not in dict(options.to_capabilities())
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# --- pytest plugin fixture URL routing ---
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _FakeOptions:
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self.platform_name: str | None = None
|
|
57
|
+
self.capabilities: dict[str, object] = {}
|
|
58
|
+
|
|
59
|
+
def set_capability(self, key: str, value: object) -> None:
|
|
60
|
+
self.capabilities[key] = value
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _FakeDriver:
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self.session_id = "sess-x"
|
|
66
|
+
self.capabilities: dict[str, object] = {}
|
|
67
|
+
|
|
68
|
+
def quit(self) -> None:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _FakeClient:
|
|
73
|
+
def get_driver_pack_catalog(self) -> dict[str, object]:
|
|
74
|
+
return {"packs": []}
|
|
75
|
+
|
|
76
|
+
def register_session_from_driver(self, driver: object, **kwargs: object) -> dict[str, object]:
|
|
77
|
+
return {"ok": True}
|
|
78
|
+
|
|
79
|
+
def register_session(self, **kwargs: object) -> dict[str, object]:
|
|
80
|
+
return {"ok": True}
|
|
81
|
+
|
|
82
|
+
def update_session_status(self, session_id: str, status: str, *, suppress_errors: bool = True) -> dict[str, object]:
|
|
83
|
+
return {"ok": True}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _make_plugin_generator(monkeypatch: pytest.MonkeyPatch) -> tuple[list[tuple[str, object]], Iterator[object]]:
|
|
87
|
+
"""Return (captured_calls, generator) after installing a minimal webdriver.Remote spy."""
|
|
88
|
+
captured: list[tuple[str, object]] = []
|
|
89
|
+
|
|
90
|
+
def fake_remote(url: str, *, options: object) -> _FakeDriver:
|
|
91
|
+
captured.append((url, options))
|
|
92
|
+
return _FakeDriver()
|
|
93
|
+
|
|
94
|
+
monkeypatch.setattr(appium_mod, "AppiumOptions", _FakeOptions)
|
|
95
|
+
# String target: `pytest_plugin.webdriver` is a transitive module attribute
|
|
96
|
+
# mypy strict (no_implicit_reexport) refuses to access statically.
|
|
97
|
+
monkeypatch.setattr("gridfleet_testkit.pytest_plugin.webdriver.Remote", fake_remote)
|
|
98
|
+
|
|
99
|
+
request = types.SimpleNamespace(
|
|
100
|
+
param={"platformName": "Android"},
|
|
101
|
+
node=types.SimpleNamespace(name="test_plugin_url"),
|
|
102
|
+
)
|
|
103
|
+
# pytest wraps fixtures in FixtureFunctionDefinition; __wrapped__ exists at
|
|
104
|
+
# runtime but not in pytest's stubs, so reach it via getattr.
|
|
105
|
+
fixture_fn = getattr(pytest_plugin.appium_driver, "__wrapped__") # noqa: B009
|
|
106
|
+
gen: Iterator[object] = fixture_fn(request, _FakeClient())
|
|
107
|
+
return captured, gen
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_plugin_fixture_uses_run_scoped_url_when_run_id_set(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
111
|
+
"""When GRIDFLEET_RUN_ID is set, the plugin driver connects to GRID_URL/run/{id}."""
|
|
112
|
+
monkeypatch.setenv("GRID_URL", "http://router:4444")
|
|
113
|
+
monkeypatch.setenv("GRIDFLEET_RUN_ID", RID)
|
|
114
|
+
|
|
115
|
+
captured, gen = _make_plugin_generator(monkeypatch)
|
|
116
|
+
next(gen)
|
|
117
|
+
|
|
118
|
+
assert len(captured) == 1
|
|
119
|
+
assert captured[0][0] == f"http://router:4444/run/{RID}"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_plugin_fixture_uses_bare_url_when_run_id_unset(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
123
|
+
"""When GRIDFLEET_RUN_ID is absent, the plugin driver connects to the bare GRID_URL."""
|
|
124
|
+
monkeypatch.setenv("GRID_URL", "http://router:4444")
|
|
125
|
+
monkeypatch.delenv("GRIDFLEET_RUN_ID", raising=False)
|
|
126
|
+
|
|
127
|
+
captured, gen = _make_plugin_generator(monkeypatch)
|
|
128
|
+
next(gen)
|
|
129
|
+
|
|
130
|
+
assert len(captured) == 1
|
|
131
|
+
assert captured[0][0] == "http://router:4444"
|
|
@@ -136,7 +136,7 @@ wheels = [
|
|
|
136
136
|
|
|
137
137
|
[[package]]
|
|
138
138
|
name = "gridfleet-testkit"
|
|
139
|
-
version = "0.
|
|
139
|
+
version = "0.10.0"
|
|
140
140
|
source = { editable = "." }
|
|
141
141
|
dependencies = [
|
|
142
142
|
{ name = "appium-python-client" },
|
|
@@ -147,7 +147,6 @@ dependencies = [
|
|
|
147
147
|
[package.optional-dependencies]
|
|
148
148
|
dev = [
|
|
149
149
|
{ name = "mypy" },
|
|
150
|
-
{ name = "pytest" },
|
|
151
150
|
{ name = "ruff" },
|
|
152
151
|
]
|
|
153
152
|
|
|
@@ -157,7 +156,6 @@ requires-dist = [
|
|
|
157
156
|
{ name = "httpx", specifier = ">=0.27,<1" },
|
|
158
157
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.20.2,<3" },
|
|
159
158
|
{ name = "pytest", specifier = ">=9.0.3,<10" },
|
|
160
|
-
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3,<10" },
|
|
161
159
|
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.12,<1" },
|
|
162
160
|
]
|
|
163
161
|
provides-extras = ["dev"]
|
|
@@ -456,27 +454,27 @@ wheels = [
|
|
|
456
454
|
|
|
457
455
|
[[package]]
|
|
458
456
|
name = "ruff"
|
|
459
|
-
version = "0.15.
|
|
460
|
-
source = { registry = "https://pypi.org/simple" }
|
|
461
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
462
|
-
wheels = [
|
|
463
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
464
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
465
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
466
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
467
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
468
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
469
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
470
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
471
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
472
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
473
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
474
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
475
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
476
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
477
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
478
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
479
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
457
|
+
version = "0.15.16"
|
|
458
|
+
source = { registry = "https://pypi.org/simple" }
|
|
459
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" }
|
|
460
|
+
wheels = [
|
|
461
|
+
{ url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" },
|
|
462
|
+
{ url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" },
|
|
463
|
+
{ url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" },
|
|
464
|
+
{ url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" },
|
|
465
|
+
{ url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" },
|
|
466
|
+
{ url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" },
|
|
467
|
+
{ url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" },
|
|
468
|
+
{ url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" },
|
|
469
|
+
{ url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" },
|
|
470
|
+
{ url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" },
|
|
471
|
+
{ url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" },
|
|
472
|
+
{ url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" },
|
|
473
|
+
{ url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" },
|
|
474
|
+
{ url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" },
|
|
475
|
+
{ url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" },
|
|
476
|
+
{ url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" },
|
|
477
|
+
{ url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" },
|
|
480
478
|
]
|
|
481
479
|
|
|
482
480
|
[[package]]
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
|
-
from gridfleet_testkit.appium import build_appium_options
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
import pytest
|
|
9
|
-
from appium.options.common import AppiumOptions
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def _caps(options: AppiumOptions) -> dict[str, object]:
|
|
13
|
-
return dict(options.to_capabilities())
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_injects_free_when_no_run_id_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
17
|
-
monkeypatch.delenv("GRIDFLEET_RUN_ID", raising=False)
|
|
18
|
-
|
|
19
|
-
options = build_appium_options(capabilities={"platformName": "Android"})
|
|
20
|
-
|
|
21
|
-
assert _caps(options)["gridfleet:run_id"] == "free"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_injects_run_id_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
25
|
-
monkeypatch.setenv("GRIDFLEET_RUN_ID", "run-123")
|
|
26
|
-
|
|
27
|
-
options = build_appium_options(capabilities={"platformName": "Android"})
|
|
28
|
-
|
|
29
|
-
assert _caps(options)["gridfleet:run_id"] == "run-123"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gridfleet_testkit-0.9.4 → gridfleet_testkit-0.10.0}/tests/test_sessions_resolve_device_handle.py
RENAMED
|
File without changes
|