gridfleet-testkit 0.9.1__tar.gz → 0.9.3__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.
Files changed (36) hide show
  1. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/CHANGELOG.md +15 -0
  2. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/PKG-INFO +1 -1
  3. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_android_mobile_screenshot.py +0 -2
  4. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_android_tv_screenshot.py +0 -2
  5. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_firetv_screenshot.py +0 -2
  6. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_ios_simulator_screenshot.py +0 -2
  7. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_roku_screenshot.py +0 -2
  8. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_roku_sideload_screenshot.py +0 -2
  9. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/test_tvos_screenshot.py +0 -2
  10. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/__init__.py +1 -1
  11. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/client.py +17 -9
  12. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/pyproject.toml +1 -1
  13. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_client.py +23 -11
  14. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/uv.lock +1 -1
  15. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/.gitignore +0 -0
  16. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/README.md +0 -0
  17. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/__init__.py +0 -0
  18. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/_example_helpers.py +0 -0
  19. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/examples/assets/hello-world.zip +0 -0
  20. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/allocation.py +0 -0
  21. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/appium.py +0 -0
  22. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/py.typed +0 -0
  23. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/pytest_plugin.py +0 -0
  24. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/sessions.py +0 -0
  25. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/gridfleet_testkit/types.py +0 -0
  26. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_allocation.py +0 -0
  27. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_appium.py +0 -0
  28. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_client_test_data.py +0 -0
  29. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_docs_contract.py +0 -0
  30. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_driver_agnostic_guard.py +0 -0
  31. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_package_metadata.py +0 -0
  32. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_pytest_plugin.py +0 -0
  33. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_pytest_plugin_grid_run_id_injection.py +0 -0
  34. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_pytest_plugin_test_data.py +0 -0
  35. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_sessions.py +0 -0
  36. {gridfleet_testkit-0.9.1 → gridfleet_testkit-0.9.3}/tests/test_sessions_resolve_device_handle.py +0 -0
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to the GridFleet testkit (`gridfleet-testkit` on PyPI) are documented here.
4
4
 
