testmu-playwright-python 0.1.0__tar.gz → 0.1.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 (64) hide show
  1. {testmu_playwright_python-0.1.0/testmu_playwright_python.egg-info → testmu_playwright_python-0.1.3}/PKG-INFO +14 -21
  2. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/README.md +6 -8
  3. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/pyproject.toml +10 -19
  4. testmu_playwright_python-0.1.3/testmu/_config.py +13 -0
  5. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_decorator.py +10 -7
  6. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_heal_patch.py +13 -3
  7. testmu_playwright_python-0.1.3/testmu/_helpers/_http.py +20 -0
  8. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/assertion.py +13 -10
  9. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/execute_api.py +9 -9
  10. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/execute_db.py +9 -8
  11. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/execute_js.py +2 -2
  12. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/kane_cli.py +12 -8
  13. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/network.py +10 -8
  14. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/smartui.py +9 -2
  15. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/vision.py +60 -73
  16. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/wait.py +6 -4
  17. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_reporter/__init__.py +10 -7
  18. testmu_playwright_python-0.1.3/testmu/_reporter/local.py +31 -0
  19. testmu_playwright_python-0.1.3/testmu/_reporter/lt.py +75 -0
  20. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_reporter/null.py +1 -1
  21. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_session.py +50 -7
  22. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_vars.py +14 -14
  23. testmu_playwright_python-0.1.3/testmu/playwright_async/__init__.py +67 -0
  24. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3/testmu_playwright_python.egg-info}/PKG-INFO +14 -21
  25. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu_playwright_python.egg-info/SOURCES.txt +20 -3
  26. testmu_playwright_python-0.1.3/tests/test_capability.py +423 -0
  27. testmu_playwright_python-0.1.3/tests/test_config.py +65 -0
  28. testmu_playwright_python-0.1.3/tests/test_configure.py +73 -0
  29. testmu_playwright_python-0.1.3/tests/test_decorator.py +60 -0
  30. testmu_playwright_python-0.1.3/tests/test_heal_patch.py +97 -0
  31. testmu_playwright_python-0.1.3/tests/test_helper_assertion.py +361 -0
  32. testmu_playwright_python-0.1.3/tests/test_helper_execute_api.py +307 -0
  33. testmu_playwright_python-0.1.3/tests/test_helper_execute_db.py +269 -0
  34. testmu_playwright_python-0.1.3/tests/test_helper_execute_js.py +157 -0
  35. testmu_playwright_python-0.1.3/tests/test_helper_network_math.py +215 -0
  36. testmu_playwright_python-0.1.3/tests/test_helper_remaining.py +438 -0
  37. testmu_playwright_python-0.1.3/tests/test_helper_tabs_drag.py +150 -0
  38. testmu_playwright_python-0.1.3/tests/test_helper_vision.py +477 -0
  39. testmu_playwright_python-0.1.3/tests/test_helpers_stub.py +39 -0
  40. testmu_playwright_python-0.1.3/tests/test_integration.py +34 -0
  41. testmu_playwright_python-0.1.3/tests/test_step.py +54 -0
  42. testmu_playwright_python-0.1.3/tests/test_test_config.py +85 -0
  43. testmu_playwright_python-0.1.3/tests/test_vars.py +474 -0
  44. testmu_playwright_python-0.1.0/LICENSE +0 -21
  45. testmu_playwright_python-0.1.0/MANIFEST.in +0 -8
  46. testmu_playwright_python-0.1.0/testmu/_config.py +0 -9
  47. testmu_playwright_python-0.1.0/testmu/_reporter/local.py +0 -25
  48. testmu_playwright_python-0.1.0/testmu/_reporter/lt.py +0 -72
  49. testmu_playwright_python-0.1.0/testmu/playwright_async/__init__.py +0 -36
  50. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/setup.cfg +0 -0
  51. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/__init__.py +0 -0
  52. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_capability.py +0 -0
  53. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_configure.py +0 -0
  54. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_errors.py +0 -0
  55. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/__init__.py +0 -0
  56. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/drag.py +0 -0
  57. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/math.py +0 -0
  58. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_helpers/tabs.py +0 -0
  59. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_matchers.py +0 -0
  60. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_step.py +0 -0
  61. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu/_test_config.py +0 -0
  62. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu_playwright_python.egg-info/dependency_links.txt +0 -0
  63. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu_playwright_python.egg-info/requires.txt +0 -0
  64. {testmu_playwright_python-0.1.0 → testmu_playwright_python-0.1.3}/testmu_playwright_python.egg-info/top_level.txt +0 -0
