Skip to content

Commit da5d401

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20321-rls-tags-retire
2 parents e31875c + 862b6ce commit da5d401

28 files changed

Lines changed: 2396 additions & 194 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/cli': minor
3+
'@objectstack/core': minor
4+
'@objectstack/spec': patch
5+
---
6+
7+
`os test` reports the suite and scenario names an author writes, and selects scenarios with `--tags` (#20289)
8+
9+
Clause-②: no
10+
11+
A Quality Protocol suite's `name`, each scenario's `name` and `description`, and scenario `tags` were parsed at load and then read by nothing: the report headed each suite with its file's basename, printed every scenario by its `id`, and `os test --tags critical` failed with `Nonexistent flag: --tags`.
12+
13+
- **Names in the report.** The suite heading is now the suite's `name` followed by its file — `📄 Running suite: Accounts smoke (accounts.test.json)` — and each scenario line is its `name` with the `id` in brackets — `✅ Scenario: An account can be created [acct-create] (12ms)` (the id alone when the two are equal). A failed scenario's `description` is printed under its line, before the error. A suite whose file fails to load is still headed by the file alone, since no name was parsed.
14+
- **`--tags TAG[,TAG...]`** runs only the scenarios carrying AT LEAST ONE of the listed tags (any-of, exact, case-sensitive) — the comma-list reading of Odoo's `--test-tags` and the everyday use of Playwright's `--grep @a|@b`. With the flag, an untagged scenario is left out. Left-out scenarios are **deselected**: not run, counted on the summary (`--tags smoke selected 1 of 4 scenarios; 3 deselected (not run, not counted as passed).`), never counted as passed. A requested tag that no loaded scenario carries is named on the summary. An empty entry (`--tags smoke,`) is refused before anything runs. Without the flag nothing changes: every scenario runs.
15+
- **Exit status.** A selection that matches no scenario takes the posture an empty pattern already has: exit `0` with `No scenario matched --tags …`, and exit `1` under `--fail-on-empty`, whose description now covers both cases. The `Found N test suites.` line and the `SUCCESS: All N scenarios passed.` / `FAILED: …` summary lines keep their spelling.
16+
- **`@objectstack/core`:** `QA.TestResult` gains `scenarioName` and `description` on every result, and `suiteName` on every result `runSuite` produces (absent only from a lone `runScenario` call, which has no suite).
17+
- **`@objectstack/spec`:** `TestScenario.requires` (`params`, `plugins`) is still checked by nothing — its describe() now says **NOT CHECKED** instead of reading as a guard, so a scenario that declares a plugin the target lacks still runs, and the unmet requirement surfaces only as whatever failure it causes, if any. The liveness ledger (`liveness/qa.json`) moves the four keys above to `live`, citing their readers.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/formula': minor
3+
'@objectstack/spec': minor
4+
---
5+
6+
Expression refusals now carry a stable `code` and typed `params` beside their English `message`, so a localized author surface can render its own words: `validateExpression` (every entry in `errors[]` and `warnings[]`), `collectCelRootIdentifiers` (its `ok: false` arm), `predicateSlotRefusal` and `structuralConditionRefusal` (#20291)
7+
8+
Clause-②: yes
9+
10+
Until now the only thing a refusal said was an English sentence, and a designer running in another locale could only show it verbatim, beside its own translated headings. Each refusal now also names its code — one of a closed, kebab-case set — and the values its sentence interpolates, so a consumer keys a catalogue row to the code and fills it from the params. The `message` is the same sentence, byte for byte; nothing is removed or renamed, so no existing reader changes.
11+
12+
- `@objectstack/formula` exports `EXPRESSION_REFUSAL_CODES` (the closed set as a frozen list) and the types `ExpressionRefusalCode`, `ExpressionRefusalParams` (code → params), `ExpressionRefusal`, `ExprValidationCode`, `CelRootsRefusalCode`, `CelFieldRole`, `ExpressionSourceKind` and `CelRootIdentifiersResult`. `ExprValidationError` gains `code` and `params`.
13+
- `@objectstack/spec/automation` exports `FLOW_SLOT_REFUSAL_CODES` (the closed set of both flow-slot refusal producers, as a frozen list) and the types `FlowSlotRefusalCode`, `FlowSlotRefusalParams` (code → params), `PredicateSlotRefusalCode`, `PredicateSlotRefusal`, `PredicateSlotValueKind`, `StructuralConditionRefusalCode`, `StructuralConditionRefusal` and `StructuralConditionValueKind`. `predicateSlotRefusal` returns `PredicateSlotRefusal` and `structuralConditionRefusal` returns `StructuralConditionRefusal`; each is its previous `{ message, source }` plus `code` and `params`.
14+
- Narrowing on `code` narrows `params`. A code never changes once published: a reworded message keeps its code, and a new refusal gets a new one.
15+
- A `detail` param is the CEL or template engine's own diagnostic, in English, passed through as the message carries it.
16+
- These are authoring diagnostics returned as values, not ADR-0112 request error codes, which is why they are kebab-case.
17+
- The `validate_expression` MCP tool forwards `validateExpression`'s `errors` and `warnings` as they are, so each entry in its answer now also carries `code` and `params`.

‎content/docs/deployment/cli.mdx‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1651,6 +1651,7 @@ os test --url http://localhost:4000 # Custom server URL
16511651
os test --token my-api-key # With authentication
16521652
os test 'qa/**/*.test.json' # Recursive — quote it, or the shell expands it first
16531653
os test --fail-on-empty # Matching no suite is a failure, not a pass
1654+
os test --tags smoke,critical # Only scenarios tagged smoke OR critical
16541655
```
16551656
16561657
The pattern accepts `*` (one path segment) and `**` (any number of segments);
@@ -1677,6 +1678,30 @@ switch and reported ✅, so a `contains` against a missing path was a test that
16771678
silently deleted itself. Assert absence with `is_null`; compare a scalar with
16781679
`equals`.
16791680
1681+
The report prints the **names the suite author wrote**. Each suite is headed by
1682+
its `name` and the file it was loaded from —
1683+
`📄 Running suite: Accounts smoke (accounts.test.json)` — and each scenario line
1684+
leads with the scenario's `name` and carries its `id` in brackets:
1685+
`✅ Scenario: An account can be created [acct-create] (12ms)`, or the id alone when
1686+
the two are equal. A failed scenario also prints its `description` under its line,
1687+
before the error, so a red run says what the scenario was checking. A file that is
1688+
refused at load time is headed by its file name alone, because no suite name was
1689+
ever parsed from it.
1690+
1691+
**`--tags` selects scenarios by their `tags`.** Pass a comma-separated list: a
1692+
scenario runs when it carries **at least one** of the listed tags (any-of), so
1693+
`--tags smoke,critical` runs everything tagged `smoke` or `critical`. Matching is
1694+
exact and case-sensitive — a tag is a name, not a pattern — and while the flag is
1695+
given an untagged scenario is never selected. The scenarios the selection leaves
1696+
out are **deselected**: not run, counted on the summary
1697+
(`--tags smoke selected 1 of 4 scenarios; 3 deselected (not run, not counted as passed).`),
1698+
and never counted as passed. A listed tag that no loaded scenario carries is named
1699+
on the summary, since a typo narrows the run without failing it, and an empty entry
1700+
(`--tags smoke,`) is refused before anything runs. Without the flag every scenario
1701+
runs. A scenario's `requires` block is **not checked**: a scenario that names a
1702+
plugin the target does not load still runs, and the unmet requirement surfaces
1703+
only as whatever failure it causes, if any — the scenario can still pass.
1704+
16801705
**A pattern that matches no suite is not a failure by default.** The run prints
16811706
`Found 0 test suites.` — the same machine-readable line a full run prints, so a
16821707
caller can tell "every suite passed" from "there were no suites" — and exits
@@ -1685,6 +1710,10 @@ build. That is a posture, not an oversight, and it has the cost you would expect
16851710
a CI step whose glob stops matching (a renamed directory, a moved suite) reports
16861711
success forever. Pass **`--fail-on-empty`** to opt into the strict reading, where
16871712
an empty match exits 1 (#7848).
1713+
A `--tags` selection that matches no scenario takes the same posture: it prints
1714+
`No scenario matched --tags nightly.` and exits **0**, and **`--fail-on-empty`**
1715+
makes it exit 1 — a renamed tag in a CI step is the same trap as a renamed
1716+
directory.
16881717
16891718
The **record-shaped** action types — `create_record`, `read_record`,
16901719
`update_record`, `delete_record`, `query_records` — **ask the server where the

‎content/docs/references/qa/testing.mdx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ A complete test scenario with setup, execution steps, and teardown
115115
| **setup** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to run before main test (preconditions) |
116116
| **steps** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | ✅ | Main test sequence to execute |
117117
| **teardown** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to cleanup after test execution |
118-
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario |
118+
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario. NOT CHECKED by `os test` or the core TestRunner: the scenario runs whether or not they hold, and an unmet requirement surfaces only as the failure it causes |
119119

120120
### Nested Shape: `TestScenario.setup[number]`
121121

@@ -157,8 +157,8 @@ A single step in a test scenario, consisting of an action and optional assertion
157157

158158
| Property | Type | Required | Description |
159159
| :--- | :--- | :--- | :--- |
160-
| **params** | `string[]` | optional | Required environment variables or parameters |
161-
| **plugins** | `string[]` | optional | Required plugins that must be loaded |
160+
| **params** | `string[]` | optional | Environment variables or parameters the scenario needs. Declared only: nothing checks them before the scenario runs |
161+
| **plugins** | `string[]` | optional | Plugins the scenario needs loaded on the target. Declared only: nothing checks them before the scenario runs |
162162

163163

164164
---
@@ -223,7 +223,7 @@ A complete test scenario with setup, execution steps, and teardown
223223
| **setup** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to run before main test (preconditions) |
224224
| **steps** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | ✅ | Main test sequence to execute |
225225
| **teardown** | `{ name: string; description?: string; action: object; assertions?: object[]; … }[]` | optional | Steps to cleanup after test execution |
226-
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario |
226+
| **requires** | `{ params?: string[]; plugins?: string[] }` | optional | Environment requirements for this scenario. NOT CHECKED by `os test` or the core TestRunner: the scenario runs whether or not they hold, and an unmet requirement surfaces only as the failure it causes |
227227

228228

229229
---

0 commit comments

Comments
 (0)