5
+ ## [0.9.3](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.2...gridfleet-testkit-v0.9.3) (2026-05-16)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * bind testkit-registered sessions to their device ([35030ef](https://github.com/quidow/gridfleet/commit/35030eff438b3d8cfa17b54f1329dfeb03dadf07))
11
+ * **testkit:** stop sending client-derived device identity on register ([c025d1e](https://github.com/quidow/gridfleet/commit/c025d1e10d2bd793dadc4ca167aaaf4a7a2e23e7))
12
+
13
+ ## [0.9.2](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.1...gridfleet-testkit-v0.9.2) (2026-05-16)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **testkit:** remove pytest_plugins shim and stale --extra appium docs ([#270](https://github.com/quidow/gridfleet/issues/270)) ([1165fc1](https://github.com/quidow/gridfleet/commit/1165fc1e7326853e7467500c41d8b1c197e7046f))
19
+
5
20
  ## [0.9.1](https://github.com/quidow/gridfleet/compare/gridfleet-testkit-v0.9.0...gridfleet-testkit-v0.9.1) (2026-05-13)
6
21
 
7
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridfleet-testkit
3
- Version: 0.9.1
3
+ Version: 0.9.3
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
@@ -16,8 +16,6 @@ from appium.webdriver.webdriver import WebDriver
16
16
 
17
17
  from examples._example_helpers import print_connection_context, save_and_assert_screenshot
18
18
 
19
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
20
-
21
19
 
22
20
  @pytest.mark.parametrize(
23
21
  "appium_driver",
@@ -16,8 +16,6 @@ from appium.webdriver.webdriver import WebDriver
16
16
 
17
17
  from examples._example_helpers import print_connection_context, save_and_assert_screenshot
18
18
 
19
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
20
-
21
19
 
22
20
  @pytest.mark.parametrize(
23
21
  "appium_driver",
@@ -16,8 +16,6 @@ from appium.webdriver.webdriver import WebDriver
16
16
 
17
17
  from examples._example_helpers import print_connection_context, save_and_assert_screenshot
18
18
 
19
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
20
-
21
19
 
22
20
  @pytest.mark.parametrize(
23
21
  "appium_driver",
@@ -17,8 +17,6 @@ from appium.webdriver.webdriver import WebDriver
17
17
 
18
18
  from examples._example_helpers import print_connection_context, save_and_assert_screenshot
19
19
 
20
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
21
-
22
20
 
23
21
  @pytest.mark.parametrize(
24
22
  "appium_driver",
@@ -21,8 +21,6 @@ from examples._example_helpers import (
21
21
  save_and_assert_screenshot,
22
22
  )
23
23
 
24
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
25
-
26
24
 
27
25
  @pytest.mark.parametrize(
28
26
  "appium_driver",
@@ -22,8 +22,6 @@ from examples._example_helpers import (
22
22
  save_and_assert_screenshot,
23
23
  )
24
24
 
25
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
26
-
27
25
 
28
26
  @pytest.mark.parametrize(
29
27
  "appium_driver",
@@ -18,8 +18,6 @@ from appium.webdriver.webdriver import WebDriver
18
18
 
19
19
  from examples._example_helpers import print_connection_context, save_and_assert_screenshot
20
20
 
21
- pytest_plugins = ["gridfleet_testkit.pytest_plugin"]
22
-
23
21
 
24
22
  @pytest.mark.parametrize(
25
23
  "appium_driver",
@@ -47,7 +47,7 @@ from .sessions import build_error_session_payload, resolve_device_handle_from_dr
47
47
  try:
48
48
  __version__ = version("gridfleet-testkit")
49
49
  except PackageNotFoundError:
50
- __version__ = "0.9.1"
50
+ __version__ = "0.9.3"
51
51
 
52
52
  __all__ = [
53
53
  "GRIDFLEET_API_URL",
@@ -511,11 +511,23 @@ class GridFleetClient:
511
511
  run_id: str | None = None,
512
512
  suppress_errors: bool = True,
513
513
  ) -> JsonObject | None:
514
- """Extract session metadata from an Appium driver and register it.
515
-
516
- Also wraps ``driver.quit`` so that the first call after a successful
517
- registration fires :meth:`notify_session_finished` automatically.
518
- Errors from notify are suppressed they must never break the caller.
514
+ """Register a running Appium session and wire ``driver.quit`` for notify.
515
+
516
+ The testkit does not try to derive a device identity from
517
+ ``driver.capabilities``. ``udid`` / ``deviceName`` are not unique
518
+ across drivers (Roku reuses the IP, AVDs reuse port-named handles,
519
+ iOS simulators reuse a UDID across reboots), and the Appium driver
520
+ strips the ``appium:`` vendor prefix and drops nested vendor keys
521
+ like ``appium:gridfleet:deviceId`` from the W3C echo, so identifying
522
+ caps cannot be read reliably from the client side at all.
523
+
524
+ The row is registered with only ``session_id`` + capabilities; the
525
+ backend ``session_sync_loop`` queries the Grid hub on each cycle and
526
+ binds the row to its Device by reading ``slot.session.stereotype``,
527
+ which is prefix-stable and carries ``appium:gridfleet:deviceId``.
528
+ ``driver.quit`` is wrapped so the first call posts to ``/finished``
529
+ exactly once; errors from notify are suppressed so they never break
530
+ the caller.
519
531
  """
520
532
  capabilities = cast("JsonObject", driver.capabilities) if isinstance(driver.capabilities, dict) else {}
521
533
  if not isinstance(capabilities, dict):
@@ -523,13 +535,9 @@ class GridFleetClient:
523
535
  session_id = getattr(driver, "session_id", None)
524
536
  if not isinstance(session_id, str) or not session_id:
525
537
  raise RuntimeError("Created Appium driver did not expose a session ID")
526
- device_id = capabilities.get("appium:gridfleet:deviceId") or capabilities.get("gridfleet:deviceId")
527
- connection_target = capabilities.get("appium:udid") or capabilities.get("appium:deviceName")
528
538
  result = self.register_session(
529
539
  session_id=session_id,
530
540
  test_name=test_name,
531
- device_id=device_id if isinstance(device_id, str) and device_id else None,
532
- connection_target=connection_target if isinstance(connection_target, str) and connection_target else None,
533
541
  requested_capabilities=capabilities,
534
542
  run_id=run_id,
535
543
  suppress_errors=suppress_errors,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "gridfleet-testkit"
7
- version = "0.9.1"
7
+ version = "0.9.3"
8
8
  description = "Supported pytest and run-orchestration helpers for GridFleet integrations"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -549,7 +549,17 @@ def test_update_session_status_patches_status(monkeypatch):
549
549
  }
550
550
 
551
551
 
552
- def test_register_session_from_driver_extracts_gridfleet_capabilities(monkeypatch):
552
+ def test_register_session_from_driver_does_not_send_client_side_device_identity(monkeypatch):
553
+ """The testkit deliberately does not attempt to identify the device.
554
+
555
+ Vendor-prefixed identifying caps do not survive the Appium driver's W3C
556
+ echo (``appium:`` stripped, ``appium:gridfleet:deviceId`` dropped) and
557
+ the stripped forms (``udid`` / ``deviceName``) are not globally unique
558
+ across drivers. The backend ``session_sync_loop`` reads the
559
+ prefix-stable ``slot.session.stereotype`` from the Grid hub instead, so
560
+ the registration payload carries only ``session_id`` + capabilities +
561
+ ``test_name`` + ``run_id``.
562
+ """
553
563
  captured: JsonObject = {}
554
564
 
555
565
  def fake_register_session(self: GridFleetClient, **kwargs: object) -> JsonObject:
@@ -561,10 +571,12 @@ def test_register_session_from_driver_extracts_gridfleet_capabilities(monkeypatc
561
571
  class FakeDriver:
562
572
  session_id = "sess-1"
563
573
  capabilities: ClassVar[dict[str, object]] = {
564
- "appium:gridfleet:deviceId": "dev-1",
565
- "appium:udid": "SERIAL123",
566
- "appium:platform": "android_mobile",
567
- "platformName": "Android",
574
+ # Real Appium echo: ``appium:`` stripped, no
575
+ # ``appium:gridfleet:deviceId`` survives.
576
+ "udid": "SERIAL123",
577
+ "deviceName": "SERIAL123",
578
+ "automationName": "UiAutomator2",
579
+ "platformName": "ANDROID",
568
580
  }
569
581
 
570
582
  def quit(self) -> None:
@@ -574,16 +586,16 @@ def test_register_session_from_driver_extracts_gridfleet_capabilities(monkeypatc
574
586
  client = GridFleetClient("http://manager/api")
575
587
 
576
588
  assert client.register_session_from_driver(driver, test_name="test_login", run_id="run-1") == {"ok": True}
589
+ assert "device_id" not in captured
590
+ assert "connection_target" not in captured
577
591
  assert captured == {
578
592
  "session_id": "sess-1",
579
593
  "test_name": "test_login",
580
- "device_id": "dev-1",
581
- "connection_target": "SERIAL123",
582
594
  "requested_capabilities": {
583
- "appium:gridfleet:deviceId": "dev-1",
584
- "appium:udid": "SERIAL123",
585
- "appium:platform": "android_mobile",
586
- "platformName": "Android",
595
+ "udid": "SERIAL123",
596
+ "deviceName": "SERIAL123",
597
+ "automationName": "UiAutomator2",
598
+ "platformName": "ANDROID",
587
599
  },
588
600
  "run_id": "run-1",
589
601
  "suppress_errors": True,
@@ -136,7 +136,7 @@ wheels = [
136
136
 
137
137
  [[package]]
138
138
  name = "gridfleet-testkit"
139
- version = "0.9.1"
139
+ version = "0.9.3"
140
140
  source = { editable = "." }
141
141
  dependencies = [
142
142
  { name = "appium-python-client" },