gridfleet-testkit 0.8.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.8.0 → gridfleet_testkit-0.9.0}/CHANGELOG.md +14 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/PKG-INFO +34 -1
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/README.md +33 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/__init__.py +1 -1
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/allocation.py +7 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/client.py +10 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/pyproject.toml +1 -1
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_allocation.py +11 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_client.py +21 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin.py +27 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/uv.lock +1 -1
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/.gitignore +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/__init__.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/_example_helpers.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/assets/hello-world.zip +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_android_mobile_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_android_tv_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_firetv_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_ios_simulator_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_roku_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_roku_sideload_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_tvos_screenshot.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/appium.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/py.typed +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/pytest_plugin.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/gridfleet_testkit/sessions.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_appium.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_client_test_data.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_docs_contract.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_driver_agnostic_guard.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_package_metadata.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin_grid_run_id_injection.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_pytest_plugin_test_data.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_sessions.py +0 -0
- {gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/tests/test_sessions_resolve_device_handle.py +0 -0
|
@@ -2,6 +2,20 @@
|
|
|
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
|
+
|
|
5
19
|
## [0.8.0](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.7.0...gridfleet-testkit-v0.8.0) (2026-05-12)
|
|
6
20
|
|
|
7
21
|
|
|
@@ -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
|
|
@@ -192,6 +192,7 @@ finally:
|
|
|
192
192
|
| `GridFleetClient.get_device_by_connection_target(connection_target)` | Fetch one device detail row by runtime connection target |
|
|
193
193
|
| `GridFleetClient.get_device_capabilities(device_id)` | Fetch current Appium capability metadata for a device |
|
|
194
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 |
|
|
195
196
|
| `GridFleetClient.replace_device_test_data(device_id, body)` | Replace test_data with the supplied object |
|
|
196
197
|
| `GridFleetClient.merge_device_test_data(device_id, body)` | Deep-merge into device test_data |
|
|
197
198
|
| `GridFleetClient.resolve_device_id_by_connection_target(connection_target)` | Resolve the backend device id for a runtime connection target |
|
|
@@ -216,6 +217,38 @@ finally:
|
|
|
216
217
|
| `get_device_test_data_for_driver(driver, gridfleet_client=None)` | Fetch test_data for a live Appium driver |
|
|
217
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 |
|
|
218
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
|
+
|
|
219
252
|
### Worker Identity
|
|
220
253
|
|
|
221
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.
|
|
@@ -158,6 +158,7 @@ finally:
|
|
|
158
158
|
| `GridFleetClient.get_device_by_connection_target(connection_target)` | Fetch one device detail row by runtime connection target |
|
|
159
159
|
| `GridFleetClient.get_device_capabilities(device_id)` | Fetch current Appium capability metadata for a device |
|
|
160
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 |
|
|
161
162
|
| `GridFleetClient.replace_device_test_data(device_id, body)` | Replace test_data with the supplied object |
|
|
162
163
|
| `GridFleetClient.merge_device_test_data(device_id, body)` | Deep-merge into device test_data |
|
|
163
164
|
| `GridFleetClient.resolve_device_id_by_connection_target(connection_target)` | Resolve the backend device id for a runtime connection target |
|
|
@@ -182,6 +183,38 @@ finally:
|
|
|
182
183
|
| `get_device_test_data_for_driver(driver, gridfleet_client=None)` | Fetch test_data for a live Appium driver |
|
|
183
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 |
|
|
184
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
|
+
|
|
185
218
|
### Worker Identity
|
|
186
219
|
|
|
187
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.
|
|
@@ -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
|
|
|
@@ -290,6 +290,16 @@ class GridFleetClient:
|
|
|
290
290
|
resp.raise_for_status()
|
|
291
291
|
return cast("dict[str, Any]", resp.json())
|
|
292
292
|
|
|
293
|
+
def get_run(self, run_id: str) -> dict[str, Any]:
|
|
294
|
+
"""Fetch one run detail row by backend run id."""
|
|
295
|
+
resp = httpx.get(
|
|
296
|
+
f"{self.base_url}/runs/{run_id}",
|
|
297
|
+
timeout=10,
|
|
298
|
+
auth=self._auth,
|
|
299
|
+
)
|
|
300
|
+
resp.raise_for_status()
|
|
301
|
+
return cast("dict[str, Any]", resp.json())
|
|
302
|
+
|
|
293
303
|
def replace_device_test_data(self, device_id: str, body: dict[str, Any]) -> dict[str, Any]:
|
|
294
304
|
"""Replace device test_data with the supplied object."""
|
|
295
305
|
resp = httpx.put(
|
|
@@ -328,6 +328,17 @@ def test_hydrate_uses_inline_test_data() -> None:
|
|
|
328
328
|
assert client.test_data_calls == []
|
|
329
329
|
|
|
330
330
|
|
|
331
|
+
def test_hydrate_allocated_device_populates_inline_tags() -> None:
|
|
332
|
+
client = FakeClient()
|
|
333
|
+
allocated = hydrate_allocated_device(
|
|
334
|
+
device_handle(tags={"screen_type": "4k"}),
|
|
335
|
+
run_id="run-1",
|
|
336
|
+
client=client,
|
|
337
|
+
fetch_config=False,
|
|
338
|
+
)
|
|
339
|
+
assert allocated.tags == {"screen_type": "4k"}
|
|
340
|
+
|
|
341
|
+
|
|
331
342
|
def test_hydrate_defaults_test_data_to_none_when_absent() -> None:
|
|
332
343
|
client = FakeClient()
|
|
333
344
|
allocated = hydrate_allocated_device(
|
|
@@ -187,6 +187,27 @@ def test_get_device_fetches_device_detail_by_id(monkeypatch):
|
|
|
187
187
|
assert calls == [("http://manager/api/devices/dev-1", 10)]
|
|
188
188
|
|
|
189
189
|
|
|
190
|
+
def test_get_run_fetches_run_endpoint(monkeypatch):
|
|
191
|
+
calls: list[tuple[str, int | None]] = []
|
|
192
|
+
|
|
193
|
+
def fake_get(
|
|
194
|
+
url: str,
|
|
195
|
+
*,
|
|
196
|
+
timeout: int | None = None,
|
|
197
|
+
auth: Any = None,
|
|
198
|
+
) -> DummyResponse:
|
|
199
|
+
calls.append((url, timeout))
|
|
200
|
+
return DummyResponse({"id": "run-1", "name": "smoke"})
|
|
201
|
+
|
|
202
|
+
monkeypatch.setattr("gridfleet_testkit.client.httpx.get", fake_get)
|
|
203
|
+
|
|
204
|
+
client = GridFleetClient("http://manager/api")
|
|
205
|
+
run = client.get_run("run-1")
|
|
206
|
+
|
|
207
|
+
assert run == {"id": "run-1", "name": "smoke"}
|
|
208
|
+
assert calls == [("http://manager/api/runs/run-1", 10)]
|
|
209
|
+
|
|
210
|
+
|
|
190
211
|
def test_get_driver_pack_catalog_fetches_catalog_endpoint(monkeypatch):
|
|
191
212
|
calls: list[tuple[str, str, dict[str, Any] | None, int | None]] = []
|
|
192
213
|
|
|
@@ -188,6 +188,33 @@ def test_appium_driver_builds_capabilities_and_reports_status(monkeypatch, repor
|
|
|
188
188
|
assert gridfleet_client.reported_statuses == [("sess-1", expected_status, True)]
|
|
189
189
|
|
|
190
190
|
|
|
191
|
+
def test_appium_driver_passes_tag_capabilities_through(monkeypatch):
|
|
192
|
+
created_drivers = []
|
|
193
|
+
install_fake_appium(monkeypatch, created_drivers)
|
|
194
|
+
RecordingClient.instances.clear()
|
|
195
|
+
gridfleet_client = RecordingClient()
|
|
196
|
+
|
|
197
|
+
request = FakeRequest(
|
|
198
|
+
{
|
|
199
|
+
"pack_id": "appium-uiautomator2",
|
|
200
|
+
"platform_id": "android_mobile",
|
|
201
|
+
"appium:gridfleet:tag:screen_type": "4k",
|
|
202
|
+
},
|
|
203
|
+
test_name="test_tag_cap",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
fixture_fn = pytest_plugin.appium_driver.__wrapped__
|
|
207
|
+
generator = fixture_fn(request, gridfleet_client)
|
|
208
|
+
driver = next(generator)
|
|
209
|
+
|
|
210
|
+
assert created_drivers[0][1]["appium:gridfleet:tag:screen_type"] == "4k"
|
|
211
|
+
assert created_drivers[0][1]["gridfleet:testName"] == "test_tag_cap"
|
|
212
|
+
|
|
213
|
+
driver.quit()
|
|
214
|
+
with pytest.raises(StopIteration):
|
|
215
|
+
next(generator)
|
|
216
|
+
|
|
217
|
+
|
|
191
218
|
def test_device_config_uses_runtime_connection_target():
|
|
192
219
|
gridfleet_client = types.SimpleNamespace(get_device_config=lambda target: {"target": target})
|
|
193
220
|
driver = types.SimpleNamespace(capabilities={"appium:udid": "SERIAL123"})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_android_mobile_screenshot.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_ios_simulator_screenshot.py
RENAMED
|
File without changes
|
|
File without changes
|
{gridfleet_testkit-0.8.0 → gridfleet_testkit-0.9.0}/examples/test_roku_sideload_screenshot.py
RENAMED
|
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.8.0 → gridfleet_testkit-0.9.0}/tests/test_sessions_resolve_device_handle.py
RENAMED
|
File without changes
|