@pikku/cli 0.12.89 → 0.12.91
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/README.md +25 -2
- package/console-app/assets/{index-BaOGIr2O.js → index-CpkvmAGb.js} +148 -148
- package/console-app/assets/{index-CSzCJzBb.css → index-D0HG8q0B.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +21 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +6 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +52 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +4 -4
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +73 -13
- package/dist/.pikku/function/pikku-functions.gen.js +9 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +8 -0
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +8 -0
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +4 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.d.ts +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +10 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.json +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.js +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.d.ts +1 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +10 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.js +1 -0
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +9 -1
- package/dist/.pikku/schemas/schemas/DbBaselineInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbCheckInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbCodegenInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/DbExportInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricValidateInput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +19 -0
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +17 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +152 -3
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +39 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +42 -0
- package/dist/src/fabric/fabric-commands.d.ts +9 -3
- package/dist/src/fabric/fabric-commands.js +6 -0
- package/dist/src/fabric/functions/validate.function.d.ts +15 -5
- package/dist/src/fabric/functions/validate.function.js +69 -16
- package/dist/src/fabric/lib/config.d.ts +11 -1
- package/dist/src/fabric/lib/config.js +39 -1
- package/dist/src/fabric/lib/frontend-typecheck.d.ts +46 -0
- package/dist/src/fabric/lib/frontend-typecheck.js +139 -0
- package/dist/src/functions/commands/db-baseline.d.ts +13 -0
- package/dist/src/functions/commands/db-baseline.js +48 -0
- package/dist/src/functions/commands/db-check.d.ts +12 -0
- package/dist/src/functions/commands/db-check.js +64 -0
- package/dist/src/functions/commands/db-codegen.d.ts +11 -0
- package/dist/src/functions/commands/db-codegen.js +39 -0
- package/dist/src/functions/commands/db-export.d.ts +12 -0
- package/dist/src/functions/commands/db-export.js +31 -0
- package/dist/src/functions/commands/db-generate.js +19 -26
- package/dist/src/functions/commands/dev.js +2 -0
- package/dist/src/functions/commands/load-user-project.d.ts +7 -0
- package/dist/src/functions/commands/load-user-project.js +21 -0
- package/dist/src/functions/commands/pikku-command-bootstrap.js +12 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +69 -0
- package/dist/src/functions/commands/scenario-browser.js +60 -0
- package/dist/src/functions/commands/scenario-formatter.d.ts +81 -0
- package/dist/src/functions/commands/scenario-formatter.js +120 -0
- package/dist/src/functions/commands/scenario-ladder.d.ts +66 -0
- package/dist/src/functions/commands/scenario-ladder.js +132 -0
- package/dist/src/functions/commands/scenario-plan.d.ts +61 -0
- package/dist/src/functions/commands/scenario-plan.js +106 -0
- package/dist/src/functions/commands/scenario.d.ts +18 -0
- package/dist/src/functions/commands/scenario.js +305 -38
- package/dist/src/functions/commands/serve.js +2 -0
- package/dist/src/functions/db/db-migrator.d.ts +35 -4
- package/dist/src/functions/db/db-migrator.js +67 -10
- package/dist/src/functions/db/local-db.d.ts +235 -13
- package/dist/src/functions/db/local-db.js +430 -45
- package/dist/src/functions/db/postgres/postgres-introspector.js +19 -1
- package/dist/src/functions/db/postgres/postgres-migrator.d.ts +1 -0
- package/dist/src/functions/db/postgres/postgres-migrator.js +4 -1
- package/dist/src/functions/db/sqlite/sqlite-introspector.js +2 -1
- package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +1 -0
- package/dist/src/functions/db/sqlite/sqlite-migrator.js +6 -1
- package/dist/src/functions/wirings/functions/pikku-command-functions.js +26 -8
- package/dist/src/functions/wirings/functions/serialize-function-types.js +3 -3
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +29 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +58 -0
- package/dist/src/functions/wirings/scenarios/serialize-feature-meta.d.ts +14 -0
- package/dist/src/functions/wirings/scenarios/serialize-feature-meta.js +29 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.d.ts +12 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.js +56 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.d.ts +7 -0
- package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.js +40 -0
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +43 -8
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +13 -2
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.d.ts +4 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +62 -0
- package/dist/src/functions/wirings/workflow/serialize-workflow-bootstrap-map.js +10 -3
- package/dist/src/functions/wirings/workflow/serialize-workflow-map.js +10 -3
- package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +4 -4
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +232 -3
- package/dist/src/server/server-ready.d.ts +12 -0
- package/dist/src/server/server-ready.js +12 -0
- package/dist/src/server/spawn-dev-server.d.ts +50 -0
- package/dist/src/server/spawn-dev-server.js +112 -0
- package/dist/src/services.js +12 -1
- package/dist/src/utils/meta-diff.js +5 -2
- package/dist/src/utils/pikku-cli-config.js +26 -0
- package/dist/src/utils/resolve-scenario-actors.d.ts +23 -0
- package/dist/src/utils/resolve-scenario-actors.js +74 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +16 -6
- package/skills/pikku-i18n/SKILL.md +53 -6
- package/skills/pikku-scenario/SKILL.md +196 -29
|
@@ -5,12 +5,12 @@ description: >-
|
|
|
5
5
|
test coverage. A scenario (pikkuScenario) drives the app the way users do — steps run as actors
|
|
6
6
|
over the real transport against a running server — so a flow doubles as an e2e test and a
|
|
7
7
|
staged/production health check. Covers scenario.do / expectEventually / expectError /
|
|
8
|
-
expectService,
|
|
8
|
+
expectService, declared steps via pikkuScenarioStep (including browser steps driven by
|
|
9
|
+
@pikku/playwright), actors and environments in pikku.config.json, SCENARIO_ACTOR_SECRET, the
|
|
9
10
|
`pikku scenario list|run` commands, live function coverage via `pikku dev --coverage`, and
|
|
10
11
|
plain unit tests for pure function logic. TRIGGER when: user asks about scenarios, testing a
|
|
11
|
-
Pikku function, test coverage, end-to-end flows, or health checks. DO NOT
|
|
12
|
-
asks about
|
|
13
|
-
suite (use Bash), or CI configuration.
|
|
12
|
+
Pikku function, test coverage, end-to-end flows, browser/UI e2e, or health checks. DO NOT
|
|
13
|
+
TRIGGER when: user asks about running an existing test suite (use Bash) or CI configuration.
|
|
14
14
|
installGroups: [core]
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -35,7 +35,7 @@ A scenario is a `pikkuScenario` export that drives the app **as real actors over
|
|
|
35
35
|
Consequences that matter, and bite if ignored:
|
|
36
36
|
|
|
37
37
|
- **There is no state reset.** A scenario runs against a live server. Scope what you create (unique ids, your own rows) and never assume a clean database.
|
|
38
|
-
- **Every
|
|
38
|
+
- **Every effect runs as somebody, or as a declared step.** `scenario.do(...)` without `{ actor }` throws `Scenario tried to run '<rpc>' as an internal step…` — there is no bare internal-RPC step. The other way to do work is `scenario.step/given/when/then`, which runs a `pikkuScenarioStep`; its actor is optional (setup steps have none) unless it declares `browser: true`.
|
|
39
39
|
- **Actors must be configured and signed in**, or the scenario cannot run.
|
|
40
40
|
|
|
41
41
|
Scenarios live in `srcDirectories` like any other function — by convention `*.scenario.ts`.
|
|
@@ -84,24 +84,167 @@ A scenario takes the same config fields as a workflow (`title`, `description`, `
|
|
|
84
84
|
|
|
85
85
|
### The scenario API
|
|
86
86
|
|
|
87
|
-
| Call
|
|
88
|
-
|
|
|
89
|
-
| `scenario.do(step, rpc, data, { actor })`
|
|
87
|
+
| Call | Purpose |
|
|
88
|
+
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `scenario.do(step, rpc, data, { actor })` | Run an RPC as that actor. The step name is what appears in the run output. |
|
|
90
90
|
| `scenario.expectEventually(step, rpc, data, predicate, { actor, within, interval })` | Poll until `predicate(out)` passes or `within` elapses. For anything asynchronous — queues, workers, eventual state. |
|
|
91
|
-
| `scenario.expectError(step, rpc, data, { actor, matches })`
|
|
92
|
-
| `scenario.expectService(step, 'service.method', { actor, calledWith })`
|
|
91
|
+
| `scenario.expectError(step, rpc, data, { actor, matches })` | Assert the call **fails**. For fault injection and negative paths. |
|
|
92
|
+
| `scenario.expectService(step, 'service.method', { actor, calledWith })` | Assert a stubbed service was called. Requires the server to run with `--test`. |
|
|
93
|
+
| `scenario.step(step, stepName, data, { actor })` | Run a declared `pikkuScenarioStep`. `given`/`when`/`then` are the same call with a keyword in the rendered prose. |
|
|
93
94
|
|
|
94
95
|
`expectEventually` is **scenario-only**. Calling it from a `pikkuWorkflowFunc` is a critical inspector error (`PKU675`) pointing you at `pikkuScenario`.
|
|
95
96
|
|
|
96
|
-
Prefer `expectEventually` over sleeping.
|
|
97
|
+
Prefer `expectEventually` over sleeping.
|
|
98
|
+
|
|
99
|
+
### Setup and teardown (`before` / `after`)
|
|
100
|
+
|
|
101
|
+
A scenario config takes `before` and `after`. Both have the **same signature as `func`** — `(services, data, wire)` — with the return value discarded:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const resetsCredentials = async (_services, _data, { actors }) => {
|
|
105
|
+
await actors!.admin!.invoke('resetCredentials', {})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const credentialScenario = pikkuScenario({
|
|
109
|
+
title: 'A credential is loaded on first use',
|
|
110
|
+
tags: ['scenario', 'credential'],
|
|
111
|
+
before: resetsCredentials,
|
|
112
|
+
after: removesInstalledAddon,
|
|
113
|
+
func: async (services, data, { scenario, actors }) => { /* … */ },
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Rule |
|
|
118
|
+
| -------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| `before` throwing skips the body and fails the run — but `after` still runs. |
|
|
120
|
+
| `after` always runs, in a `finally`, whether the scenario passed or failed. |
|
|
121
|
+
| `after` throwing fails a run that would otherwise have passed. |
|
|
122
|
+
| `after` throwing on an already-failed run attaches as the `cause` and never replaces the original error. |
|
|
123
|
+
| Neither runs when the run is suspended or waiting — teardown only fires at a terminal outcome. |
|
|
124
|
+
| Hooks are **not** ladder rows. The runner records nothing for them; a failure is labelled by phase. |
|
|
125
|
+
|
|
126
|
+
A hook reaches the app the same way the body does: through `wire.actors`. If you want cleanup to be *visible* on the ladder, make it an ordinary `scenario.then(...)` instead.
|
|
127
|
+
|
|
128
|
+
Hooks are scenario-only. A `before`/`after` on a `pikkuWorkflowFunc` never runs — a workflow is durable and resumable, so a callback that reran on every replay would have no honest meaning.
|
|
129
|
+
|
|
130
|
+
### Grouping scenarios (`pikkuFeature`)
|
|
131
|
+
|
|
132
|
+
`pikkuFeature` groups scenarios the way gherkin's `Feature:` groups `Scenario:`. Scenarios are referenced by **imported identifier**, so a renamed or deleted scenario is a compile error rather than a silent skip:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { pikkuFeature } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
136
|
+
import {
|
|
137
|
+
credentialLazyLoadScenario,
|
|
138
|
+
credentialRoundTripScenario,
|
|
139
|
+
} from './credential.scenario.js'
|
|
140
|
+
|
|
141
|
+
export const credentialFeature = pikkuFeature({
|
|
142
|
+
name: 'Credential API',
|
|
143
|
+
description: 'Credentials resolve lazily and are scoped per user',
|
|
144
|
+
tags: ['credential'],
|
|
145
|
+
before: startsMockOAuthServer,
|
|
146
|
+
after: stopsMockOAuthServer,
|
|
147
|
+
scenarios: [
|
|
148
|
+
credentialLazyLoadScenario,
|
|
149
|
+
...['stripe', 'google', 'hmac-key'].map((name) => ({
|
|
150
|
+
scenario: credentialRoundTripScenario,
|
|
151
|
+
data: { name },
|
|
152
|
+
})),
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
| Rule |
|
|
158
|
+
| ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| The **export identifier is the feature's id**; `name` is the human-readable label. Both must be exported or the build fails. |
|
|
160
|
+
| A `{ scenario, data }` entry is gherkin's `Examples:` — one run per entry. `data` is typed against that scenario's input. |
|
|
161
|
+
| Feature hooks run **once around the whole group** (`before → a → b → c → after`), _not_ per scenario. `after` runs in a `finally`. |
|
|
162
|
+
| There is deliberately **no `Background:`**. Per-scenario setup is the scenario's own `before`, referencing a shared function. |
|
|
163
|
+
| A scenario's effective tags are its own **plus** the feature's, so `--tags credential` selects through the feature. |
|
|
164
|
+
| A scenario need not belong to a feature — one with no input still runs standalone. |
|
|
165
|
+
| Membership is resolved by **object identity** at runtime, which is why a loop works and why a scenario built inline in a feature is an error. |
|
|
166
|
+
|
|
167
|
+
The **feature is the run unit**: `--flows` on a scenario whose every feature entry carries `data` errors and names the features containing it, because the feature is what supplies that data. Use `--features` for those. A scenario referenced bare anywhere, or in no feature at all, still runs standalone.
|
|
168
|
+
|
|
169
|
+
### Declared steps (`pikkuScenarioStep`)
|
|
170
|
+
|
|
171
|
+
`scenario.do` can only name an RPC. A **step** is a named, typed unit of scenario behaviour whose body is an ordinary pikku function — so it can call several RPCs as its actor, assert, or drive a browser.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { pikkuScenarioStep } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
175
|
+
import { requireActor } from '@pikku/core/workflow'
|
|
176
|
+
|
|
177
|
+
export const buysAnApple = pikkuScenarioStep<
|
|
178
|
+
{ qty: number },
|
|
179
|
+
{ orderId: string }
|
|
180
|
+
>({
|
|
181
|
+
name: 'buysAnApple',
|
|
182
|
+
description: 'buys an apple',
|
|
183
|
+
template: 'buys {qty} apples',
|
|
184
|
+
func: async (_services, { qty }, { scenarioStep }) => {
|
|
185
|
+
return await requireActor(scenarioStep).invoke('placeOrder', { qty })
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
await scenario.given(
|
|
192
|
+
'buys an apple',
|
|
193
|
+
'buysAnApple',
|
|
194
|
+
{ qty: 1 },
|
|
195
|
+
{ actor: actors.shopper }
|
|
196
|
+
)
|
|
197
|
+
// reporter renders: Given the shopper buys 1 apples ✓ 412ms
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Rules that bite:
|
|
201
|
+
|
|
202
|
+
- **The step is referenced by its typed string name, not by importing the const** — exactly like `workflow.do`. The name is the step's `pikkuFuncId` and is checked against the generated step map. A non-literal target is a critical error (`PKU678`).
|
|
203
|
+
- **Steps are not RPCs.** They are deliberately never network-callable — a browser-driving step must not be.
|
|
204
|
+
- **`actor.invoke` is typed over the exposed RPC map**, so the name and the payload are checked and the result comes back narrowed — no cast. `actor.invokeRaw(name, data, { headers })` is the same call reporting `{ status, ok, body }` instead of throwing; use it whenever the refusal *is* the assertion.
|
|
205
|
+
- **`actor` and `env` are optional on the wire**, because a pure assertion step needs neither. Narrow them with `requireActor(scenarioStep)` and `requireScenarioEnv(scenarioStep)` from `@pikku/core/workflow` rather than a local guard — both name the step and say what to pass. `env` is `{ apiUrl, appUrl? }` from the environment the run targets, and is how a raw-HTTP step learns the target's URL: a step runs in the CLI process, where there is no `variables` service and `process.env` is not the answer.
|
|
206
|
+
- **Steps default to `retries: 0`**, unlike ordinary workflow steps. Retrying a failed assertion is wrong; pass `retries` explicitly if a step is genuinely flaky-by-nature.
|
|
207
|
+
- **Step results are persisted**, so return JSON-serialisable data — never a `Locator` or a client object.
|
|
208
|
+
- **`description` documents the step; `template` is what the report renders.** `template`'s `{placeholders}` are filled from the input the step was called with, so one step reads differently for each call — `sees {state} addon {packageName}` reports as "sees available addon @pikku/addon-stripe". Reflect every input field in the template, and type the values so they read as words (`state?: 'installed' | 'available'`, not `installed?: boolean`). A placeholder with no value renders as nothing and the whitespace collapses.
|
|
209
|
+
- Prose precedence is `options.description` → the step's `template` → the step's own `description` → the positional step name. Repeated names get `#1`, `#2` ordinals, so a `for` loop over a data set is how you write a Scenario Outline. A loop-generated step name is not statically known, so it is matched back to its declaration by step function instead — which works as long as that function's call sites agree on their phase, actor and prose. Two call sites that disagree make the loop step report under its bare runtime name.
|
|
210
|
+
|
|
211
|
+
### Browser steps
|
|
212
|
+
|
|
213
|
+
A step declaring `browser: true` gets `wire.browser` — a session bound to **its actor**, signed in through the same `signInPath` + `SCENARIO_ACTOR_SECRET` path the HTTP actors use, so the browser and the RPC calls are one identity. Calling such a step without an actor is a critical error (`PKU677`).
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
export const opensTheCart = pikkuScenarioStep<
|
|
217
|
+
{ path: string },
|
|
218
|
+
{ url: string },
|
|
219
|
+
true
|
|
220
|
+
>({
|
|
221
|
+
name: 'opensTheCart',
|
|
222
|
+
description: 'opens the cart',
|
|
223
|
+
browser: true,
|
|
224
|
+
func: async (_services, { path }, { browser }) => {
|
|
225
|
+
await browser.goto(path)
|
|
226
|
+
return { url: browser.page.url() }
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- Install `@pikku/playwright` and `@playwright/test`, and import `@pikku/playwright` once (`import type {} from '@pikku/playwright'`) so `browser.page` is a typed Playwright `Page`. Without it you still get the structural `goto`/`screenshot` handle.
|
|
232
|
+
- The environment needs an `appUrl` beside its `apiUrl`. `pikku scenario run` fails fast before running anything if a browser scenario has no `appUrl` or the driver is not installed.
|
|
233
|
+
- `pikku scenario run <env> --no-browser` **skips** scenarios containing browser steps and reports them as skipped — it does not fail them. That is how a machine with no browser stays green.
|
|
234
|
+
- Playwright auto-waits; do not wrap `page.click` in `expectEventually`.
|
|
97
235
|
|
|
98
236
|
## Configuration
|
|
99
237
|
|
|
100
|
-
|
|
238
|
+
Personas, actors and environments live in `pikku.config.json`:
|
|
101
239
|
|
|
102
240
|
```json
|
|
103
241
|
{
|
|
104
242
|
"scenarios": {
|
|
243
|
+
"personas": {
|
|
244
|
+
"shopper": { "description": "Buys things here", "primary": true },
|
|
245
|
+
"support": { "description": "Answers for the shop", "proficiency": "power" },
|
|
246
|
+
"reminders": { "description": "The shop chasing abandoned carts", "kind": "system" }
|
|
247
|
+
},
|
|
105
248
|
"actors": {
|
|
106
249
|
"shopper": {
|
|
107
250
|
"email": "shopper@actors.local",
|
|
@@ -109,7 +252,7 @@ Actors and environments live in `pikku.config.json`:
|
|
|
109
252
|
"jobTitle": "First-time buyer",
|
|
110
253
|
"personality": "Impatient shopper who abandons slow checkouts"
|
|
111
254
|
},
|
|
112
|
-
"
|
|
255
|
+
"shopperB": { "persona": "shopper", "email": "shopper-b@actors.local" }
|
|
113
256
|
},
|
|
114
257
|
"environments": {
|
|
115
258
|
"local": {
|
|
@@ -121,23 +264,37 @@ Actors and environments live in `pikku.config.json`:
|
|
|
121
264
|
}
|
|
122
265
|
```
|
|
123
266
|
|
|
267
|
+
### Personas and actors
|
|
268
|
+
|
|
269
|
+
A **persona** is a kind of person; an **actor** is one body that signs in as one. Above, `support` is declared only as a persona — its actor is materialised (`support@actors.local`), so `actors.support` works without an `actors` entry. Write an actor by hand only when you need something the materialised one wouldn't have:
|
|
270
|
+
|
|
271
|
+
- a **real email or personality** for it, like `shopper`;
|
|
272
|
+
- a **second body of the same persona**, like `shopperB` — which is what tenant isolation, peer sharing, and "another member's row" scenarios are made of. Two actors of one persona must be two different users, so **two actors sharing an email is an error**.
|
|
273
|
+
|
|
274
|
+
A persona holds only what is true of that kind of person for the app's whole lifetime — `description`, `primary` (whose experience the product is), `kind`, `proficiency`. What someone is trying to get done, and the circumstances they are doing it in, belong to the **scenario**, not to them.
|
|
275
|
+
|
|
276
|
+
`kind: "system"` is the app acting on its own — a schedule, a cleanup, a send. It gets **no actor**: there is nobody to sign in. Give it one by hand only if it genuinely has a service account.
|
|
277
|
+
|
|
278
|
+
An actor with no `persona` is its own persona, so a project that never declares any keeps working unchanged.
|
|
279
|
+
|
|
124
280
|
- `environments.<name>.apiUrl` is required. `signInPath` defaults to `/auth/sign-in/actor`, `rpcPath` to `/rpc`.
|
|
125
281
|
- **`SCENARIO_ACTOR_SECRET` is an environment variable and never goes in `pikku.config.json`.** It signs actors in. `pikku scenario run` throws without it; a server auto-building actors warns and runs without them.
|
|
126
282
|
|
|
127
283
|
## Running
|
|
128
284
|
|
|
129
285
|
```bash
|
|
130
|
-
pikku scenario list #
|
|
286
|
+
pikku scenario list # features with their scenarios indented, then ungrouped scenarios
|
|
131
287
|
SCENARIO_ACTOR_SECRET=… pikku scenario run local
|
|
132
288
|
SCENARIO_ACTOR_SECRET=… pikku scenario run local --flows orderSupportScenario
|
|
289
|
+
SCENARIO_ACTOR_SECRET=… pikku scenario run local --features credentialFeature
|
|
133
290
|
SCENARIO_ACTOR_SECRET=… pikku scenario run local --tags smoke,scenario
|
|
134
291
|
```
|
|
135
292
|
|
|
136
|
-
`run` takes the environment as a **required positional** — the key from `scenarios.environments`. `--flows`/`-f` filters by scenario name, `--tags`/`-t` by tag (match-any).
|
|
293
|
+
`run` takes the environment as a **required positional** — the key from `scenarios.environments`. `--flows`/`-f` filters by scenario name, `--features` by feature id, `--tags`/`-t` by tag (match-any). Every filter narrows the same plan, so narrowing a feature to two of its five scenarios still runs the feature's hooks exactly once around those two.
|
|
137
294
|
|
|
138
|
-
Output is `PASS <name> (<ms>) → <output>` / `FAIL <name> (<ms>): <error>`, then `N/M scenarios passed against '<env>'`.
|
|
295
|
+
Output is `PASS <name> (<ms>) → <output>` / `FAIL <name> (<ms>): <error>`, then `N/M scenarios passed against '<env>'`. A scenario inside a feature is named `<Feature> › <scenario> <data>`.
|
|
139
296
|
|
|
140
|
-
**Exit code is 1** if any scenario fails
|
|
297
|
+
**Exit code is 1** if any scenario fails _or_ if no scenario matched the filter — a typo'd `--flows` is a hard error, not a silent zero-run pass. It throws outright on an unknown environment, an unknown flow name, or a missing `SCENARIO_ACTOR_SECRET`.
|
|
141
298
|
|
|
142
299
|
## Coverage
|
|
143
300
|
|
|
@@ -160,7 +317,15 @@ SCENARIO_ACTOR_SECRET=… pikku scenario run local --coverage
|
|
|
160
317
|
The run resets coverage before each scenario and snapshots after, writing **`<outDir>/coverage/scenario-coverage.json`**:
|
|
161
318
|
|
|
162
319
|
```jsonc
|
|
163
|
-
{
|
|
320
|
+
{
|
|
321
|
+
"generatedAt": "…",
|
|
322
|
+
"environment": "local",
|
|
323
|
+
"scenarios": {
|
|
324
|
+
"<name>": {
|
|
325
|
+
/* FunctionCoverageReport */
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
}
|
|
164
329
|
```
|
|
165
330
|
|
|
166
331
|
Coverage is best-effort: it disables itself with a warning if the server is not collecting or the first actor cannot invoke, and it needs at least one configured actor. If you get no coverage, check those first.
|
|
@@ -184,7 +349,9 @@ import assert from 'node:assert'
|
|
|
184
349
|
|
|
185
350
|
describe('createTodo', () => {
|
|
186
351
|
test('creates a todo', async () => {
|
|
187
|
-
const services = {
|
|
352
|
+
const services = {
|
|
353
|
+
todoStore: { add: async (title: string) => ({ id: '1', title }) },
|
|
354
|
+
}
|
|
188
355
|
const result = await createTodo.func(services as any, { title: 'Buy milk' })
|
|
189
356
|
assert.equal(result.title, 'Buy milk')
|
|
190
357
|
})
|
|
@@ -199,16 +366,16 @@ Services are plain objects — a Pikku function is pure business logic, so a moc
|
|
|
199
366
|
|
|
200
367
|
## Red flags
|
|
201
368
|
|
|
202
|
-
| Smell
|
|
203
|
-
|
|
|
204
|
-
| `pikku tests …`
|
|
205
|
-
| `.feature` files / Gherkin for function tests | Scenarios are TypeScript, not Gherkin. The in-process cucumber function world was deleted.
|
|
206
|
-
| `scenario.do(...)` with no `{ actor }`
|
|
207
|
-
| A scenario per function
|
|
208
|
-
| Assuming a clean database
|
|
209
|
-
| `sleep()` before asserting
|
|
210
|
-
| `expectEventually` in a `pikkuWorkflowFunc`
|
|
211
|
-
| Coverage silently 0
|
|
369
|
+
| Smell | Why it's wrong |
|
|
370
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
371
|
+
| `pikku tests …` | Removed in #865. Use `pikku scenario`. |
|
|
372
|
+
| `.feature` files / Gherkin for function tests | Scenarios are TypeScript, not Gherkin. The in-process cucumber function world was deleted. |
|
|
373
|
+
| `scenario.do(...)` with no `{ actor }` | Throws. Every step runs as somebody. |
|
|
374
|
+
| A scenario per function | Scenarios are user flows. One flow covers many functions; that is the point. |
|
|
375
|
+
| Assuming a clean database | There is no state reset — it may be a staging server. Scope what you create. |
|
|
376
|
+
| `sleep()` before asserting | Use `expectEventually`. |
|
|
377
|
+
| `expectEventually` in a `pikkuWorkflowFunc` | `PKU675` — scenario-only. |
|
|
378
|
+
| Coverage silently 0 | Server not run with `--coverage`, `verboseMeta` off, `scaffold.scenarios` unset, or no actors configured. |
|
|
212
379
|
|
|
213
380
|
`@pikku/cucumber` is a **browser/e2e** harness (`Actor`, `BrowserWorld`, `PersonaData`, `DbUtils`) — out of scope here.
|
|
214
381
|
|