@@ -1,25 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: testmu-playwright-python
3
- Version: 0.1.0
4
- Summary: TestMu binding for Playwright Python — thin test runtime for TestMu/LambdaTest exports
5
- Author-email: TestMu AI <engineering@testmu.ai>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://testmu.ai
8
- Project-URL: Documentation, https://docs.testmu.ai
9
- Project-URL: Repository, https://github.com/testmuai/testmu-bindings
10
- Keywords: testing,ai,agents,playwright,lambdatest,testmu
3
+ Version: 0.1.3
4
+ Summary: Testmu binding for Playwright Python — thin test runtime for LambdaTest exports
5
+ Author-email: LambdaTest <engineering@lambdatest.com>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Project-URL: Homepage, https://github.com/shravan-lambdatest/testmu-bindings
8
+ Project-URL: Repository, https://github.com/shravan-lambdatest/testmu-bindings
11
9
  Classifier: Development Status :: 3 - Alpha
12
10
  Classifier: Intended Audience :: Developers
13
- Classifier: Natural Language :: English
14
- Classifier: Topic :: Software Development :: Testing
15
- Classifier: Framework :: Pytest
16
11
  Classifier: Programming Language :: Python :: 3
17
12
  Classifier: Programming Language :: Python :: 3.11
18
13
  Classifier: Programming Language :: Python :: 3.12
19
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Framework :: Pytest
16
+ Classifier: Topic :: Software Development :: Testing
20
17
  Requires-Python: >=3.11
21
18
  Description-Content-Type: text/markdown
22
- License-File: LICENSE
23
19
  Requires-Dist: playwright>=1.57.0
24
20
  Requires-Dist: pyotp>=2.9.0
25
21
  Requires-Dist: aiohttp
@@ -28,16 +24,18 @@ Requires-Dist: requests>=2.28.0
28
24
  Provides-Extra: dev
29
25
  Requires-Dist: pytest>=7.0; extra == "dev"
30
26
  Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
31
- Dynamic: license-file
32
27
 
33
28
  # testmu-playwright-python
34
29
 
35
- TestMu binding for Playwright Python — a thin test runtime for running TestMu exported tests locally or on the LambdaTest grid.
30
+ Testmu binding for Playwright Python — a thin test runtime for running LambdaTest exported tests locally or on the LambdaTest grid.
36
31
 
37
32
  ## Installation
38
33
 
39
34
  ```bash
40
- pip install testmu-playwright-python
35
+ # From TestPyPI (pre-release)
36
+ pip install --index-url https://test.pypi.org/simple/ \
37
+ --extra-index-url https://pypi.org/simple/ \
38
+ testmu-playwright-python
41
39
  ```
42
40
 
43
41
  ## Quick Start
@@ -75,11 +73,6 @@ testmu.run(my_test)
75
73
  - Python >= 3.11
76
74
  - Playwright >= 1.57.0
77
75
 
