verifaied 0.30.0.dev77__tar.gz → 0.31.0.dev78__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.30.0.dev77 → verifaied-0.31.0.dev78}/.gitignore +5 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/PKG-INFO +69 -4
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/README.md +68 -3
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/pyproject.toml +1 -1
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/cli.py +97 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/client.py +46 -5
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/skill/reference/commands.md +6 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/testcmd.py +603 -30
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/conftest.py +7 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_cli.py +44 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_client.py +119 -2
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_testcmd.py +951 -33
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/uv.lock +1 -1
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/LICENSE +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/__init__.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/__main__.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/agent_mcp.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/agent_steps.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/analyzer.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/audit.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/collect_browser.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/collect_browser.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/config.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/failure_text.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/instrument.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/instrument_snippets.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/instrumenter.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/instrumenter.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/prompts.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/proxy.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/record.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/recorder_driver.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/recorder_observer.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/recorder_picker.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/recorder_refs.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/recorder_spec.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/replay_harvest.ts +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/repo_config.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/run_reporter.js +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/runner_dir.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/skill/SKILL.md +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/skillcmd.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/source_index.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/stepcheck.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/subprocess_util.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/suite_report.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/uploader.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/src/verifaied/varcmd.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/__init__.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_agent_mcp.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_audit.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_browser_settings_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_caption_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_check.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_check_done.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_clear_manual.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_collect_browser.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_config.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_console_label_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_instrument.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_instrument_detect.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_instrument_snippets.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_instrumenter.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_pause_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_prompt_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_proxy.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_record.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_replay_harvest_runner.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_repo_config.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_skill_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_skillcmd.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_source_index.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_stepcheck_parity.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_suite_report.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_uploader.py +0 -0
- {verifaied-0.30.0.dev77 → verifaied-0.31.0.dev78}/tests/test_varcmd.py +0 -0
|
@@ -25,6 +25,8 @@ frontend/coverage/
|
|
|
25
25
|
# Environment
|
|
26
26
|
**/.env
|
|
27
27
|
**/.env.*
|
|
28
|
+
# The e2e/bt stacks' template carries no secrets and is what the docs point at.
|
|
29
|
+
!e2e/.env.example
|
|
28
30
|
|
|
29
31
|
# Docker
|
|
30
32
|
pgdata/
|
|
@@ -48,6 +50,9 @@ backend/coverage-e2e.json
|
|
|
48
50
|
|
|
49
51
|
# Keys
|
|
50
52
|
*.pem
|
|
53
|
+
# ...except the throwaway GitHub App key the offline test stacks sign with
|
|
54
|
+
# (paired with no App; see its header).
|
|
55
|
+
!e2e/fixtures/github-app/private-key.pem
|
|
51
56
|
|
|
52
57
|
TODO.txt
|
|
53
58
|
TODO.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: verifaied
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.0.dev78
|
|
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
|
|
@@ -476,9 +476,16 @@ verifaied test run --all --env stage
|
|
|
476
476
|
# skips the flow's Pause steps and the holds after its checks.
|
|
477
477
|
verifaied test run "Settings/API tokens" --video watch
|
|
478
478
|
|
|
479
|
+
# On a busy CI machine: replay a failed test once more, from a fresh reset.
|
|
480
|
+
verifaied test run --all --retries 1
|
|
481
|
+
|
|
479
482
|
# What's recorded, and how each last went on this branch.
|
|
480
483
|
verifaied test list
|
|
481
484
|
|
|
485
|
+
# Wrote or pulled a spec by hand? Give every spec on disk its row, its
|
|
486
|
+
# chain and its flowchart. Nothing is deleted; --dry-run sends nothing.
|
|
487
|
+
verifaied test sync
|
|
488
|
+
|
|
482
489
|
# Or leave a recorder running and drive the whole thing from the web.
|
|
483
490
|
verifaied test studio
|
|
484
491
|
```
|
|
@@ -719,6 +726,48 @@ recording of one flow — the composition happens in a throwaway file at run
|
|
|
719
726
|
time, which is what makes editing `Auth/Log in` fix every test that starts
|
|
720
727
|
by logging in. You can also pick pre-steps on the **New test** page.
|
|
721
728
|
|
|
729
|
+
### Hand-written specs — `test sync`
|
|
730
|
+
|
|
731
|
+
A spec doesn't have to come out of the recorder. Every spec names itself
|
|
732
|
+
in a header above its import, and the recorder writes that header on
|
|
733
|
+
every save:
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
// verifaied-test: Settings/API tokens/Create a token
|
|
737
|
+
// verifaied-pre: Login/Paid user
|
|
738
|
+
import { test, expect } from '@playwright/test';
|
|
739
|
+
|
|
740
|
+
test('Create a token', async ({ page }) => {
|
|
741
|
+
await page.getByRole('link', { name: 'Settings' }).click();
|
|
742
|
+
// …
|
|
743
|
+
});
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
`verifaied-test` is the test's `<feature path>/<name>`, once.
|
|
747
|
+
`verifaied-pre` is a pre-step, one line each, in the order they run. Write
|
|
748
|
+
the file yourself — or pull one somebody else wrote — and `verifaied test
|
|
749
|
+
sync` makes verifAIed agree with it: the test is created if it is new,
|
|
750
|
+
marked recorded, given its chain, and its flowchart is drawn on the
|
|
751
|
+
detail page from the file.
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
verifaied test sync # create and update rows from .verifaied/tests/
|
|
755
|
+
verifaied test sync --dry-run # say what would change, send nothing
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Everything is checked before anything is sent, and one problem means
|
|
759
|
+
nothing is sent at all: a spec with no header, a spec that isn't at the
|
|
760
|
+
path its name gives (`.verifaied/tests/<feature path>/<name>.spec.ts`,
|
|
761
|
+
slugged), a pre-step that isn't a spec on disk, a loop, or a test whose
|
|
762
|
+
file verifAIed already has somewhere else — a renamed test keeps the file
|
|
763
|
+
it was recorded to. Pre-steps are synced before the tests that name them,
|
|
764
|
+
so one run is always enough. A test that already agrees with its file
|
|
765
|
+
costs no request.
|
|
766
|
+
|
|
767
|
+
Sync never deletes anything. A test whose spec isn't on disk is listed at
|
|
768
|
+
the end — it may be on another branch, or renamed — so you can delete it
|
|
769
|
+
on the web if it really is gone.
|
|
770
|
+
|
|
722
771
|
### Environments
|
|
723
772
|
|
|
724
773
|
A recorded flow runs against a real app *somewhere*, and somewhere is the
|
|
@@ -1052,7 +1101,9 @@ replay and lays over the file.
|
|
|
1052
1101
|
- `0` — recorded, or replayed cleanly
|
|
1053
1102
|
- `*` — the replay's own exit code when the spec failed (`test run --all`
|
|
1054
1103
|
reports the worst across the run)
|
|
1055
|
-
- `2` — the replay was clean but its final coverage upload failed
|
|
1104
|
+
- `2` — the replay was clean but its final coverage upload failed (coverage,
|
|
1105
|
+
video and trace uploads are each tried three times, 2 s and 6 s apart, on a
|
|
1106
|
+
dropped connection, a timeout or a 5xx — never on a 4xx)
|
|
1056
1107
|
- `1` — usage/config error (no token, bad `--repo`, unknown test, no
|
|
1057
1108
|
Playwright)
|
|
1058
1109
|
|
|
@@ -1082,10 +1133,24 @@ chain leaves the browser, or at `base_url` itself when there is no chain
|
|
|
1082
1133
|
either),
|
|
1083
1134
|
`--description <text>`, `--pre "<Feature>/<Name>"` (repeatable;
|
|
1084
1135
|
replaces the stored chain — omit it to keep what the test already has),
|
|
1085
|
-
and `--no-replay` to skip the replay. `run` takes `--all`
|
|
1086
|
-
|
|
1136
|
+
and `--no-replay` to skip the replay. `run` takes `--all` or `--folder
|
|
1137
|
+
"<folder path>"` in place of a test name, `--video fast|watch|off`, and
|
|
1138
|
+
`--retries <n>` (default 0): a test whose replay **failed** is reset and
|
|
1139
|
+
replayed again, up to `n` more times, and the summary says `passed on
|
|
1140
|
+
retry 1 of 2` when a later attempt passes. Retries are for infrastructure
|
|
1141
|
+
— a sign-in that hung, a page slow to load on a loaded machine — not for a
|
|
1142
|
+
flow that is wrong, which fails every attempt. Each attempt is its own
|
|
1143
|
+
run; the **last** one is the verdict, the coverage and the video and trace
|
|
1144
|
+
the test keeps (a failed attempt that is retried keeps its verdict and its
|
|
1145
|
+
failure text but uploads no video or trace). A refusal — a missing spec, a
|
|
1146
|
+
variable this machine cannot supply, a reset that failed — is never
|
|
1147
|
+
retried, and neither is a clean run whose coverage upload failed (`2`). `studio` takes `--no-replay` and takes no test name at all —
|
|
1087
1148
|
what it records is whatever the web asks for.
|
|
1088
1149
|
|
|
1150
|
+
`sync` takes `--repo`, `--branch` (the branch a new test is stamped with),
|
|
1151
|
+
`--root`, `--api-url`, `--token` and `--dry-run`. It exits `1` when a spec
|
|
1152
|
+
needs fixing (and sends nothing) and `2` when the API refuses a request.
|
|
1153
|
+
|
|
1089
1154
|
## `verifaied skill install` — hand the loop to your agent
|
|
1090
1155
|
|
|
1091
1156
|
The CLI ships the agent skill for using it. One command writes it where a
|
|
@@ -430,9 +430,16 @@ verifaied test run --all --env stage
|
|
|
430
430
|
# skips the flow's Pause steps and the holds after its checks.
|
|
431
431
|
verifaied test run "Settings/API tokens" --video watch
|
|
432
432
|
|
|
433
|
+
# On a busy CI machine: replay a failed test once more, from a fresh reset.
|
|
434
|
+
verifaied test run --all --retries 1
|
|
435
|
+
|
|
433
436
|
# What's recorded, and how each last went on this branch.
|
|
434
437
|
verifaied test list
|
|
435
438
|
|
|
439
|
+
# Wrote or pulled a spec by hand? Give every spec on disk its row, its
|
|
440
|
+
# chain and its flowchart. Nothing is deleted; --dry-run sends nothing.
|
|
441
|
+
verifaied test sync
|
|
442
|
+
|
|
436
443
|
# Or leave a recorder running and drive the whole thing from the web.
|
|
437
444
|
verifaied test studio
|
|
438
445
|
```
|
|
@@ -673,6 +680,48 @@ recording of one flow — the composition happens in a throwaway file at run
|
|
|
673
680
|
time, which is what makes editing `Auth/Log in` fix every test that starts
|
|
674
681
|
by logging in. You can also pick pre-steps on the **New test** page.
|
|
675
682
|
|
|
683
|
+
### Hand-written specs — `test sync`
|
|
684
|
+
|
|
685
|
+
A spec doesn't have to come out of the recorder. Every spec names itself
|
|
686
|
+
in a header above its import, and the recorder writes that header on
|
|
687
|
+
every save:
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
// verifaied-test: Settings/API tokens/Create a token
|
|
691
|
+
// verifaied-pre: Login/Paid user
|
|
692
|
+
import { test, expect } from '@playwright/test';
|
|
693
|
+
|
|
694
|
+
test('Create a token', async ({ page }) => {
|
|
695
|
+
await page.getByRole('link', { name: 'Settings' }).click();
|
|
696
|
+
// …
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
`verifaied-test` is the test's `<feature path>/<name>`, once.
|
|
701
|
+
`verifaied-pre` is a pre-step, one line each, in the order they run. Write
|
|
702
|
+
the file yourself — or pull one somebody else wrote — and `verifaied test
|
|
703
|
+
sync` makes verifAIed agree with it: the test is created if it is new,
|
|
704
|
+
marked recorded, given its chain, and its flowchart is drawn on the
|
|
705
|
+
detail page from the file.
|
|
706
|
+
|
|
707
|
+
```bash
|
|
708
|
+
verifaied test sync # create and update rows from .verifaied/tests/
|
|
709
|
+
verifaied test sync --dry-run # say what would change, send nothing
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
Everything is checked before anything is sent, and one problem means
|
|
713
|
+
nothing is sent at all: a spec with no header, a spec that isn't at the
|
|
714
|
+
path its name gives (`.verifaied/tests/<feature path>/<name>.spec.ts`,
|
|
715
|
+
slugged), a pre-step that isn't a spec on disk, a loop, or a test whose
|
|
716
|
+
file verifAIed already has somewhere else — a renamed test keeps the file
|
|
717
|
+
it was recorded to. Pre-steps are synced before the tests that name them,
|
|
718
|
+
so one run is always enough. A test that already agrees with its file
|
|
719
|
+
costs no request.
|
|
720
|
+
|
|
721
|
+
Sync never deletes anything. A test whose spec isn't on disk is listed at
|
|
722
|
+
the end — it may be on another branch, or renamed — so you can delete it
|
|
723
|
+
on the web if it really is gone.
|
|
724
|
+
|
|
676
725
|
### Environments
|
|
677
726
|
|
|
678
727
|
A recorded flow runs against a real app *somewhere*, and somewhere is the
|
|
@@ -1006,7 +1055,9 @@ replay and lays over the file.
|
|
|
1006
1055
|
- `0` — recorded, or replayed cleanly
|
|
1007
1056
|
- `*` — the replay's own exit code when the spec failed (`test run --all`
|
|
1008
1057
|
reports the worst across the run)
|
|
1009
|
-
- `2` — the replay was clean but its final coverage upload failed
|
|
1058
|
+
- `2` — the replay was clean but its final coverage upload failed (coverage,
|
|
1059
|
+
video and trace uploads are each tried three times, 2 s and 6 s apart, on a
|
|
1060
|
+
dropped connection, a timeout or a 5xx — never on a 4xx)
|
|
1010
1061
|
- `1` — usage/config error (no token, bad `--repo`, unknown test, no
|
|
1011
1062
|
Playwright)
|
|
1012
1063
|
|
|
@@ -1036,10 +1087,24 @@ chain leaves the browser, or at `base_url` itself when there is no chain
|
|
|
1036
1087
|
either),
|
|
1037
1088
|
`--description <text>`, `--pre "<Feature>/<Name>"` (repeatable;
|
|
1038
1089
|
replaces the stored chain — omit it to keep what the test already has),
|
|
1039
|
-
and `--no-replay` to skip the replay. `run` takes `--all`
|
|
1040
|
-
|
|
1090
|
+
and `--no-replay` to skip the replay. `run` takes `--all` or `--folder
|
|
1091
|
+
"<folder path>"` in place of a test name, `--video fast|watch|off`, and
|
|
1092
|
+
`--retries <n>` (default 0): a test whose replay **failed** is reset and
|
|
1093
|
+
replayed again, up to `n` more times, and the summary says `passed on
|
|
1094
|
+
retry 1 of 2` when a later attempt passes. Retries are for infrastructure
|
|
1095
|
+
— a sign-in that hung, a page slow to load on a loaded machine — not for a
|
|
1096
|
+
flow that is wrong, which fails every attempt. Each attempt is its own
|
|
1097
|
+
run; the **last** one is the verdict, the coverage and the video and trace
|
|
1098
|
+
the test keeps (a failed attempt that is retried keeps its verdict and its
|
|
1099
|
+
failure text but uploads no video or trace). A refusal — a missing spec, a
|
|
1100
|
+
variable this machine cannot supply, a reset that failed — is never
|
|
1101
|
+
retried, and neither is a clean run whose coverage upload failed (`2`). `studio` takes `--no-replay` and takes no test name at all —
|
|
1041
1102
|
what it records is whatever the web asks for.
|
|
1042
1103
|
|
|
1104
|
+
`sync` takes `--repo`, `--branch` (the branch a new test is stamped with),
|
|
1105
|
+
`--root`, `--api-url`, `--token` and `--dry-run`. It exits `1` when a spec
|
|
1106
|
+
needs fixing (and sends nothing) and `2` when the API refuses a request.
|
|
1107
|
+
|
|
1043
1108
|
## `verifaied skill install` — hand the loop to your agent
|
|
1044
1109
|
|
|
1045
1110
|
The CLI ships the agent skill for using it. One command writes it where a
|
|
@@ -67,6 +67,7 @@ from verifaied.testcmd import (
|
|
|
67
67
|
run_test_record,
|
|
68
68
|
run_test_run,
|
|
69
69
|
run_test_studio,
|
|
70
|
+
run_test_sync,
|
|
70
71
|
)
|
|
71
72
|
from verifaied.uploader import (
|
|
72
73
|
UploaderError,
|
|
@@ -2053,6 +2054,16 @@ def test_run_command(
|
|
|
2053
2054
|
"replay_type_delay_ms / replay_pauses for this run."
|
|
2054
2055
|
),
|
|
2055
2056
|
),
|
|
2057
|
+
retries: int = typer.Option(
|
|
2058
|
+
0,
|
|
2059
|
+
"--retries",
|
|
2060
|
+
min=0,
|
|
2061
|
+
help=(
|
|
2062
|
+
"Replay a test whose run failed up to this many more times, each "
|
|
2063
|
+
"from a fresh reset. For flaky infrastructure; the last attempt "
|
|
2064
|
+
"is the verdict and coverage recorded. Refusals are never retried."
|
|
2065
|
+
),
|
|
2066
|
+
),
|
|
2056
2067
|
) -> None:
|
|
2057
2068
|
"""Replay a recorded test, capturing video, trace and coverage.
|
|
2058
2069
|
|
|
@@ -2078,6 +2089,10 @@ def test_run_command(
|
|
|
2078
2089
|
reset that fails stops the run; so does a variable this machine
|
|
2079
2090
|
cannot supply, before anything is reset.
|
|
2080
2091
|
|
|
2092
|
+
``--retries N`` replays a test whose run failed up to N more times,
|
|
2093
|
+
each from a fresh reset. The last attempt is the verdict and the
|
|
2094
|
+
coverage the test keeps; a refusal is never retried.
|
|
2095
|
+
|
|
2081
2096
|
Exit codes:
|
|
2082
2097
|
|
|
2083
2098
|
0 every test replayed cleanly
|
|
@@ -2109,6 +2124,7 @@ def test_run_command(
|
|
|
2109
2124
|
proxy_target=proxy_target,
|
|
2110
2125
|
proxy_port=proxy_port,
|
|
2111
2126
|
video=video.value if video is not None else None,
|
|
2127
|
+
retries=retries,
|
|
2112
2128
|
)
|
|
2113
2129
|
except (RecordedTestError, UploaderError) as e:
|
|
2114
2130
|
err_console.print(f"[red]VerifAIed error[/red]: {e}")
|
|
@@ -2323,6 +2339,87 @@ def test_list_command(
|
|
|
2323
2339
|
raise typer.Exit(code=2) from e
|
|
2324
2340
|
|
|
2325
2341
|
|
|
2342
|
+
@test_app.command("sync")
|
|
2343
|
+
def test_sync_command(
|
|
2344
|
+
root: Path | None = typer.Option(
|
|
2345
|
+
None,
|
|
2346
|
+
"--root",
|
|
2347
|
+
help="Repo root whose .verifaied/tests/ is synced (default: cwd).",
|
|
2348
|
+
),
|
|
2349
|
+
dry_run: bool = typer.Option(
|
|
2350
|
+
False,
|
|
2351
|
+
"--dry-run",
|
|
2352
|
+
help="Say what would be created and updated, and send nothing.",
|
|
2353
|
+
),
|
|
2354
|
+
repo: str | None = typer.Option(
|
|
2355
|
+
None,
|
|
2356
|
+
"--repo",
|
|
2357
|
+
"-r",
|
|
2358
|
+
help=(
|
|
2359
|
+
"Repository to sync. Accepts a UUID, an `owner/name` slug, or "
|
|
2360
|
+
"omit to auto-detect from `git remote get-url origin`."
|
|
2361
|
+
),
|
|
2362
|
+
),
|
|
2363
|
+
branch: str | None = typer.Option(
|
|
2364
|
+
None,
|
|
2365
|
+
"--branch",
|
|
2366
|
+
"-b",
|
|
2367
|
+
help=(
|
|
2368
|
+
"Branch a new test is stamped with. Defaults to "
|
|
2369
|
+
"`git branch --show-current`."
|
|
2370
|
+
),
|
|
2371
|
+
),
|
|
2372
|
+
api_url: str | None = typer.Option(
|
|
2373
|
+
None,
|
|
2374
|
+
"--api-url",
|
|
2375
|
+
help=f"Backend base URL. Defaults to ${ENV_API_URL} or {DEFAULT_API_URL}.",
|
|
2376
|
+
),
|
|
2377
|
+
token: str | None = typer.Option(
|
|
2378
|
+
None,
|
|
2379
|
+
"--token",
|
|
2380
|
+
help=f"API token (vr_live_...). Defaults to ${ENV_API_TOKEN}.",
|
|
2381
|
+
),
|
|
2382
|
+
) -> None:
|
|
2383
|
+
"""Create or update a test row for every spec under .verifaied/tests/.
|
|
2384
|
+
|
|
2385
|
+
Each spec names itself in a header above its import —
|
|
2386
|
+
``// verifaied-test: <feature path>/<name>`` — and any tests that
|
|
2387
|
+
replay first, one ``// verifaied-pre: <ref>`` line each, in order. A
|
|
2388
|
+
recorded spec carries the header already; a hand-written one gets its
|
|
2389
|
+
row, its chain and its flowchart from this command. Everything is
|
|
2390
|
+
checked before anything is sent, and a row that already agrees costs
|
|
2391
|
+
no request. Rows are never deleted: one whose spec is missing is
|
|
2392
|
+
listed for you to remove on the web.
|
|
2393
|
+
|
|
2394
|
+
Exit codes:
|
|
2395
|
+
|
|
2396
|
+
0 every spec is synced (or would be, with --dry-run)
|
|
2397
|
+
1 a spec needs fixing first (nothing was sent), or a usage error
|
|
2398
|
+
2 the API refused a request
|
|
2399
|
+
"""
|
|
2400
|
+
resolved_url, resolved_token, repo_id = _test_prelude(
|
|
2401
|
+
repo=repo, api_url=api_url, token=token
|
|
2402
|
+
)
|
|
2403
|
+
try:
|
|
2404
|
+
run_test_sync(
|
|
2405
|
+
api_url=resolved_url,
|
|
2406
|
+
token=resolved_token,
|
|
2407
|
+
repo_id=repo_id,
|
|
2408
|
+
root=root or Path.cwd(),
|
|
2409
|
+
branch=branch,
|
|
2410
|
+
dry_run=dry_run,
|
|
2411
|
+
console=console,
|
|
2412
|
+
)
|
|
2413
|
+
except (RecordedTestError, UploaderError) as e:
|
|
2414
|
+
err_console.print(f"[red]VerifAIed error[/red]: {e}")
|
|
2415
|
+
raise typer.Exit(code=1) from e
|
|
2416
|
+
except ApiError as e:
|
|
2417
|
+
err_console.print(
|
|
2418
|
+
f"[red]VerifAIed error[/red]: {_api_error_text(e, resolved_url)}"
|
|
2419
|
+
)
|
|
2420
|
+
raise typer.Exit(code=2) from e
|
|
2421
|
+
|
|
2422
|
+
|
|
2326
2423
|
@var_app.command("set")
|
|
2327
2424
|
def var_set_command(
|
|
2328
2425
|
name: str = typer.Argument(
|
|
@@ -7,6 +7,8 @@ can be swapped if/when we add a streaming variant for huge uploads.
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import Callable
|
|
10
12
|
from dataclasses import dataclass
|
|
11
13
|
from pathlib import Path
|
|
12
14
|
from typing import Any
|
|
@@ -16,6 +18,36 @@ import httpx
|
|
|
16
18
|
|
|
17
19
|
from verifaied.uploader import Payload
|
|
18
20
|
|
|
21
|
+
# Seconds to wait before the second and third attempt of an upload. A
|
|
22
|
+
# replay's evidence is posted once, at the end, over whatever network the
|
|
23
|
+
# machine has — a laptop roaming between access points, a CI runner
|
|
24
|
+
# sharing one uplink with five others — and one dropped connection there
|
|
25
|
+
# would otherwise turn a passing run into exit 2.
|
|
26
|
+
UPLOAD_BACKOFF: tuple[float, ...] = (2.0, 6.0)
|
|
27
|
+
# Indirected so tests can skip the waits.
|
|
28
|
+
_sleep: Callable[[float], None] = time.sleep
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _send_with_retry(send: Callable[[], httpx.Response]) -> httpx.Response:
|
|
32
|
+
"""Send an upload, retrying what a retry can fix.
|
|
33
|
+
|
|
34
|
+
A connection that failed or timed out, or a 5xx, is tried again after
|
|
35
|
+
each :data:`UPLOAD_BACKOFF` delay; the last attempt's answer — or its
|
|
36
|
+
exception — is what the caller sees. A 4xx is returned at once: the
|
|
37
|
+
request itself is wrong (a bad token, a payload too big) and sending
|
|
38
|
+
it again only repeats the refusal.
|
|
39
|
+
"""
|
|
40
|
+
for delay in UPLOAD_BACKOFF:
|
|
41
|
+
try:
|
|
42
|
+
response = send()
|
|
43
|
+
except httpx.TransportError:
|
|
44
|
+
_sleep(delay)
|
|
45
|
+
continue
|
|
46
|
+
if response.status_code < 500:
|
|
47
|
+
return response
|
|
48
|
+
_sleep(delay)
|
|
49
|
+
return send()
|
|
50
|
+
|
|
19
51
|
|
|
20
52
|
class RepoNotFoundError(Exception):
|
|
21
53
|
"""The owner/name slug isn't connected to verifAIed under this
|
|
@@ -72,7 +104,8 @@ def upload(
|
|
|
72
104
|
|
|
73
105
|
Times out at 60s by default — the synchronous endpoint persists rows
|
|
74
106
|
and re-runs the analyzer inline, so very large repos may push past
|
|
75
|
-
the default httpx 5s. Callers can override for CI use.
|
|
107
|
+
the default httpx 5s. Callers can override for CI use. A dropped
|
|
108
|
+
connection, a timeout or a 5xx is tried twice more, after 2s and 6s.
|
|
76
109
|
|
|
77
110
|
``session_id`` attaches the upload to a session in the evidence ledger;
|
|
78
111
|
``driver`` says who was at the wheel for the implicit session a
|
|
@@ -101,7 +134,9 @@ def upload(
|
|
|
101
134
|
headers = {"Authorization": f"Bearer {token}"}
|
|
102
135
|
|
|
103
136
|
try:
|
|
104
|
-
response =
|
|
137
|
+
response = _send_with_retry(
|
|
138
|
+
lambda: httpx.post(url, json=body, headers=headers, timeout=timeout)
|
|
139
|
+
)
|
|
105
140
|
except httpx.HTTPError as e:
|
|
106
141
|
raise ApiError(0, f"could not reach {url}: {e}") from e
|
|
107
142
|
|
|
@@ -292,21 +327,27 @@ def upload_artifact(
|
|
|
292
327
|
|
|
293
328
|
Streamed as multipart with a generous timeout: a trace archive is tens
|
|
294
329
|
of megabytes and the server hashes it on the way through, so the
|
|
295
|
-
default 60s used elsewhere is too tight to be safe.
|
|
330
|
+
default 60s used elsewhere is too tight to be safe. Retried like
|
|
331
|
+
:func:`upload` on a dropped connection, a timeout or a 5xx.
|
|
296
332
|
"""
|
|
297
333
|
url = f"{api_url.rstrip('/')}/sessions/{session_id}/artifacts"
|
|
298
334
|
data: dict[str, str] = {"kind": kind}
|
|
299
335
|
if label is not None:
|
|
300
336
|
data["label"] = label
|
|
301
|
-
|
|
337
|
+
|
|
338
|
+
def send() -> httpx.Response:
|
|
339
|
+
# Reopened per attempt: a retry has to stream the file from the top.
|
|
302
340
|
with path.open("rb") as handle:
|
|
303
|
-
|
|
341
|
+
return httpx.post(
|
|
304
342
|
url,
|
|
305
343
|
data=data,
|
|
306
344
|
files={"file": (path.name, handle, _media_type(path))},
|
|
307
345
|
headers={"Authorization": f"Bearer {token}"},
|
|
308
346
|
timeout=timeout,
|
|
309
347
|
)
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
response = _send_with_retry(send)
|
|
310
351
|
except OSError as e:
|
|
311
352
|
raise ApiError(0, f"could not read {path}: {e}") from e
|
|
312
353
|
except httpx.HTTPError as e:
|
|
@@ -135,6 +135,12 @@ a normal `@playwright/test` file: read it, edit it, commit it.
|
|
|
135
135
|
steps, then rebuild from there; `--from 0` starts again, `--from <step
|
|
136
136
|
count>` appends
|
|
137
137
|
- `verifaied test studio` — leave a recorder running for the web UI to drive
|
|
138
|
+
- `verifaied test sync` — create or update the row for every spec under
|
|
139
|
+
`.verifaied/tests/`, read off each spec's header (`// verifaied-test:
|
|
140
|
+
<ref>` and one `// verifaied-pre: <ref>` per pre-step, above the import).
|
|
141
|
+
How a hand-written spec gets its row, chain and flowchart. Checks every
|
|
142
|
+
spec before sending anything, never deletes a row, and `--dry-run` sends
|
|
143
|
+
nothing
|
|
138
144
|
|
|
139
145
|
`--env <name>` points any of them at another environment from
|
|
140
146
|
`.verifaied/config.toml`; `--no-replay` skips the replay that a recording
|