verifaied 0.31.0.dev78__tar.gz → 0.32.0.dev80__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.
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/PKG-INFO +15 -5
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/README.md +14 -4
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/pyproject.toml +1 -1
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/client.py +51 -10
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/record.py +50 -6
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/testcmd.py +298 -33
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_client.py +118 -1
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_record.py +5 -2
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_testcmd.py +752 -4
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/uv.lock +1 -1
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/.gitignore +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/LICENSE +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/__init__.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/__main__.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/agent_mcp.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/agent_steps.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/analyzer.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/audit.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/cli.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/collect_browser.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/collect_browser.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/config.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/failure_text.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/instrument.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/instrument_snippets.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/instrumenter.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/instrumenter.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/prompts.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/proxy.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/recorder_driver.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/recorder_observer.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/recorder_picker.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/recorder_refs.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/recorder_spec.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/replay_harvest.ts +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/repo_config.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/run_reporter.js +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/runner_dir.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/skill/SKILL.md +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/skill/reference/commands.md +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/skillcmd.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/source_index.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/stepcheck.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/subprocess_util.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/suite_report.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/uploader.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/src/verifaied/varcmd.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/__init__.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/conftest.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_agent_mcp.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_audit.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_browser_settings_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_caption_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_check.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_check_done.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_clear_manual.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_cli.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_collect_browser.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_config.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_console_label_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_instrument.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_instrument_detect.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_instrument_snippets.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_instrumenter.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_pause_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_prompt_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_proxy.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_replay_harvest_runner.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_repo_config.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_skill_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_skillcmd.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_source_index.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_stepcheck_parity.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_suite_report.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_uploader.py +0 -0
- {verifaied-0.31.0.dev78 → verifaied-0.32.0.dev80}/tests/test_varcmd.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifaied
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.32.0.dev80
|
|
4
4
|
Summary: Find what's untested in your code — locally, no account required
|
|
5
5
|
Project-URL: Homepage, https://pypi.org/project/verifaied/
|
|
6
6
|
Author: Kyle Richards
|
|
@@ -1054,11 +1054,18 @@ cloud can stop it. A request that fails is reported with the reason and
|
|
|
1054
1054
|
the studio carries on — one bad pre-step chain is not a reason for it to
|
|
1055
1055
|
die.
|
|
1056
1056
|
|
|
1057
|
+
**Stopping a run from the web.** While a run is replaying, the spinning
|
|
1058
|
+
Run button on the page turns into **Stop**. The studio hears it on its next
|
|
1059
|
+
beat, ends the replay's whole process group (SIGTERM, then SIGKILL), and
|
|
1060
|
+
records the run as failed with "Stopped from the web"; its video and trace
|
|
1061
|
+
are not kept. This uses POSIX process groups (`os.killpg`), so it needs
|
|
1062
|
+
macOS or Linux — the studio does not support Windows.
|
|
1063
|
+
|
|
1057
1064
|
**Branches.** The branch is re-detected on every beat, so switching
|
|
1058
1065
|
branches under a running studio moves what the web shows and what the next
|
|
1059
1066
|
recording lands against. `--branch` pins it instead.
|
|
1060
1067
|
|
|
1061
|
-
**Ctrl-C** does the safe thing at either moment. Mid-recording, the
|
|
1068
|
+
**Ctrl-C** (and SIGTERM, or the terminal closing) does the safe thing at either moment. Mid-recording, the
|
|
1062
1069
|
interrupt is forwarded to the recorder exactly as it is for `test record`:
|
|
1063
1070
|
the browser closes, the spec is saved, the replay runs, the request
|
|
1064
1071
|
completes — and only then does the studio exit. At the idle prompt it
|
|
@@ -1101,9 +1108,12 @@ replay and lays over the file.
|
|
|
1101
1108
|
- `0` — recorded, or replayed cleanly
|
|
1102
1109
|
- `*` — the replay's own exit code when the spec failed (`test run --all`
|
|
1103
1110
|
reports the worst across the run)
|
|
1104
|
-
- `2` — the replay was clean but its final coverage upload failed
|
|
1105
|
-
|
|
1106
|
-
|
|
1111
|
+
- `2` — the replay was clean but its final coverage upload failed, or the
|
|
1112
|
+
service never received its verdict because the session could not be closed
|
|
1113
|
+
(coverage, video and trace uploads and the session's close are each tried
|
|
1114
|
+
three times, 2 s and 6 s apart, on a dropped connection, a timeout or a 5xx
|
|
1115
|
+
— never on a 4xx; a close that still fails prints the session id and the
|
|
1116
|
+
`curl` that records the verdict by hand)
|
|
1107
1117
|
- `1` — usage/config error (no token, bad `--repo`, unknown test, no
|
|
1108
1118
|
Playwright)
|
|
1109
1119
|
|
|
@@ -1008,11 +1008,18 @@ cloud can stop it. A request that fails is reported with the reason and
|
|
|
1008
1008
|
the studio carries on — one bad pre-step chain is not a reason for it to
|
|
1009
1009
|
die.
|
|
1010
1010
|
|
|
1011
|
+
**Stopping a run from the web.** While a run is replaying, the spinning
|
|
1012
|
+
Run button on the page turns into **Stop**. The studio hears it on its next
|
|
1013
|
+
beat, ends the replay's whole process group (SIGTERM, then SIGKILL), and
|
|
1014
|
+
records the run as failed with "Stopped from the web"; its video and trace
|
|
1015
|
+
are not kept. This uses POSIX process groups (`os.killpg`), so it needs
|
|
1016
|
+
macOS or Linux — the studio does not support Windows.
|
|
1017
|
+
|
|
1011
1018
|
**Branches.** The branch is re-detected on every beat, so switching
|
|
1012
1019
|
branches under a running studio moves what the web shows and what the next
|
|
1013
1020
|
recording lands against. `--branch` pins it instead.
|
|
1014
1021
|
|
|
1015
|
-
**Ctrl-C** does the safe thing at either moment. Mid-recording, the
|
|
1022
|
+
**Ctrl-C** (and SIGTERM, or the terminal closing) does the safe thing at either moment. Mid-recording, the
|
|
1016
1023
|
interrupt is forwarded to the recorder exactly as it is for `test record`:
|
|
1017
1024
|
the browser closes, the spec is saved, the replay runs, the request
|
|
1018
1025
|
completes — and only then does the studio exit. At the idle prompt it
|
|
@@ -1055,9 +1062,12 @@ replay and lays over the file.
|
|
|
1055
1062
|
- `0` — recorded, or replayed cleanly
|
|
1056
1063
|
- `*` — the replay's own exit code when the spec failed (`test run --all`
|
|
1057
1064
|
reports the worst across the run)
|
|
1058
|
-
- `2` — the replay was clean but its final coverage upload failed
|
|
1059
|
-
|
|
1060
|
-
|
|
1065
|
+
- `2` — the replay was clean but its final coverage upload failed, or the
|
|
1066
|
+
service never received its verdict because the session could not be closed
|
|
1067
|
+
(coverage, video and trace uploads and the session's close are each tried
|
|
1068
|
+
three times, 2 s and 6 s apart, on a dropped connection, a timeout or a 5xx
|
|
1069
|
+
— never on a 4xx; a close that still fails prints the session id and the
|
|
1070
|
+
`curl` that records the verdict by hand)
|
|
1061
1071
|
- `1` — usage/config error (no token, bad `--repo`, unknown test, no
|
|
1062
1072
|
Playwright)
|
|
1063
1073
|
|
|
@@ -18,11 +18,11 @@ import httpx
|
|
|
18
18
|
|
|
19
19
|
from verifaied.uploader import Payload
|
|
20
20
|
|
|
21
|
-
# Seconds to wait before the second and third attempt of an upload
|
|
22
|
-
# replay's evidence
|
|
23
|
-
# machine has — a laptop roaming between
|
|
24
|
-
# sharing one uplink with five others — and one
|
|
25
|
-
# would otherwise turn a passing run into exit 2.
|
|
21
|
+
# Seconds to wait before the second and third attempt of an upload or of a
|
|
22
|
+
# session's close. A replay's evidence and its verdict are posted once, at
|
|
23
|
+
# the end, over whatever network the machine has — a laptop roaming between
|
|
24
|
+
# access points, a CI runner sharing one uplink with five others — and one
|
|
25
|
+
# dropped connection there would otherwise turn a passing run into exit 2.
|
|
26
26
|
UPLOAD_BACKOFF: tuple[float, ...] = (2.0, 6.0)
|
|
27
27
|
# Indirected so tests can skip the waits.
|
|
28
28
|
_sleep: Callable[[float], None] = time.sleep
|
|
@@ -245,6 +245,11 @@ def finalize_session(
|
|
|
245
245
|
``failure_message`` — what the runner said there — are sent only when
|
|
246
246
|
there is one, so a close from anything without a flowchart is the same
|
|
247
247
|
request it always was.
|
|
248
|
+
|
|
249
|
+
Retried like :func:`upload` on a dropped connection, a timeout or a
|
|
250
|
+
5xx. Safe to repeat: the service keeps the first close's end time and
|
|
251
|
+
writes the verdict again, so a close that landed but whose answer was
|
|
252
|
+
lost is only recorded twice.
|
|
248
253
|
"""
|
|
249
254
|
url = f"{api_url.rstrip('/')}/sessions/{session_id}"
|
|
250
255
|
body: dict[str, Any] = {"ended": True}
|
|
@@ -255,11 +260,13 @@ def finalize_session(
|
|
|
255
260
|
if failure_message is not None:
|
|
256
261
|
body["failure_message"] = failure_message
|
|
257
262
|
try:
|
|
258
|
-
response =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
response = _send_with_retry(
|
|
264
|
+
lambda: httpx.patch(
|
|
265
|
+
url,
|
|
266
|
+
json=body,
|
|
267
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
268
|
+
timeout=timeout,
|
|
269
|
+
)
|
|
263
270
|
)
|
|
264
271
|
except httpx.HTTPError as e:
|
|
265
272
|
raise ApiError(0, f"could not reach {url}: {e}") from e
|
|
@@ -715,6 +722,7 @@ def post_recorder_heartbeat(
|
|
|
715
722
|
kinds: list[str] | None = None,
|
|
716
723
|
environment: str | None = None,
|
|
717
724
|
busy: bool = False,
|
|
725
|
+
working: UUID | None = None,
|
|
718
726
|
timeout: float = 30.0,
|
|
719
727
|
) -> dict[str, Any]:
|
|
720
728
|
"""Say a studio is listening, and collect what to record next.
|
|
@@ -762,6 +770,10 @@ def post_recorder_heartbeat(
|
|
|
762
770
|
body["environment"] = environment
|
|
763
771
|
if busy:
|
|
764
772
|
body["busy"] = True
|
|
773
|
+
if working is not None:
|
|
774
|
+
# Which run this busy studio is on, so the service does not stop it
|
|
775
|
+
# as a run whose studio went away. Sent only when named.
|
|
776
|
+
body["working"] = str(working)
|
|
765
777
|
try:
|
|
766
778
|
response = _http().post(
|
|
767
779
|
request_url,
|
|
@@ -813,6 +825,35 @@ def post_recording_request_status(
|
|
|
813
825
|
raise ApiError(response.status_code, _detail(response))
|
|
814
826
|
|
|
815
827
|
|
|
828
|
+
def cancel_queued_runs(
|
|
829
|
+
*,
|
|
830
|
+
api_url: str,
|
|
831
|
+
token: str,
|
|
832
|
+
repo_id: UUID,
|
|
833
|
+
timeout: float = 10.0,
|
|
834
|
+
) -> int:
|
|
835
|
+
"""Take back this user's queued runs in a repository; how many.
|
|
836
|
+
|
|
837
|
+
The web's "Stop queued runs", called by a studio on its way out: a run
|
|
838
|
+
queued for it is one nothing else will pick up, because presence is
|
|
839
|
+
one studio per user per repository. Runs only, never one a studio has
|
|
840
|
+
already taken — the endpoint's own rule. A short timeout, because the
|
|
841
|
+
caller is a process that has been asked to exit.
|
|
842
|
+
"""
|
|
843
|
+
url = f"{api_url.rstrip('/')}/repositories/{repo_id}/recording-requests"
|
|
844
|
+
try:
|
|
845
|
+
response = httpx.delete(
|
|
846
|
+
url,
|
|
847
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
848
|
+
timeout=timeout,
|
|
849
|
+
)
|
|
850
|
+
except httpx.HTTPError as e:
|
|
851
|
+
raise ApiError(0, f"could not reach {url}: {e}") from e
|
|
852
|
+
if response.status_code >= 400:
|
|
853
|
+
raise ApiError(response.status_code, _detail(response))
|
|
854
|
+
return int(response.json().get("cancelled", 0))
|
|
855
|
+
|
|
856
|
+
|
|
816
857
|
def delete_recorded_test(
|
|
817
858
|
*,
|
|
818
859
|
api_url: str,
|
|
@@ -38,6 +38,7 @@ from typing import Any
|
|
|
38
38
|
from uuid import UUID
|
|
39
39
|
|
|
40
40
|
from rich.console import Console
|
|
41
|
+
from rich.markup import escape
|
|
41
42
|
|
|
42
43
|
from verifaied.client import (
|
|
43
44
|
ApiError,
|
|
@@ -1062,10 +1063,17 @@ def _close_session(
|
|
|
1062
1063
|
err_console: Console,
|
|
1063
1064
|
failed_step: int | None = None,
|
|
1064
1065
|
failure_message: str | None = None,
|
|
1065
|
-
) ->
|
|
1066
|
-
"""Close the session
|
|
1067
|
-
|
|
1068
|
-
|
|
1066
|
+
) -> bool:
|
|
1067
|
+
"""Close the session. Returns whether the service recorded the close.
|
|
1068
|
+
|
|
1069
|
+
The close is tried three times (``finalize_session`` retries a dropped
|
|
1070
|
+
connection, a timeout or a 5xx). If it still fails, one line names the
|
|
1071
|
+
session and the exact request that closes it by hand, and False goes
|
|
1072
|
+
back to the caller: a replay's verdict rides this close, so a caller
|
|
1073
|
+
whose verdict is the point (``test run``) turns it into exit 2 rather
|
|
1074
|
+
than reporting a pass nobody recorded. A caller with nothing riding on
|
|
1075
|
+
it may ignore the answer. With no session there is nothing to close,
|
|
1076
|
+
which is True.
|
|
1069
1077
|
|
|
1070
1078
|
``failed_step`` is which flowchart block a failing replay died on, and
|
|
1071
1079
|
``failure_message`` is what the runner said there. Both default to None
|
|
@@ -1074,7 +1082,7 @@ def _close_session(
|
|
|
1074
1082
|
fail.
|
|
1075
1083
|
"""
|
|
1076
1084
|
if session_id is None:
|
|
1077
|
-
return
|
|
1085
|
+
return True
|
|
1078
1086
|
try:
|
|
1079
1087
|
finalize_session(
|
|
1080
1088
|
api_url=api_url,
|
|
@@ -1085,7 +1093,43 @@ def _close_session(
|
|
|
1085
1093
|
failure_message=failure_message,
|
|
1086
1094
|
)
|
|
1087
1095
|
except ApiError as e:
|
|
1088
|
-
err_console.print(
|
|
1096
|
+
err_console.print(
|
|
1097
|
+
f"[yellow]could not close session {session_id}[/yellow]: "
|
|
1098
|
+
f"{escape(e.message)} — its verdict was not recorded, and the "
|
|
1099
|
+
"service will mark it abandoned. To record it now: "
|
|
1100
|
+
+ escape(
|
|
1101
|
+
_close_command(
|
|
1102
|
+
api_url=api_url,
|
|
1103
|
+
session_id=session_id,
|
|
1104
|
+
exit_code=exit_code,
|
|
1105
|
+
failed_step=failed_step,
|
|
1106
|
+
)
|
|
1107
|
+
),
|
|
1108
|
+
soft_wrap=True,
|
|
1109
|
+
)
|
|
1110
|
+
return False
|
|
1111
|
+
return True
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def _close_command(
|
|
1115
|
+
*, api_url: str, session_id: UUID, exit_code: int, failed_step: int | None
|
|
1116
|
+
) -> str:
|
|
1117
|
+
"""The ``curl`` that closes a session by hand, with the verdict it had.
|
|
1118
|
+
|
|
1119
|
+
The token is left as ``$VERIFAIED_API_TOKEN`` rather than printed: this
|
|
1120
|
+
line lands in CI logs. The failure text is left out — it is free text
|
|
1121
|
+
that would need shell quoting to survive, and the exit code and step
|
|
1122
|
+
are the verdict.
|
|
1123
|
+
"""
|
|
1124
|
+
body: dict[str, Any] = {"ended": True, "exit_code": exit_code}
|
|
1125
|
+
if failed_step is not None:
|
|
1126
|
+
body["failed_step"] = failed_step
|
|
1127
|
+
return (
|
|
1128
|
+
f"curl -X PATCH {api_url.rstrip('/')}/sessions/{session_id} "
|
|
1129
|
+
'-H "Authorization: Bearer $VERIFAIED_API_TOKEN" '
|
|
1130
|
+
"-H 'Content-Type: application/json' "
|
|
1131
|
+
f"-d '{json.dumps(body, separators=(',', ':'))}'"
|
|
1132
|
+
)
|
|
1089
1133
|
|
|
1090
1134
|
|
|
1091
1135
|
def _window_url(
|