78
- ## Links
79
-
80
- - Homepage: [testmu.ai](https://testmu.ai)
81
- - Documentation: [docs.testmu.ai](https://docs.testmu.ai)
82
-
83
76
  ## License
84
77
 
85
- MIT see [LICENSE](LICENSE)
78
+ Proprietary - LambdaTest
@@ -1,11 +1,14 @@
1
1
  # testmu-playwright-python
2
2
 
3
- TestMu binding for Playwright Python — a thin test runtime for running TestMu exported tests locally or on the LambdaTest grid.
3
+ Testmu binding for Playwright Python — a thin test runtime for running LambdaTest exported tests locally or on the LambdaTest grid.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pip install testmu-playwright-python
8
+ # From TestPyPI (pre-release)
9
+ pip install --index-url https://test.pypi.org/simple/ \
10
+ --extra-index-url https://pypi.org/simple/ \
11
+ testmu-playwright-python
9
12
  ```
10
13
 
11
14
  ## Quick Start
@@ -43,11 +46,6 @@ testmu.run(my_test)
43
46
  - Python >= 3.11
44
47
  - Playwright >= 1.57.0
45
48
 
46
- ## Links
47
-
48
- - Homepage: [testmu.ai](https://testmu.ai)
49
- - Documentation: [docs.testmu.ai](https://docs.testmu.ai)
50
-
51
49
  ## License
52
50
 
53
- MIT see [LICENSE](LICENSE)
51
+ Proprietary - LambdaTest
@@ -1,29 +1,24 @@
1
1
  [build-system]
2
- requires = ["setuptools>=68.0", "wheel"]
2
+ requires = ["setuptools>=68.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "testmu-playwright-python"
7
- version = "0.1.0"
8
- description = "TestMu binding for Playwright Python — thin test runtime for TestMu/LambdaTest exports"
9
- readme = "README.md"
7
+ version = "0.1.3"
8
+ description = "Testmu binding for Playwright Python — thin test runtime for LambdaTest exports"
10
9
  requires-python = ">=3.11"
11
- license = "MIT"
12
- license-files = ["LICENSE"]
13
- authors = [
14
- {name = "TestMu AI", email = "engineering@testmu.ai"},
15
- ]
16
- keywords = ["testing", "ai", "agents", "playwright", "lambdatest", "testmu"]
10
+ license = "LicenseRef-Proprietary"
11
+ authors = [{name = "LambdaTest", email = "engineering@lambdatest.com"}]
12
+ readme = "README.md"
17
13
  classifiers = [
18
14
  "Development Status :: 3 - Alpha",
19
15
  "Intended Audience :: Developers",
20
- "Natural Language :: English",
21
- "Topic :: Software Development :: Testing",
22
- "Framework :: Pytest",
23
16
  "Programming Language :: Python :: 3",
24
17
  "Programming Language :: Python :: 3.11",
25
18
  "Programming Language :: Python :: 3.12",
26
19
  "Programming Language :: Python :: 3.13",
20
+ "Framework :: Pytest",
21
+ "Topic :: Software Development :: Testing",
27
22
  ]
28
23
  dependencies = [
29
24
  "playwright>=1.57.0",
@@ -40,12 +35,8 @@ dev = [
40
35
  ]
41
36
 
42
37
  [project.urls]
43
- Homepage = "https://testmu.ai"
44
- Documentation = "https://docs.testmu.ai"
45
- Repository = "https://github.com/testmuai/testmu-bindings"
46
-
47
- [tool.setuptools.packages.find]
48
- include = ["testmu*"]
38
+ Homepage = "https://github.com/shravan-lambdatest/testmu-bindings"
39
+ Repository = "https://github.com/shravan-lambdatest/testmu-bindings"
49
40
 
50
41
  [tool.pytest.ini_options]
51
42
  asyncio_mode = "auto"
@@ -0,0 +1,13 @@
1
+ """Run target, auth, and smart detection.
2
+
3
+ Three orthogonal flags:
4
+ - run_target: "local" (default) or "cloud". Only "cloud" connects to LT CDP.
5
+ - lt_auth: LT creds available. Enables ATMS/automind/downloads/API proxy
6
+ regardless of run target.
7
+ - smart: AI/heal features. Requires lt_auth; validated at testmu.run().
8
+ """
9
+ import os
10
+
11
+ run_target = os.getenv("TESTMU_RUN_TARGET", "local").lower()
12
+ lt_auth = bool(os.getenv("LT_USERNAME")) and bool(os.getenv("LT_ACCESS_KEY"))
13
+ smart = os.getenv("TESTMU_SMART", "0") == "1"
@@ -34,13 +34,16 @@ def test(fn):
34
34
  await rep.pass_test()
35
35
  return result
36
36
  except Exception as e:
37
- page = _find_page(args, kwargs)
38
- if page is not None:
39
- try:
40
- screenshot = await page.screenshot()
41
- await rep.attach_screenshot(screenshot)
42
- except Exception:
43
- pass
37
+ # Screenshot-on-failure disabled — attach_screenshot is a stub
38
+ # that doesn't upload bytes anywhere. Re-enable once LT CDP upload
39
+ # is wired up.
40
+ # page = _find_page(args, kwargs)
41
+ # if page is not None:
42
+ # try:
43
+ # screenshot = await page.screenshot()
44
+ # await rep.attach_screenshot(screenshot)
45
+ # except Exception:
46
+ # pass
44
47
  await rep.fail_test(e)
45
48
  raise
46
49
  return async_wrapper
@@ -5,6 +5,16 @@ Installs wrappers on Locator action methods that catch TimeoutError
5
5
 
6
6
  The patch only fires inside a testmu.step() block (ContextVar check).
7
7
  Outside a step, the wrapper is a pure passthrough.
8
+
9
+ heal_fn contract:
10
+ async def heal_fn(page, description, method_name, *args, **kwargs) -> bool
11
+
12
+ Returns True if heal handled the action (wrapper returns normally).
13
+ Returns False if heal couldn't help (wrapper re-raises original error).
14
+
15
+ Today's default heal does a coordinate lookup via vision API and clicks
16
+ at the returned coordinates for click/hover methods. Other methods are
17
+ not supported until locator re-resolution lands.
8
18
  """
9
19
  import functools
10
20
  from testmu._step import _current_step
@@ -41,9 +51,9 @@ def _make_wrapper(name, original, heal_fn):
41
51
  except TimeoutError:
42
52
  if await self.count() > 0:
43
53
  raise # element exists but not actionable — heal won't help
44
- healed = await heal_fn(self.page, step.description)
45
- if healed is None:
54
+ handled = await heal_fn(self.page, step.description, name, *args, **kwargs)
55
+ if not handled:
46
56
  raise
47
- return await original(healed, *args, **kwargs)
57
+ # heal performed the action (e.g. via page.mouse) — nothing to return
48
58
 
49
59
  return wrapper
@@ -0,0 +1,20 @@
1
+ """Shared HTTP session factory for AI API calls.
2
+
3
+ All smart helpers (vision, assertion, wait, network) call LT-hosted
4
+ endpoints that require Basic auth via LT_USERNAME + LT_ACCESS_KEY.
5
+ """
6
+ import base64
7
+ import os
8
+
9
+ import aiohttp
10
+
11
+
12
+ def create_session(**kwargs) -> aiohttp.ClientSession:
13
+ """Create an aiohttp session with LT Basic auth headers."""
14
+ headers = kwargs.pop("headers", {})
15
+ username = os.getenv("LT_USERNAME", "")
16
+ access_key = os.getenv("LT_ACCESS_KEY", "")
17
+ if username and access_key:
18
+ auth = base64.b64encode(f"{username}:{access_key}".encode()).decode()
19
+ headers["Authorization"] = f"Basic {auth}"
20
+ return aiohttp.ClientSession(headers=headers, **kwargs)
@@ -15,9 +15,14 @@ there are no store_keys (skips the visual fallback).
15
15
  """
16
16
  import base64
17
17
  import logging
18
+ import os
18
19
 
19
20
  import aiohttp
20
21
 
22
+ from testmu._helpers._http import create_session
23
+
24
+ _AI_API_HOST = os.getenv("TESTMU_AI_API_HOST", "http://localhost:8000")
25
+
21
26
  from testmu import _config
22
27
  from testmu._vars import _variable_store, var
23
28
 
@@ -68,9 +73,9 @@ async def _call_evaluate_api(claim, composite_op, eval_sub_checks):
68
73
  "composite_operator": composite_op,
69
74
  "sub_checks": eval_sub_checks,
70
75
  }
71
- async with aiohttp.ClientSession() as session:
76
+ async with create_session() as session:
72
77
  async with session.post(
73
- "http://localhost:8000/api/v1/evaluate",
78
+ f"{_AI_API_HOST}/api/v1/evaluate",
74
79
  json=request_body,
75
80
  timeout=aiohttp.ClientTimeout(total=60),
76
81
  ) as response:
@@ -82,17 +87,16 @@ async def _call_evaluate_api(claim, composite_op, eval_sub_checks):
82
87
 
83
88
  async def _evaluate_deterministic(claim, composite_op, sub_checks):
84
89
  """Deterministic path: resolve variables then call /api/v1/evaluate."""
85
- _log.debug(f"[AssertionAPI] Deterministic evaluation: '{claim}'")
90
+ _log.info(" [assertion] deterministic claim=%s", claim[:80])
86
91
  eval_sub_checks = await _resolve_sub_checks(sub_checks)
87
- _log.debug(f"[AssertionAPI] resolved sub_checks={eval_sub_checks!r}")
88
92
  result = await _call_evaluate_api(claim, composite_op, eval_sub_checks)
89
- _log.debug(f"[AssertionAPI] result={result!r}")
93
+ _log.info(" [assertion] result status=%s", result.get("status", "unknown"))
90
94
  return result
91
95
 
92
96
 
93
97
  async def _verify_visual(page, claim, composite_op, sub_checks, assertion_tree):
94
98
  """Visual path: screenshot + LLM-based assertion via /api/v1/assertions/verify."""
95
- _log.debug(f"[AssertionAPI] Visual verification: '{claim}'")
99
+ _log.info(" [assertion] visual claim=%s", claim[:80])
96
100
 
97
101
  screenshot_bytes = await page.screenshot()
98
102
  screenshot_b64 = base64.b64encode(screenshot_bytes).decode("utf-8")
@@ -110,10 +114,9 @@ async def _verify_visual(page, claim, composite_op, sub_checks, assertion_tree):
110
114
  if verification:
111
115
  request_body["verification"] = verification
112
116
 
113
- _log.debug("[AssertionAPI] Sending to assertions/verify API...")
114
- async with aiohttp.ClientSession() as session:
117
+ async with create_session() as session:
115
118
  async with session.post(
116
- "http://localhost:8000/api/v1/assertions/verify",
119
+ f"{_AI_API_HOST}/api/v1/assertions/verify",
117
120
  json=request_body,
118
121
  timeout=aiohttp.ClientTimeout(total=60),
119
122
  ) as response:
@@ -122,7 +125,7 @@ async def _verify_visual(page, claim, composite_op, sub_checks, assertion_tree):
122
125
  raise Exception(f"Assertion API error {response.status}: {text}")
123
126
  result = await response.json()
124
127
 
125
- _log.debug(f"[AssertionAPI] Result: {result.get('status', 'unknown')}")
128
+ _log.info(" [assertion] result status=%s", result.get("status", "unknown"))
126
129
  return result
127
130
 
128
131
 
@@ -3,9 +3,9 @@
3
3
  Variable resolution: {{var}} templates in URL, headers, body, and params are
4
4
  resolved via testmu._vars.var() before the request is made.
5
5
 
6
- Proxy routing: when running on HyperExecute (platform is ON), routes all
7
- requests through the local proxy at 127.0.0.1:22000. When local, makes
8
- direct requests.
6
+ Proxy routing: when running on HyperExecute (run_target == "cloud"),
7
+ routes all requests through the local proxy at 127.0.0.1:22000. On local
8
+ run target, makes direct requests.
9
9
 
10
10
  Raises RuntimeError on request failure (network error or bad/unsupported input).
11
11
  Returns the response dict directly on success.
@@ -166,8 +166,8 @@ def _do_request(method, url, headers, body, params, authorization, timeout, veri
166
166
  _log.debug(f"AWS Signature auth failed: {e}")
167
167
 
168
168
  # -- Proxy ---------------------------------------------------------------
169
- # Route through HyperExecute local proxy when on the platform.
170
- proxy = "http://127.0.0.1:22000" if _config.platform else None
169
+ # Route through HyperExecute local proxy only when running on cloud target.
170
+ proxy = "http://127.0.0.1:22000" if _config.run_target == "cloud" else None
171
171
 
172
172
  # -- Build request kwargs ------------------------------------------------
173
173
  _method = method.upper()
@@ -223,7 +223,7 @@ def _do_request(method, url, headers, body, params, authorization, timeout, veri
223
223
  raise RuntimeError(f"Unsupported HTTP method: {_method!r}")
224
224
 
225
225
  elapsed_ms = (time.time() - start) * 1000
226
- _log.debug(f"execute_api response: status={response.status_code}, time={elapsed_ms:.0f}ms")
226
+ _log.info(" [execute_api] status=%d time=%.0fms", response.status_code, elapsed_ms)
227
227
 
228
228
  # -- Build response dict -------------------------------------------------
229
229
  resp = {
@@ -281,8 +281,8 @@ async def execute_api(
281
281
  ):
282
282
  """Execute an HTTP API request.
283
283
 
284
- Routes through the HyperExecute proxy (127.0.0.1:22000) when running on
285
- the LT platform; makes direct requests otherwise.
284
+ Routes through the HyperExecute proxy (127.0.0.1:22000) when
285
+ run_target == "cloud"; makes direct requests on local runs.
286
286
 
287
287
  Variable templates ({{var}}) in url, headers, body, and params are resolved
288
288
  via testmu._vars.var() before the request is made.
@@ -304,7 +304,7 @@ async def execute_api(
304
304
  Raises:
305
305
  RuntimeError: On invalid URL, unsupported method, or request failure.
306
306
  """
307
- _log.debug(f"execute_api: {method} {url}")
307
+ _log.info(" [execute_api] %s %s", method, url[:80])
308
308
  try:
309
309
  return await asyncio.to_thread(
310
310
  _do_request, method, url, headers, body, params,
@@ -1,7 +1,8 @@
1
1
  """execute_db helper — runs a SQL query via the automind /db-query endpoint.
2
2
 
3
- Platform-gated: requires LT_USERNAME + LT_ACCESS_KEY to be set. When the
4
- platform is OFF, raises RuntimeError immediately.
3
+ Auth-gated: requires LT_USERNAME + LT_ACCESS_KEY to be set (automind is an
4
+ LT-hosted service). Works in both local and cloud run targets as long as
5
+ creds are present. Raises RuntimeError when creds are missing.
5
6
 
6
7
  Env vars consumed (when not passed explicitly):
7
8
  LT_USERNAME — LambdaTest username (used to build auth header)
@@ -10,7 +11,7 @@ Env vars consumed (when not passed explicitly):
10
11
  LT_PROXY_TUNNEL_ID — Tunnel ID for network connectivity
11
12
 
12
13
  Raises:
13
- RuntimeError: When platform is off, on non-200 response, or on network failure.
14
+ RuntimeError: When LT creds are missing, on non-200 response, or on network failure.
14
15
 
15
16
  Returns:
16
17
  Query result dict on success.
@@ -71,7 +72,7 @@ async def execute_db(
71
72
  ):
72
73
  """Execute a database query via the automind /db-query endpoint.
73
74
 
74
- Platform-gated: raises RuntimeError when LT credentials are not configured.
75
+ Auth-gated: raises RuntimeError when LT_USERNAME/LT_ACCESS_KEY are not set.
75
76
 
76
77
  Args:
77
78
  query: Base64-encoded SQL query string.
@@ -86,13 +87,13 @@ async def execute_db(
86
87
  Query result dict on success.
87
88
 
88
89
  Raises:
89
- RuntimeError: When platform credentials are missing, on non-200 response,
90
+ RuntimeError: When LT creds are missing, on non-200 response,
90
91
  or on network/query failure.
91
92
  """
92
93
  from testmu import _config
93
94
 
94
- if not _config.platform:
95
- raise RuntimeError("DB query requires LT platform credentials")
95
+ if not _config.lt_auth:
96
+ raise RuntimeError("DB query requires LT_USERNAME and LT_ACCESS_KEY")
96
97
 
97
98
  # Fall back to environment variables
98
99
  if not automind_url:
@@ -105,7 +106,7 @@ async def execute_db(
105
106
  if not tunnel_id:
106
107
  tunnel_id = os.environ.get("LT_PROXY_TUNNEL_ID", "")
107
108
 
108
- _log.debug(f"execute_db: db_id={db_id!r} db_name={db_name!r}")
109
+ _log.info(" [execute_db] db_id=%r db_name=%r", db_id, db_name)
109
110
  try:
110
111
  return await asyncio.to_thread(
111
112
  _do_query, query, db_id, db_name, timeout, tunnel_id, auth_header, automind_url
@@ -35,7 +35,7 @@ async def execute_js(page, script: str):
35
35
  from testmu._vars import _variable_store
36
36
 
37
37
  preview = script[:100] + ("..." if len(script) > 100 else "")
38
- _log.debug(f"execute_js: {preview}")
38
+ _log.info(" [execute_js] %s", preview)
39
39
 
40
40
  try:
41
41
  user_js_code = script + "\n"
@@ -88,7 +88,7 @@ async def execute_js(page, script: str):
88
88
  if result is None or result == "":
89
89
  result = "null"
90
90
 
91
- _log.debug(f"execute_js result: {str(result)[:100]}")
91
+ _log.info(" [execute_js] result=%s", str(result)[:100])
92
92
  return result
93
93
 
94
94
  except RuntimeError:
@@ -1,14 +1,12 @@
1
1
  """Kane CLI helper — execute_kane_cli.
2
2
 
3
- Smart AND platform gated: requires LT credentials for kane-cli auth and
4
- TESTMU_SMART=1 for the feature to be active.
5
-
6
- When smart is OFF: logs "branch unresolved", returns None.
3
+ Gated: smart AND run_target == "cloud". kane-cli needs the HyperExecute
4
+ relay proxy to connect back to the running test, which is only available
5
+ in cloud runs. On local or smart-off runs, logs a warning and returns None.
7
6
 
8
7
  Note: relay proxy disconnect/reconnect is NOT ported here — that's a
9
8
  deferred gap. The subprocess runs kane-cli directly; if relay proxy is
10
- absent the test may fail when kane-cli tries to connect, which is expected
11
- until relay proxy support is ported.
9
+ absent the test may fail when kane-cli tries to connect.
12
10
  """
13
11
  import asyncio
14
12
  import logging
@@ -22,8 +20,8 @@ _log = logging.getLogger("testmu")
22
20
  async def execute_kane_cli(objective: str):
23
21
  """Execute a test objective via kane-cli subprocess.
24
22
 
25
- Smart AND platform gated: when _config.smart is OFF, logs a warning
26
- and returns None without running kane-cli.
23
+ Gated: smart ON AND run_target == "cloud". Logs a warning and returns
24
+ None without running kane-cli otherwise (branch unresolved).
27
25
 
28
26
  Uses env vars:
29
27
  LT_USERNAME — LambdaTest username (required by kane-cli auth)
@@ -42,6 +40,12 @@ async def execute_kane_cli(objective: str):
42
40
  "[execute_kane_cli] smart is OFF — branch unresolved, skipping kane-cli"
43
41
  )
44
42
  return None
43
+ if _config.run_target != "cloud":
44
+ _log.warning(
45
+ "[execute_kane_cli] run_target is not 'cloud' — kane-cli needs the "
46
+ "HyperExecute relay proxy, skipping (branch unresolved)"
47
+ )
48
+ return None
45
49
 
46
50
  username = os.getenv("LT_USERNAME", "")
47
51
  access_key = os.getenv("LT_ACCESS_KEY", "")
@@ -104,13 +104,14 @@ def evaluate_network_assertion(assertion_tree: dict) -> bool:
104
104
 
105
105
 
106
106
  # ---------------------------------------------------------------------------
107
- # network_query — smart-gated, polls HAR server at localhost:8181
107
+ # network_query — cloud-only, polls HAR server at localhost:8181
108
108
  # ---------------------------------------------------------------------------
109
109
 
110
110
  async def network_query(method, url, index, network_log_id="", polling_interval=2, max_polling_time=10):
111
111
  """Poll the HAR server for a matching network entry.
112
112
 
113
- Smart-gated: returns None immediately when _config.smart is OFF.
113
+ Cloud-only: the HAR server at localhost:8181 is a HyperExecute sidecar.
114
+ Returns None when run_target is not "cloud".
114
115
 
115
116
  Args:
116
117
  method: HTTP method to match (e.g. "GET", "POST").
@@ -121,16 +122,17 @@ async def network_query(method, url, index, network_log_id="", polling_interval=
121
122
  max_polling_time: Total seconds before giving up.
122
123
 
123
124
  Returns:
124
- Decoded HAR entry dict, or None when smart is OFF / no match found.
125
+ Decoded HAR entry dict, or None when not on cloud / no match found.
125
126
  """
126
127
  from testmu import _config
127
128
 
128
- if not _config.smart:
129
- _log.info("[network_query] smart is OFFskipping HAR lookup")
129
+ if _config.run_target != "cloud":
130
+ _log.info("[network_query] run_target is not 'cloud' — HAR server unavailable, skipping")
130
131
  return None
131
132
 
132
133
  import asyncio
133
134
  import aiohttp
135
+ from testmu._helpers._http import create_session
134
136
 
135
137
  _har_base = "http://127.0.0.1:8181"
136
138
 
@@ -141,7 +143,7 @@ async def network_query(method, url, index, network_log_id="", polling_interval=
141
143
 
142
144
  if network_log_id:
143
145
  try:
144
- async with aiohttp.ClientSession() as session:
146
+ async with create_session() as session:
145
147
  async with session.get(
146
148
  f"{_har_base}/logs/entry?id={network_log_id}",
147
149
  timeout=aiohttp.ClientTimeout(total=30),
@@ -162,7 +164,7 @@ async def network_query(method, url, index, network_log_id="", polling_interval=
162
164
  num_tries += 1
163
165
  _log.info(f"[network_query] polling attempt {num_tries}/{max_tries}")
164
166
  try:
165
- async with aiohttp.ClientSession() as session:
167
+ async with create_session() as session:
166
168
  async with session.get(
167
169
  f"{_har_base}/logs",
168
170
  timeout=aiohttp.ClientTimeout(total=30),
@@ -187,7 +189,7 @@ async def network_query(method, url, index, network_log_id="", polling_interval=
187
189
  f" entry_id={entry_id!r}"
188
190
  )
189
191
  if entry_id:
190
- async with aiohttp.ClientSession() as s2:
192
+ async with create_session() as s2:
191
193
  async with s2.get(
192
194
  f"{_har_base}/logs/entry?id={entry_id}",
193
195
  timeout=aiohttp.ClientTimeout(total=30),
@@ -1,9 +1,10 @@
1
1
  """SmartUI visual comparison screenshot.
2
2
 
3
3
  Captures a screenshot and sends it to LambdaTest's SmartUI via the
4
- lambdatest_action CDP protocol.
4
+ lambdatest_action CDP protocol — cloud only (CDP channel is intercepted
5
+ server-side by LT infrastructure).
5
6
 
6
- Smart-gated: no-op when TESTMU_SMART is OFF.
7
+ Gated: smart ON AND run_target == "cloud". Warns and no-ops otherwise.
7
8
  """
8
9
  import json
9
10
  import logging
@@ -22,6 +23,12 @@ async def smartui_snapshot(page, name):
22
23
  """
23
24
  if not _config.smart:
24
25
  return None
26
+ if _config.run_target != "cloud":
27
+ _log.warning(
28
+ f"SmartUI snapshot '{name}' skipped — requires cloud run target "
29
+ f"(TESTMU_RUN_TARGET=cloud)"
30
+ )
31
+ return None
25
32
 
26
33
  try:
27
34
  payload = json.dumps({