trxa-tool 0.0.0-stage → 0.1.1
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.
- package/.claude/skills/replay-trxa-testplan/SKILL.md +627 -0
- package/LICENSE +120 -0
- package/MANUAL.md +548 -0
- package/README.md +67 -3
- package/STUDIO.md +319 -0
- package/dist/analyze.d.ts +58 -0
- package/dist/analyze.js +122 -0
- package/dist/bpm.d.ts +40 -0
- package/dist/bpm.js +60 -0
- package/dist/build-test-script.d.ts +24 -0
- package/dist/build-test-script.js +40 -0
- package/dist/csv.d.ts +1 -0
- package/dist/csv.js +11 -0
- package/dist/extract.d.ts +14 -0
- package/dist/extract.js +34 -0
- package/dist/fixture-schema.d.ts +12 -0
- package/dist/fixture-schema.js +28 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +814 -0
- package/dist/library-archive.d.ts +37 -0
- package/dist/library-archive.js +106 -0
- package/dist/mapping.d.ts +25 -0
- package/dist/mapping.js +390 -0
- package/dist/normalize.d.ts +21 -0
- package/dist/normalize.js +43 -0
- package/dist/parse.d.ts +3 -0
- package/dist/parse.js +233 -0
- package/dist/plan-runner.d.ts +27 -0
- package/dist/plan-runner.js +36 -0
- package/dist/plan-schema.d.ts +27 -0
- package/dist/plan-schema.js +45 -0
- package/dist/planrun-schema.d.ts +45 -0
- package/dist/planrun-schema.js +87 -0
- package/dist/planrun.d.ts +20 -0
- package/dist/planrun.js +46 -0
- package/dist/report-schema.d.ts +83 -0
- package/dist/report-schema.js +89 -0
- package/dist/report.d.ts +42 -0
- package/dist/report.js +70 -0
- package/dist/schema.d.ts +96 -0
- package/dist/schema.js +10 -0
- package/dist/scrub.d.ts +44 -0
- package/dist/scrub.js +166 -0
- package/dist/suggest-inputs.d.ts +19 -0
- package/dist/suggest-inputs.js +92 -0
- package/dist/suite-runner.d.ts +59 -0
- package/dist/suite-runner.js +198 -0
- package/dist/suite-schema.d.ts +59 -0
- package/dist/suite-schema.js +95 -0
- package/dist/testcase-schema.d.ts +102 -0
- package/dist/testcase-schema.js +132 -0
- package/dist/token-usage.d.ts +38 -0
- package/dist/token-usage.js +95 -0
- package/package.json +58 -6
- package/studio/client-dist/assets/index-CgWhGkEn.js +24 -0
- package/studio/client-dist/assets/index-CwAUMVCL.css +1 -0
- package/studio/client-dist/axtr-icon.svg +5 -0
- package/studio/client-dist/favicon.svg +5 -0
- package/studio/client-dist/index.html +14 -0
- package/studio/server/dist/index.js +1938 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
TRXA-TOOL END-USER LICENSE AGREEMENT
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gonzalo Garcia ("Licensor"). All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software (the compiled trxa-tool CLI and its accompanying Studio application, together the
|
|
6
|
+
"Software") is licensed, not sold. This End-User License Agreement ("Agreement") governs your use
|
|
7
|
+
of the Software. By downloading, installing, or using the Software, you agree to be bound by this
|
|
8
|
+
Agreement; if you do not agree, do not download, install, or use it.
|
|
9
|
+
|
|
10
|
+
1. Grant of License
|
|
11
|
+
Subject to your compliance with this Agreement, Licensor grants you a limited, non-exclusive,
|
|
12
|
+
non-transferable, revocable license to install and use the Software for your own internal
|
|
13
|
+
business or personal purposes.
|
|
14
|
+
|
|
15
|
+
2. Restrictions
|
|
16
|
+
You may not, and may not permit anyone else to:
|
|
17
|
+
(a) copy, distribute, sell, sublicense, rent, lease, or otherwise transfer the Software or any
|
|
18
|
+
portion of it to any third party;
|
|
19
|
+
(b) reverse engineer, decompile, disassemble, or otherwise attempt to derive the source code of
|
|
20
|
+
the Software, except to the extent such restriction is prohibited by applicable law;
|
|
21
|
+
(c) modify, adapt, or create derivative works based on the Software;
|
|
22
|
+
(d) remove or obscure any copyright, trademark, or other proprietary notice included with the
|
|
23
|
+
Software;
|
|
24
|
+
(e) use the Software to build a competing product or service.
|
|
25
|
+
|
|
26
|
+
3. Ownership
|
|
27
|
+
The Software is protected by copyright and other intellectual property laws. Licensor retains
|
|
28
|
+
all right, title, and interest in and to the Software, including all intellectual property
|
|
29
|
+
rights in it. No rights are granted to you other than as expressly set out in this Agreement.
|
|
30
|
+
This Agreement grants no rights to use Licensor's trademarks, trade names, product names, or
|
|
31
|
+
logos, and no such rights should be implied from it.
|
|
32
|
+
|
|
33
|
+
4. Support and Services
|
|
34
|
+
This Agreement does not include support, maintenance, updates, or professional services.
|
|
35
|
+
Support and implementation assistance, where offered, are provided separately under their own
|
|
36
|
+
terms and are not part of the license granted here.
|
|
37
|
+
|
|
38
|
+
5. Third-Party Systems
|
|
39
|
+
The Software is designed to interact with systems you configure and control yourself, including
|
|
40
|
+
a Dynamics 365 Finance & Operations environment (via your own connection) and a large language
|
|
41
|
+
model provider you separately account for and pay. Licensor has no control over, and no
|
|
42
|
+
responsibility for, actions those systems take -- including any transaction, record, or change
|
|
43
|
+
made in your D365FO environment as a result of using the Software, whether caused by the
|
|
44
|
+
Software's operation, your own configuration or use of it, or any error, oversight, or
|
|
45
|
+
misunderstanding on your part. You are solely responsible for reviewing what the Software does
|
|
46
|
+
before running it against any production or otherwise consequential environment, and you assume
|
|
47
|
+
all risk and responsibility for any resulting transaction, record, or change.
|
|
48
|
+
|
|
49
|
+
6. No Data Collection
|
|
50
|
+
The Software does not collect, transmit, or share your data with Licensor. It runs entirely
|
|
51
|
+
against infrastructure you configure and control -- your own D365FO environment, your own LLM
|
|
52
|
+
provider account, and your own local files. Licensor has no visibility into your use of the
|
|
53
|
+
Software beyond what you choose to share directly (e.g. when requesting support).
|
|
54
|
+
|
|
55
|
+
7. Early-Stage Software
|
|
56
|
+
The Software is under active, early-stage development (currently versioned below 1.0). Its
|
|
57
|
+
features, behavior, and compatibility with any given D365 Finance & Operations environment or
|
|
58
|
+
LLM provider may change, including in breaking ways, without notice.
|
|
59
|
+
|
|
60
|
+
8. No Warranty
|
|
61
|
+
THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
62
|
+
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
|
|
63
|
+
NONINFRINGEMENT. YOU USE THE SOFTWARE ENTIRELY AT YOUR OWN RISK.
|
|
64
|
+
|
|
65
|
+
9. Limitation of Liability
|
|
66
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, AND REGARDLESS OF THE THEORY OF LIABILITY
|
|
67
|
+
ASSERTED -- WHETHER BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY, OR
|
|
68
|
+
OTHERWISE -- IN NO EVENT SHALL LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
|
|
69
|
+
CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF DATA, REVENUE, OR PROFITS, ARISING OUT OF OR
|
|
70
|
+
IN CONNECTION WITH THIS AGREEMENT OR THE USE OF THE SOFTWARE, INCLUDING ANY ACTION TAKEN IN A
|
|
71
|
+
THIRD-PARTY SYSTEM (SUCH AS A D365FO ENVIRONMENT) AS A RESULT OF USING THE SOFTWARE, EVEN IF
|
|
72
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LICENSOR'S TOTAL AGGREGATE LIABILITY FOR ANY AND ALL
|
|
73
|
+
CLAIMS ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SOFTWARE, INCLUDING ANY DIRECT DAMAGES
|
|
74
|
+
NOT OTHERWISE EXCLUDED ABOVE, SHALL NOT EXCEED THE AMOUNT YOU PAID FOR THE SOFTWARE ITSELF
|
|
75
|
+
(EXCLUDING ANY SEPARATELY-CONTRACTED SUPPORT OR SERVICES UNDER SECTION 4), WHICH FOR THE
|
|
76
|
+
CURRENTLY FREE VERSION IS ZERO.
|
|
77
|
+
|
|
78
|
+
10. Indemnification
|
|
79
|
+
You agree to defend, indemnify, and hold harmless Licensor from and against any claims,
|
|
80
|
+
damages, liabilities, costs, and expenses (including reasonable attorneys' fees) arising out of
|
|
81
|
+
or related to: (a) your use of the Software; (b) your violation of this Agreement; or (c) any
|
|
82
|
+
transaction, record, or change made in a third-party system (including a D365FO environment) as
|
|
83
|
+
a result of your use of the Software.
|
|
84
|
+
|
|
85
|
+
11. Feedback
|
|
86
|
+
If you provide Licensor with any feedback, suggestions, bug reports, or ideas regarding the
|
|
87
|
+
Software, you grant Licensor a perpetual, irrevocable, royalty-free, worldwide right to use
|
|
88
|
+
that feedback for any purpose, without any obligation to compensate or credit you.
|
|
89
|
+
|
|
90
|
+
12. Termination
|
|
91
|
+
This license terminates automatically if you breach any provision of this Agreement. Upon
|
|
92
|
+
termination, you must stop using the Software and destroy all copies in your possession.
|
|
93
|
+
|
|
94
|
+
13. Assignment
|
|
95
|
+
You may not assign or transfer this Agreement or any rights under it without Licensor's prior
|
|
96
|
+
written consent. Licensor may assign this Agreement, in whole or in part, including to a
|
|
97
|
+
successor entity or in connection with a transfer of the Software or Licensor's business,
|
|
98
|
+
without your consent.
|
|
99
|
+
|
|
100
|
+
14. Severability
|
|
101
|
+
If any provision of this Agreement is found unenforceable, that provision will be limited or
|
|
102
|
+
eliminated to the minimum extent necessary, and the remaining provisions will remain in full
|
|
103
|
+
force and effect.
|
|
104
|
+
|
|
105
|
+
15. No Waiver
|
|
106
|
+
Licensor's failure to enforce any provision of this Agreement on any occasion does not waive
|
|
107
|
+
Licensor's right to enforce that or any other provision on any other occasion.
|
|
108
|
+
|
|
109
|
+
16. Modifications
|
|
110
|
+
Licensor may modify, update, or discontinue the Software, and may revise this Agreement for
|
|
111
|
+
future versions of the Software, at any time. Continued use of a version released under revised
|
|
112
|
+
terms constitutes acceptance of those terms for that version.
|
|
113
|
+
|
|
114
|
+
17. Governing Law
|
|
115
|
+
This Agreement is governed by the laws of the State of Texas, USA, without regard to its
|
|
116
|
+
conflict-of-law principles.
|
|
117
|
+
|
|
118
|
+
18. Entire Agreement
|
|
119
|
+
This Agreement is the entire agreement between you and Licensor regarding the Software and
|
|
120
|
+
supersedes any prior agreements or understandings, written or oral.
|
package/MANUAL.md
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# TRXA-TOOL setup & usage manual
|
|
2
|
+
|
|
3
|
+
This is the step-by-step guide for setting up `trxa-tool` and using it to turn a library of
|
|
4
|
+
D365 Finance & Operations Task Recorder (`.axtr`) files into automated, sequenced, data-driven
|
|
5
|
+
tests that LLMs can execute against a real D365FO environment via `d365-erp-mcp`.
|
|
6
|
+
|
|
7
|
+
This document is the "how do I actually run this?" guide. For the browser UI, see
|
|
8
|
+
[STUDIO.md](STUDIO.md).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Mental model
|
|
13
|
+
|
|
14
|
+
Six artifacts make up the whole pipeline, each one building on the last. A raw screen recording
|
|
15
|
+
becomes a generic, mechanical test script; you turn that into a named test case with real inputs
|
|
16
|
+
and pass/fail criteria; concrete test data and a run sequence tie multiple test cases together
|
|
17
|
+
into a suite; related suites can be grouped into a plan for combined reporting; and running a
|
|
18
|
+
suite produces a durable, trackable result instead of something that only ever existed in a chat
|
|
19
|
+
transcript. Here's how those pieces connect:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
📼 .axtr file
|
|
23
|
+
This is the Task Recorder output, a recording file you get from D365FO's Task Recorder, or from an
|
|
24
|
+
LCS Business Process Modeler library export.
|
|
25
|
+
│
|
|
26
|
+
│ trxa parse
|
|
27
|
+
▼
|
|
28
|
+
📋 Test Script (testscripts/*.json)
|
|
29
|
+
This file contains the mechanics: ordered steps, each with a suggested d365-erp-mcp tool call. Fully
|
|
30
|
+
generic, never hardcodes a control name, form name, or business value.
|
|
31
|
+
│
|
|
32
|
+
│ trxa suggest-inputs (optional, drafts a starting point)
|
|
33
|
+
▼
|
|
34
|
+
🧩 Test Case (testcases/*.json)
|
|
35
|
+
This is where you come in: you edit this file and define which values are real test inputs, and what a passing run
|
|
36
|
+
looks like. This is the atomic pass/fail unit.
|
|
37
|
+
│
|
|
38
|
+
│ you also edit one or more...
|
|
39
|
+
▼
|
|
40
|
+
🗃️ Fixture (testdata/*.json)
|
|
41
|
+
These represent the concrete values for a test case's inputs (e.g. "basic" vs. "eu-company"), so the
|
|
42
|
+
same test case can run against different data.
|
|
43
|
+
│
|
|
44
|
+
│ a suite ties a test case + a fixture together, and defines their sequence.
|
|
45
|
+
▼
|
|
46
|
+
🔗 Test Suite (suites/*.json)
|
|
47
|
+
In here you define which test cases run, in what order, and where each one's
|
|
48
|
+
inputs come from (a fixture, a literal, or an earlier test case's output).
|
|
49
|
+
│
|
|
50
|
+
│ optionally, group related suites for combined reporting:
|
|
51
|
+
▼
|
|
52
|
+
📚 Test Plan (plans/*.json)
|
|
53
|
+
You use this to define a list of suites (e.g. every suite in "Accounts
|
|
54
|
+
Receivable"), run and reported on together. No dependencies between suites;
|
|
55
|
+
if two suites need to share data, they belong in one suite instead.
|
|
56
|
+
│
|
|
57
|
+
│ trxa init-run
|
|
58
|
+
▼
|
|
59
|
+
✅ Test Run (testruns/*.json)
|
|
60
|
+
Tracks pass/fail per test case, captured outputs, and
|
|
61
|
+
assertion results. Named `<suiteId>-<runId>.json` so every run is kept, not
|
|
62
|
+
overwritten, and a plan's suites can share one `runId` for combined reporting.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Claude or another LLM client (using the `replay-trxa-testplan` skill) is what actually drives
|
|
66
|
+
the last mile: it asks `trxa resolve-next` what to run, replays that test case's Test
|
|
67
|
+
Script using `d365-erp-mcp` with the resolved concrete inputs, checks its `expectations`, and
|
|
68
|
+
calls `trxa record-result` to persist the outcome.
|
|
69
|
+
|
|
70
|
+
Everything that is deterministic in nature (parsing, DAG ordering, cycle detection, binding
|
|
71
|
+
resolution, plan-level rollups) is driven by code.
|
|
72
|
+
|
|
73
|
+
Everything that requires an actual D365FO session (clicking, typing, querying, judging
|
|
74
|
+
pass/fail) is the LLM's job.
|
|
75
|
+
|
|
76
|
+
## 2. Prerequisites
|
|
77
|
+
|
|
78
|
+
Before you can run a full suite against a live environment, make sure you have each of the
|
|
79
|
+
following in place:
|
|
80
|
+
|
|
81
|
+
- **Node.js** ≥ 18.19 (`node --version`)
|
|
82
|
+
- **[Claude Cowork](https://claude.com/claude-code)** (or another agent runtime capable of
|
|
83
|
+
driving MCP tools). This is what actually executes a test suite
|
|
84
|
+
- **The `d365-erp-mcp` MCP server**, connected to the D365 Finance & Operations environment you
|
|
85
|
+
want to test against, available in the same Claude Cowork session/project.
|
|
86
|
+
- One or more `.axtr` files. Get these either by recording them yourself in D365FO (System
|
|
87
|
+
administration → the Task Recorder add-in), or from an existing LCS Business Process Modeler
|
|
88
|
+
library.
|
|
89
|
+
|
|
90
|
+
## 3. Installation
|
|
91
|
+
|
|
92
|
+
Install the package globally. This puts the `trxa` command on your `PATH`, and every command in
|
|
93
|
+
the rest of this manual is written that way:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
npm install -g trxa-tool
|
|
97
|
+
trxa help # confirms the install and lists every command
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then, in your own project folder (where your recordings, test cases and suites will live), run
|
|
101
|
+
once:
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
trxa init-skill
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Claude Code and Codex discover project skills from their own folders (`.claude/skills/` and
|
|
108
|
+
`.agents/skills/`), not from inside an installed package. `init-skill` copies the
|
|
109
|
+
`replay-trxa-testplan` skill (step 7.7 below) into both, so whichever agent you use finds it.
|
|
110
|
+
|
|
111
|
+
Studio, the browser UI (see [STUDIO.md](STUDIO.md)), is included in the package. There's nothing
|
|
112
|
+
extra to install; start it with `trxa studio`.
|
|
113
|
+
|
|
114
|
+
## 4. Directory layout
|
|
115
|
+
|
|
116
|
+
Directory-by-directory contents are below; which of these get committed to git (and why some are
|
|
117
|
+
deliberately ignored by default) is covered in [Data hygiene](#5-data-hygiene).
|
|
118
|
+
|
|
119
|
+
| Directory | Contents |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `recordings/` | Raw `.axtr` recordings |
|
|
122
|
+
| `testscripts/` | Generated Test Script JSON (output of `parse`) |
|
|
123
|
+
| `testcases/` | Test Case JSON, the reusable, shareable test library |
|
|
124
|
+
| `testdata/` | Fixture JSON, concrete input values |
|
|
125
|
+
| `suites/` | Test Suite JSON, sequencing/DAGs |
|
|
126
|
+
| `plans/` | Test Plan JSON, groups of suites for combined reporting |
|
|
127
|
+
| `testruns/` | Generated Test Run JSON per run |
|
|
128
|
+
|
|
129
|
+
## 5. Data hygiene
|
|
130
|
+
|
|
131
|
+
Read this before uploading a real `.axtr` library. A `.axtr` recording captures whatever the
|
|
132
|
+
user interacted with when the recording was made (real account numbers, journal entries,
|
|
133
|
+
amounts, customer names, etc.). `testscripts/*.json` carries the same data forward, and
|
|
134
|
+
`suggest-inputs`'s draft `example` values are copied verbatim from the recording too.
|
|
135
|
+
|
|
136
|
+
If you keep your project in git, consider:
|
|
137
|
+
|
|
138
|
+
- **Ignoring `recordings/` and `testdata/` by default** in `.gitignore`, and allow-listing only
|
|
139
|
+
files you've reviewed and know to be safe to share (for example, recordings made against
|
|
140
|
+
standard demo data).
|
|
141
|
+
- **Ignoring `testscripts/` and `testruns/` entirely.** Test scripts are fully derived from a
|
|
142
|
+
recording (regenerate them with `parse`), and test runs are generated output.
|
|
143
|
+
- **Reviewing the `example` fields** of any `testcases/*.json` you author (by hand or via
|
|
144
|
+
`suggest-inputs`) before committing. Replace real business data with a placeholder of your
|
|
145
|
+
choice.
|
|
146
|
+
- **Keeping `analysis/` local.** `analyze-library`'s `library-report.json` replaces every
|
|
147
|
+
recorded value with an opaque token, so it's safe to share with a cloud LLM, but
|
|
148
|
+
`scrubbed-values.csv` maps those tokens back to the real values. Don't open it anywhere an LLM
|
|
149
|
+
can read it.
|
|
150
|
+
|
|
151
|
+
## 6. Full CLI reference
|
|
152
|
+
|
|
153
|
+
Run everything as `trxa <command> ...`. Running a command with no further arguments prints its
|
|
154
|
+
specific usage.
|
|
155
|
+
|
|
156
|
+
| Command | Purpose |
|
|
157
|
+
|---|---|
|
|
158
|
+
| `parse <file.axtr> [-o out.testscript.json]` | Extract + normalize a recording into a Test Script |
|
|
159
|
+
| `suggest-inputs <file.axtr> [-o draft.json]` | Draft candidate `inputs` for a Test Case from a recording |
|
|
160
|
+
| `validate-testcase <testcase.json>` | Validate a Test Case file |
|
|
161
|
+
| `validate-fixture <fixture.json>` | Validate a Fixture file |
|
|
162
|
+
| `validate-suite <suite.json>` | Validate a Test Suite, including DAG cycle/unknown-id checks |
|
|
163
|
+
| `plan-suite <suite.json>` | Print a suite's topological execution order |
|
|
164
|
+
| `init-skill [--force]` | Copy the `replay-trxa-testplan` skill into this project's `.claude/skills/` and `.agents/skills/`, so Claude Code and Codex both pick it up automatically -- run this once per project; refuses to overwrite an existing copy at either location unless `--force` is passed |
|
|
165
|
+
| `init-run <suite.json> -o testruns/<suiteId>-<runId>.json` | Create a fresh Test Run, one `"pending"` entry per suite entry |
|
|
166
|
+
| `resolve-next <suite.json> <testrun.json> --testcases-dir <dir> [--fixtures-dir <dir>]` | Get the next runnable test case (inputs already resolved), or `blocked`/`waiting`/`complete` |
|
|
167
|
+
| `measure-tokens --since <isoTimestamp> --until <isoTimestamp> [--transcript <path>]` | Sum real token usage from Claude Code's own session log for a time window (mostly for manual/debugging use -- `record-result` already calls this internally; only reliable when no other Claude Code session is active against this project, see "Token/cost reporting" below) |
|
|
168
|
+
| `record-result <testrun.json> <testCaseId> --status passed\|failed\|blocked --outputs '{...}' [--assertions '[...]']` | Persist a test case's outcome. Every JSON flag has a `-file` form (`--outputs-file <path>`, `--assertions-file`, `--inputs-file`, `--messages-file`); see [Windows PowerShell](#windows-powershell-pass-json-through-files) below |
|
|
169
|
+
| `validate-plan <plan.json>` | Validate a Test Plan, including that every referenced suite exists and is itself valid |
|
|
170
|
+
| `init-plan-run <plan.json> -o runs/` | Create a fresh PlanRun record for a plan, auto-generating its id -- do this once per plan execution, before running any of its suites |
|
|
171
|
+
| `plan-status <plan.json> <run.json> --testruns-dir <dir>` | Aggregate pass/fail across every suite in a plan for one PlanRun |
|
|
172
|
+
| `analyze-library <recordingsDir> [--report ...] [--csv ...]` | Privacy-safe coverage and gap analysis across a whole `recordings/` library (see [Data hygiene](#5-data-hygiene) for its two output files) |
|
|
173
|
+
| `studio` | Start the Studio browser UI in the background and open `http://localhost:5173` (see [STUDIO.md](STUDIO.md)) |
|
|
174
|
+
| `studio stop` | Stop the backgrounded Studio server + client started by `studio` |
|
|
175
|
+
| `export -o <backup.zip>` | Bundle `recordings/`/`testcases/`/`testdata/`/`testscripts/`/`suites/`/`plans/`/`testruns/` into one zip |
|
|
176
|
+
| `import <backup.zip> [--force]` | Unpack a library backup zip; refuses on filename collisions unless `--force` |
|
|
177
|
+
|
|
178
|
+
Every command exits non-zero and prints a specific, descriptive error on invalid input, nothing
|
|
179
|
+
fails silently.
|
|
180
|
+
|
|
181
|
+
### Windows PowerShell: pass JSON through files
|
|
182
|
+
|
|
183
|
+
Windows PowerShell 5.1 can silently corrupt JSON passed inline as a command-line argument before
|
|
184
|
+
`trxa` ever receives it: it strips the double quotes from JSON without spaces, and splits JSON
|
|
185
|
+
with spaces into several arguments. Either way `record-result` fails with a JSON syntax error even
|
|
186
|
+
though the command looks correct.
|
|
187
|
+
|
|
188
|
+
Use the `-file` form of each JSON flag instead: write the JSON to a file and pass its path.
|
|
189
|
+
|
|
190
|
+
```powershell
|
|
191
|
+
[IO.File]::WriteAllText("$PWD\outputs.json", '{"PurchId":"PO-0001"}')
|
|
192
|
+
trxa record-result testruns/my-run.json create-order --status passed --outputs-file outputs.json
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Write the file without a byte-order mark, as above. In PowerShell 5.1, `Set-Content -Encoding utf8`
|
|
196
|
+
and `Out-File` add one, and the JSON then fails to parse.
|
|
197
|
+
|
|
198
|
+
This works the same in every shell, so it's a safe default anywhere. The replay skill already uses
|
|
199
|
+
it.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 7. Step-by-step: building your first test suite
|
|
204
|
+
|
|
205
|
+
This walks through a small two-test-case suite as a self-contained tutorial, using a generic
|
|
206
|
+
`create-order`/`post-packing-slip` example so the steps apply to any scenario.
|
|
207
|
+
|
|
208
|
+
### 7.1 Parse your recording
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
trxa parse recordings/my-recording.axtr -o testscripts/my-recording.testscript.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Open the output and skim `steps[]`. Each step has a `hint`, the suggested `d365-erp-mcp` tool
|
|
215
|
+
call, with a `confidence` of `"high"`, `"medium"`, or `"low"`. Low-confidence steps are almost
|
|
216
|
+
always "find and select a record" steps, because the recording only captured a screen position,
|
|
217
|
+
not a durable business key; those are exactly the steps that need to become test **inputs**.
|
|
218
|
+
|
|
219
|
+
### 7.2 Draft candidate inputs
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
trxa suggest-inputs recordings/my-recording.axtr -o testcases/my-recording.draft.json
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
This scans the Test Script and proposes one candidate per recorded field value
|
|
226
|
+
(`PropertyUserAction`) and one per low-confidence record-selection step. Each candidate has a
|
|
227
|
+
`bindTo` telling you exactly which step/control it plugs into, plus (for field values) the
|
|
228
|
+
recorded `example`; review and scrub that before it goes anywhere shared. This draft is *not*
|
|
229
|
+
a valid Test Case yet; it's the `inputs` array to copy into one.
|
|
230
|
+
|
|
231
|
+
### 7.3 Author the Test Case
|
|
232
|
+
|
|
233
|
+
Create `testcases/<your-id>.json`:
|
|
234
|
+
|
|
235
|
+
```jsonc
|
|
236
|
+
{
|
|
237
|
+
"id": "create-order", // unique within a suite
|
|
238
|
+
"name": "Select customer and open their sales order",
|
|
239
|
+
"description": "...",
|
|
240
|
+
"testScriptPath": "../testscripts/my-recording.testscript.json", // relative to this file
|
|
241
|
+
"inputs": [
|
|
242
|
+
{
|
|
243
|
+
"name": "customerAccount", // referenced by suite inputBindings
|
|
244
|
+
"description": "...",
|
|
245
|
+
"example": "US-001", // optional, from suggest-inputs
|
|
246
|
+
"required": true,
|
|
247
|
+
"bindTo": {
|
|
248
|
+
"kind": "grid-row-selector", // or "kind": "control" + "controlName"
|
|
249
|
+
"stepSequence": 21,
|
|
250
|
+
"note": "In the list, find and select the desired record."
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"outputs": [
|
|
255
|
+
{
|
|
256
|
+
"name": "salesOrderId",
|
|
257
|
+
"kind": "odata", // preferred when there's a solid business key to filter by
|
|
258
|
+
"entitySet": "SalesOrderHeadersV2",
|
|
259
|
+
"filter": "CustomerAccount eq '{{customerAccount}}'", // {{name}} = resolved input value
|
|
260
|
+
"field": "SalesOrderNumber"
|
|
261
|
+
}
|
|
262
|
+
// alternative, e.g. for a value only visible on screen (an auto-assigned ID Task Recorder
|
|
263
|
+
// never captured because nobody clicked it) -- free at replay time, no extra query:
|
|
264
|
+
// { "name": "...", "kind": "uiField", "afterStepSequence": N, "controlName": "..." }
|
|
265
|
+
// if the control name isn't known yet, use "hint" instead (plain English); the replaying LLM
|
|
266
|
+
// resolves it against live form state and writes the exact controlName back for future runs:
|
|
267
|
+
// { "name": "...", "kind": "uiField", "afterStepSequence": N, "hint": "..." }
|
|
268
|
+
],
|
|
269
|
+
"expectations": [
|
|
270
|
+
{
|
|
271
|
+
"description": "No error dialog appeared",
|
|
272
|
+
"checkAfterStepSequence": 46,
|
|
273
|
+
"kind": "uiMessage", // fallback when there's no entity to query
|
|
274
|
+
"severity": "Error",
|
|
275
|
+
"pattern": "", // plain substring match; empty = any message of this severity
|
|
276
|
+
"shouldExist": false
|
|
277
|
+
}
|
|
278
|
+
// preferred alternative:
|
|
279
|
+
// { "description": "...", "checkAfterStepSequence": N, "kind": "odata", "entitySet": "...",
|
|
280
|
+
// "filter": "...", "expectedField": "...", "expectedValue": "...", "comparator": "equals" }
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Field reference:
|
|
286
|
+
|
|
287
|
+
| Field | Type | Notes |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| `id`, `name`, `description` | `string` | `id` must be unique within any suite that references it |
|
|
290
|
+
| `testScriptPath` | `string` | Path to the Test Script JSON, relative to this file |
|
|
291
|
+
| `inputs[].bindTo.kind` | `"control"` \| `"grid-row-selector"` | `"control"` needs `controlName`; `"grid-row-selector"` needs a `note` (why/what to search for) |
|
|
292
|
+
| `outputs[].kind` | `"odata"` \| `"uiField"` | `"odata"` needs `entitySet`/`filter`/`field`; `"uiField"` needs `afterStepSequence` plus at least one of `controlName` (exact, free at replay time) or `hint` (plain English, for when the control name isn't known -- the replaying LLM resolves it against live form state and is expected to write the resolved `controlName` back) |
|
|
293
|
+
| `expectations[].kind` | `"odata"` \| `"uiMessage"` | `"odata"` needs `entitySet`/`filter`/`expectedField`/`expectedValue`/`comparator` (`equals`\|`notEquals`\|`contains`\|`greaterThan`\|`lessThan`); `"uiMessage"` needs `severity` (`Info`\|`Warning`\|`Error`)/`pattern` (plain substring of the message text, not a regex -- empty matches any message of that severity)/`shouldExist` (should a matching message be present, not whether a record exists) |
|
|
294
|
+
|
|
295
|
+
`filter` and `expectedValue` may contain `{{inputName}}` placeholders; Claude substitutes the
|
|
296
|
+
resolved input value before querying, as plain string substitution (not a templating engine).
|
|
297
|
+
|
|
298
|
+
Validate as you go:
|
|
299
|
+
|
|
300
|
+
```sh
|
|
301
|
+
trxa validate-testcase testcases/create-order.json
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 7.4 Author a Fixture (concrete test data)
|
|
305
|
+
|
|
306
|
+
Create `testdata/<your-id>.<fixture-name>.json`:
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
{
|
|
310
|
+
"testCaseId": "create-order",
|
|
311
|
+
"name": "basic",
|
|
312
|
+
"values": { "customerAccount": "US-001" }
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
You can have multiple fixtures per test case (e.g. `basic`, `eu-company`); the suite picks
|
|
317
|
+
which one to use per run. The fixture is not part of the test case itself; it's chosen at the
|
|
318
|
+
suite level (see [Author the Test Suite](#75-author-the-test-suite)), which is what lets the
|
|
319
|
+
same Test Case run against different data.
|
|
320
|
+
|
|
321
|
+
```sh
|
|
322
|
+
trxa validate-fixture testdata/create-order.basic.json
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### 7.5 Author the Test Suite
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"id": "packing-slip-flow",
|
|
330
|
+
"name": "Create order then post its packing slip",
|
|
331
|
+
"entries": [
|
|
332
|
+
{
|
|
333
|
+
"testCaseId": "create-order",
|
|
334
|
+
"dependsOn": [],
|
|
335
|
+
"inputBindings": {
|
|
336
|
+
"customerAccount": { "kind": "fixture", "fixtureName": "basic", "param": "customerAccount" }
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"testCaseId": "post-packing-slip",
|
|
341
|
+
"dependsOn": ["create-order"],
|
|
342
|
+
"inputBindings": {
|
|
343
|
+
"salesOrderId": { "kind": "testCaseOutput", "testCaseId": "create-order", "outputName": "salesOrderId" }
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`inputBindings` per param, one of:
|
|
351
|
+
|
|
352
|
+
| `kind` | Fields | Meaning |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| `"fixture"` | `fixtureName`, `param` | Pull a value from a named Fixture's `values` |
|
|
355
|
+
| `"testCaseOutput"` | `testCaseId`, `outputName` | Pull a value from an **already-passed** upstream test case's captured output; this is what threads data through a sequence |
|
|
356
|
+
| `"literal"` | `value` | A hardcoded value right in the suite file (fine for constants, not for real business data) |
|
|
357
|
+
| `"autogenerated"` | *(none)* | No value here at all -- `resolve-next` reports this param in `autogeneratedParams` (with the input's own `description`/`example` as guidance) instead of resolving it, and the orchestrating LLM replaying the suite invents a value at that point, typically for a business key that just needs to be unique per run |
|
|
358
|
+
|
|
359
|
+
An entry can also declare `runIf` to skip it conditionally, e.g. added to `post-packing-slip`
|
|
360
|
+
above:
|
|
361
|
+
|
|
362
|
+
```json
|
|
363
|
+
"runIf": { "testCaseId": "create-order", "outputName": "currency", "comparator": "equals", "value": "USD" }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`testCaseId` must be one of this entry's own `dependsOn` -- by the time `runIf` is evaluated that
|
|
367
|
+
dependency has already passed, so the condition compares its captured *output*, not its status
|
|
368
|
+
(which would always be `"passed"` here, so checking it would be pointless). A false condition
|
|
369
|
+
records the entry as `"skipped"` instead of running it -- not a pass, not a failure, and (like
|
|
370
|
+
`"blocked"`) a downstream entry that depends on a skipped one is itself reported `"blocked"`, not
|
|
371
|
+
left waiting forever.
|
|
372
|
+
|
|
373
|
+
```sh
|
|
374
|
+
trxa validate-suite suites/packing-slip-flow.json
|
|
375
|
+
trxa plan-suite suites/packing-slip-flow.json
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
`validate-suite` also runs the topological sort, so it'll immediately tell you about a cycle or
|
|
379
|
+
a `dependsOn` referencing a `testCaseId` that doesn't exist in the suite. (`plan-suite` here
|
|
380
|
+
means "print this suite's execution plan", a pre-existing command name that predates, and is
|
|
381
|
+
unrelated to, the Test Plan concept covered in
|
|
382
|
+
[Grouping suites into a Test Plan](#8-grouping-suites-into-a-test-plan).)
|
|
383
|
+
|
|
384
|
+
### 7.6 Start a run
|
|
385
|
+
|
|
386
|
+
```sh
|
|
387
|
+
trxa init-run suites/packing-slip-flow.json -o testruns/packing-slip-flow-$(date +%s).json
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 7.7 Run it: hand this to your LLM Client (Claude Cowork)
|
|
391
|
+
|
|
392
|
+
Open a Claude Cowork session with `d365-erp-mcp` connected, and ask it to run the suite (invoking
|
|
393
|
+
the `replay-trxa-testplan` skill; it will pick this up automatically from context, or you can
|
|
394
|
+
say so explicitly: *"use the replay-trxa-testplan skill to run suites/packing-slip-flow.json
|
|
395
|
+
against testruns/packing-slip-flow-....json"*).
|
|
396
|
+
|
|
397
|
+
What the LLM actually does, per test case, in a loop (see the skill's "Running as part of a Test
|
|
398
|
+
Suite" section for the authoritative version):
|
|
399
|
+
|
|
400
|
+
1. `resolve-next` → get the next test case with inputs already resolved (or `blocked`/`waiting`/`complete`)
|
|
401
|
+
2. Replay its `testScriptPath` per the normal per-step procedure, substituting each `bindTo`'d
|
|
402
|
+
input with its resolved value instead of the recorded one
|
|
403
|
+
3. Evaluate `expectations` (OData query preferred, UI message fallback)
|
|
404
|
+
4. Capture `outputs` the same way
|
|
405
|
+
5. `record-result` with status + outputs + assertion results
|
|
406
|
+
6. Repeat until `resolve-next` reports `"complete"`
|
|
407
|
+
|
|
408
|
+
### 7.8 Read the result
|
|
409
|
+
|
|
410
|
+
```sh
|
|
411
|
+
cat testruns/packing-slip-flow-....json
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`results[testCaseId].status` is `"passed"`, `"failed"`, `"blocked"` (an upstream dependency
|
|
415
|
+
failed or was skipped), or `"skipped"` (its own `runIf` evaluated false -- see §7.5). `outputs`
|
|
416
|
+
shows what was captured. `assertionResults` shows each expectation's actual observed value; this
|
|
417
|
+
is your expected-vs-actual audit trail, not something living only in a
|
|
418
|
+
chat transcript.
|
|
419
|
+
|
|
420
|
+
### Token/cost reporting
|
|
421
|
+
|
|
422
|
+
Each result can optionally carry `tokensUsed` (a raw token count) and/or `costUsd` (a dollar
|
|
423
|
+
figure). Neither is computed by this CLI itself -- it never talks to the model, so it has no
|
|
424
|
+
ground truth of its own -- but they aren't guesses either:
|
|
425
|
+
|
|
426
|
+
- `tokensUsed` is measured automatically -- `record-result` calls `measure-tokens` internally
|
|
427
|
+
whenever it isn't passed `--tokens` explicitly and the test case has a known start time, no
|
|
428
|
+
action needed from the orchestrating LLM or the person driving the CLI by hand. It reads the
|
|
429
|
+
real `usage` data Claude Code writes to `~/.claude/projects/.../<session>.jsonl` on every turn
|
|
430
|
+
and sums it for the window between this test case's `"running"` transition and the
|
|
431
|
+
`record-result` call.
|
|
432
|
+
|
|
433
|
+
**Treat this as a rough order-of-magnitude signal, not a trustworthy number.** Live-tested
|
|
434
|
+
2026-08-20 by running two suites headlessly in parallel: the reported total was inflated 24% to
|
|
435
|
+
98% (nearly double) versus each session's own authoritative usage total, because the underlying
|
|
436
|
+
heuristic (it reads whichever session log was modified most recently) occasionally attributes another Claude Code session's turns to this one whenever more
|
|
437
|
+
than one session is active against this project directory at the same time. That's not a rare
|
|
438
|
+
edge case -- it's close to the default way this actually gets used (parallel suites, or a Desktop
|
|
439
|
+
session left open while a background job runs). If you need a real number instead of a rough
|
|
440
|
+
signal, a `claude -p --output-format json` invocation reports its own authoritative
|
|
441
|
+
`total_cost_usd`/`usage` for exactly that invocation, with no cross-session risk at all -- that's
|
|
442
|
+
a materially better source than this field, if you're already running headlessly.
|
|
443
|
+
If no transcript can be found at all, `tokensUsed` is simply left unset for that result rather
|
|
444
|
+
than guessed. This is a deliberate Claude-Code-only bonus, not a core requirement -- the whole
|
|
445
|
+
`resolve-next`/`record-result` pipeline and the replay skill's instructions are meant to work
|
|
446
|
+
with any sufficiently capable agentic LLM driving them, and no part of pass/fail or captured outputs
|
|
447
|
+
depends on this field. Running under a different agent, or under Claude Cowork (which executes
|
|
448
|
+
on Anthropic's own servers rather than the local machine), simply leaves it unset -- there's no
|
|
449
|
+
local session transcript to read either way, which is expected, not a bug.
|
|
450
|
+
- `costUsd` is left unset in the interactive-skill execution mode described in this manual --
|
|
451
|
+
there's no dollar figure worth trusting without a real, current, cache-aware pricing table, and
|
|
452
|
+
this CLI has no business maintaining one. The field exists for a future execution mode (e.g.
|
|
453
|
+
headless `claude -p --output-format json`, which reports `total_cost_usd` itself) to populate
|
|
454
|
+
directly once that path exists.
|
|
455
|
+
|
|
456
|
+
Studio's Test Runs page shows both, per test case and aggregated across a whole run, whenever at
|
|
457
|
+
least one result reports them -- see [STUDIO.md](STUDIO.md).
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## 8. Grouping suites into a Test Plan
|
|
462
|
+
|
|
463
|
+
Once you have more than one suite covering a related business area (e.g. "Accounts Receivable"
|
|
464
|
+
spanning customer onboarding, order-to-cash, and collections), a Test Plan groups them for
|
|
465
|
+
combined scheduling/reporting without adding a dependency graph between suites (if two suites
|
|
466
|
+
truly need to share data, put those test cases in one suite instead).
|
|
467
|
+
|
|
468
|
+
Create `plans/<your-id>.json`:
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"id": "accounts-receivable",
|
|
473
|
+
"name": "Accounts Receivable regression pack",
|
|
474
|
+
"description": "...",
|
|
475
|
+
"suites": [
|
|
476
|
+
{ "suiteId": "packing-slip-flow", "suitePath": "../suites/packing-slip-flow.json" }
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`suitePath` is relative to the plan file, same convention as a Test Case's `testScriptPath`.
|
|
482
|
+
Array order is just the suggested run order; there's no `dependsOn` at the plan level.
|
|
483
|
+
|
|
484
|
+
```sh
|
|
485
|
+
trxa validate-plan plans/accounts-receivable.json
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
This resolves and validates every referenced suite too (catching a typo'd path or a broken
|
|
489
|
+
suite immediately), not just the plan's own shape.
|
|
490
|
+
|
|
491
|
+
**Running a plan** starts with one `init-plan-run` call to get a PlanRun record (its `id` is what
|
|
492
|
+
ties every suite's Test Run together), then the same per-suite loop from
|
|
493
|
+
[Run it: hand this to your LLM Client](#77-run-it-hand-this-to-your-llm-client-claude-cowork),
|
|
494
|
+
repeated once per suite in the plan:
|
|
495
|
+
|
|
496
|
+
```sh
|
|
497
|
+
trxa init-plan-run plans/accounts-receivable.json -o runs/
|
|
498
|
+
# prints the generated PlanRun, e.g. { "id": "2026-08-25T14-30-00.000Z", "planId": "accounts-receivable", ... }
|
|
499
|
+
runId=2026-08-25T14-30-00.000Z
|
|
500
|
+
trxa init-run suites/packing-slip-flow.json -o testruns/packing-slip-flow-$runId.json
|
|
501
|
+
# ... run that suite to completion via the skill, same as step 7.7 ...
|
|
502
|
+
# ... repeat init-run + the skill loop for each other suite in the plan, same $runId ...
|
|
503
|
+
trxa plan-status plans/accounts-receivable.json runs/$runId.json --testruns-dir testruns
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
`plan-status` reads `testruns/<suiteId>-<runId>.json` for every suite in the plan and reports a
|
|
507
|
+
rollup: `overall` is `"all-passed"`, `"has-failures"`, `"incomplete"` (a suite hasn't finished
|
|
508
|
+
running yet), or `"missing-runs"` (a suite hasn't been run at all for this PlanRun), plus a
|
|
509
|
+
per-suite breakdown. It's a pure, read-only query; nothing is persisted beyond the individual
|
|
510
|
+
suites' own Test Run files, so you can call it as often as you like while suites are still in
|
|
511
|
+
progress.
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## 9. Troubleshooting
|
|
516
|
+
|
|
517
|
+
| Symptom | Cause | Fix |
|
|
518
|
+
|---|---|---|
|
|
519
|
+
| `validate-suite` throws `has a dependency cycle involving: ...` | Two entries depend on each other (directly or transitively) | Fix `dependsOn` in the listed entries |
|
|
520
|
+
| `validate-suite` throws `depends on unknown testCaseId "..."` | Typo in `dependsOn`, or referenced entry missing from `entries[]` | Add the entry or fix the id |
|
|
521
|
+
| `validate-suite` throws `runIf references "..." which must be one of its own dependsOn` | A `runIf.testCaseId` isn't in that entry's own `dependsOn` | Add it to `dependsOn`, or point `runIf` at a test case that's already a direct dependency |
|
|
522
|
+
| `resolveInputs` throws `no inputBindings entry for required param "..."` | A Test Case input has no matching entry in the suite's `inputBindings` | Add a binding for that param name in the suite file |
|
|
523
|
+
| `resolveInputs` throws `has not passed yet` | A `testCaseOutput` binding points at a test case that hasn't run (or failed) | Check `dependsOn` includes that test case, and that it actually passed |
|
|
524
|
+
| `resolve-next` reports `"blocked"` | An upstream test case failed, or was itself skipped | Fix the upstream failure, or accept that dependent is blocked for this run |
|
|
525
|
+
| `resolve-next` reports `"skipped"` | An entry's `runIf` evaluated false against its dependency's captured output | Not an error -- this is the conditional-skip feature working as intended; nothing to fix |
|
|
526
|
+
| `resolve-next` reports `"waiting"` | A test case is stuck in `"running"` status without a matching `record-result` call | Finish that test case's `record-result` call |
|
|
527
|
+
| A step's hint has `confidence: "low"` | The recording captured a row position, not a business key | Expected. This is exactly what a Test Case input with `bindTo.kind: "grid-row-selector"` exists to solve |
|
|
528
|
+
| `validate-plan` throws `entry declares suiteId "..." but ... has id "..."` | The `suiteId` in a plan entry doesn't match the `id` inside the suite file it points at | Fix whichever one is wrong; they must match |
|
|
529
|
+
| `plan-status` reports a suite as `"missing"` | No `testruns/<suiteId>-<runId>.json` exists for that PlanRun's id | You haven't run that suite yet for this PlanRun; `init-run` + run it, using the same id as the rest of the plan |
|
|
530
|
+
| `plan-status` throws `Plan run at ... belongs to plan "..."` | The `run.json` passed in was created via `init-plan-run` against a *different* plan | Pass the PlanRun file that was actually created for this plan |
|
|
531
|
+
|
|
532
|
+
## 10. Best practices
|
|
533
|
+
|
|
534
|
+
- Never hand-guess business data during a replay; if a value should vary per run, it belongs in
|
|
535
|
+
a Fixture, not typed in on the fly.
|
|
536
|
+
- Prefer `kind: "odata"` for `expectations`/`outputs` whenever the outcome maps to a queryable
|
|
537
|
+
entity: it's far more robust than parsing UI text, and it's what actually proved the concept
|
|
538
|
+
(an inventory-shortage defect was caught this way during the initial dry run).
|
|
539
|
+
- Run `validate-testcase`/`validate-fixture`/`validate-suite`/`validate-plan` before every test
|
|
540
|
+
run; they're free and catch authoring mistakes deterministically instead of burning through
|
|
541
|
+
tokens on a live D365FO session because of a typo.
|
|
542
|
+
- Review `example` values and any literal test data before committing; see
|
|
543
|
+
[Data hygiene](#5-data-hygiene).
|
|
544
|
+
- One Test Case per distinct business scenario, reused across multiple suites/fixtures as
|
|
545
|
+
needed, rather than one giant do-everything test case.
|
|
546
|
+
- Reach for a Test Plan once you have several suites covering one business area, rather than
|
|
547
|
+
running each suite ad hoc; it's free composition/reporting with no added authoring cost per
|
|
548
|
+
suite.
|