@pikku/core 0.12.115 → 0.12.117
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/CHANGELOG.md +40 -0
- package/dist/types/core.types.d.ts +2 -0
- package/dist/wirings/addon/wire-addon.d.ts +12 -0
- package/dist/wirings/addon/wire-remote-addon.d.ts +2 -0
- package/dist/wirings/channel/channel.types.d.ts +1 -0
- package/dist/wirings/mcp/mcp.types.d.ts +18 -0
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +29 -2
- package/dist/wirings/workflow/pikku-scenario-service.js +54 -4
- package/dist/wirings/workflow/scenario-run.types.d.ts +9 -0
- package/dist/wirings/workflow/scenario-step.types.d.ts +12 -1
- package/dist/wirings/workflow/scenario.types.d.ts +1 -1
- package/package.json +1 -1
- package/src/public-surface.json +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
## 0.12.117
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 8a0ecb7: `pikku scenario run <env> --run browser --strict` now refuses every route off the run surface, rather than only reporting one at the end. An action step that would fall back to its `default` binding throws `ScenarioNoSurfaceBinding` even when it declares `default`, and a `then` witnessed only server-side throws the new `ScenarioUnwitnessedAssertion`. Without `--strict` nothing changes: the fallback still runs and the unwitnessed assertion is still counted into the coverage line.
|
|
6
|
+
|
|
7
|
+
It exists because a run is becoming a source of documentation. A docs build runs the suite `--run browser --strict`, so a feature that cannot be driven end to end through the UI cannot produce a page — no flow where three steps are screenshots and the fourth quietly happened over RPC with nothing to show. The refusal names the step, the surface the run asked for and the surfaces that did bind it, which turns "what still has no browser binding" into a worklist the run prints rather than something to go looking for.
|
|
8
|
+
|
|
9
|
+
`--strict` on `--run default` is accepted and does nothing, since nothing on the default surface can fall back or be witnessed elsewhere. `PikkuScenarioService.setRunSurface` takes the flag as a second argument, and `isStrictSurface()` reads it back.
|
|
10
|
+
|
|
11
|
+
## 0.12.116
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 87971bd: `browser.screenshot('the order, confirmed', { showcase: true })` marks one shot as fit to publish outside the run, and the artifact ledger carries the flag. A gallery, a docs page or a marketing card can then be built from the scenario run itself instead of a second browser pass configured somewhere else, and the author of the step — the only one who knows the page is at a moment worth showing a stranger — is who decides.
|
|
16
|
+
|
|
17
|
+
Each filed screenshot also carries an `id`: the same shot under one key across runs. `path` leads with the order the run happened in, so inserting a step ahead of a shot renumbers it and anything meant to outlive one run (a caption override, a diff against last week's build) loses track of it.
|
|
18
|
+
|
|
19
|
+
Two supporting fixes in `@pikku/playwright`: contexts open at a pinned `viewport` (1440x900, overridable per config or via `E2E_VIEWPORT_WIDTH`/`E2E_VIEWPORT_HEIGHT`) and screenshots are taken with animations disabled, so two runs of the same scenario photograph the same thing. `{ fullPage: true }` is available for shots of a whole scrollable page.
|
|
20
|
+
|
|
21
|
+
- b312867: A project can now serve several MCP endpoints, one per connector.
|
|
22
|
+
|
|
23
|
+
Until now every MCP tool in a project was pooled onto a single `/mcp`, so a hub offering three connectors offered one endpoint listing all three connectors' tools at once. A client pointed at it saw tools it had no business calling, and the only way to give a connector an endpoint of its own was to give it a deployment of its own — three deploys, three bills, three service graphs.
|
|
24
|
+
|
|
25
|
+
`wireAddon` gains `mcpEndpoint`. `true` serves that instance's tools at `/mcp/<name>`; a string is the path, used as given. Leaving it unset keeps the tools on the shared endpoint, which is where they have always been, so nothing existing moves.
|
|
26
|
+
|
|
27
|
+
A surfaced instance now gets its own manifest (`.pikku/mcp/mcp.<name>.gen.json`, carrying the path it answers on), its own deploy unit (`mcp-<name>`, routed on that path), and its own MCP server — with its own tool list, so a client pointed at one endpoint never sees another's tools. The plumbing for the per-surface manifest and unit already existed in `deploy apply`; nothing had ever produced one.
|
|
28
|
+
|
|
29
|
+
`pikku dev` mounts every endpoint the generated tree describes, not just the default one. Without that a project that moved its tools onto their own endpoints would have served nothing locally at all — the default manifest it reads is empty precisely because they moved — and the only way to try a connector would have been to deploy it.
|
|
30
|
+
|
|
31
|
+
The node and bun transports take `mcpSurfaces` alongside `mcpJson` and mount each at its own path, longest path first. `/mcp` claims everything beneath `/mcp/`, so without that ordering the default endpoint answers `/mcp/weather` and the surface's tools are unreachable.
|
|
32
|
+
|
|
33
|
+
OAuth discovery is split between the endpoints rather than duplicated across them. RFC 9728 folds a resource's path into its well-known route, so each endpoint's own document is already distinct, but the path-less `/.well-known/oauth-protected-resource` predates that and describes whichever resource answers it. Only the default endpoint claims it — otherwise every unit registers the same route and the provider's router decides which resource a client is told about, and in dev a client probing it is described whichever surface sorted first.
|
|
34
|
+
|
|
35
|
+
- 51bd35a: Document five public keys that carried no JSDoc: `wireChannel`'s `onDisconnect`,
|
|
36
|
+
`wireRemoteAddon`'s `serverUrl` and `tags`, and `CoreUserSession`'s `userId` and
|
|
37
|
+
`orgId`. A key printed as a name and a type is a shape; what a caller needs is
|
|
38
|
+
what to put in it, and only the JSDoc where the type is declared carries that
|
|
39
|
+
into the IDE, the console and the shipped surface doc at once.
|
|
40
|
+
|
|
1
41
|
## 0.12.115
|
|
2
42
|
|
|
3
43
|
### Patch Changes
|
|
@@ -81,7 +81,9 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
|
|
|
81
81
|
postgres?: PostgresConfig;
|
|
82
82
|
} & Config;
|
|
83
83
|
export interface CoreUserSession {
|
|
84
|
+
/** Who the session belongs to, as your own system identifies them. Pikku only carries it. */
|
|
84
85
|
userId?: string;
|
|
86
|
+
/** The tenant the session is acting inside, when the project has more than one. */
|
|
85
87
|
orgId?: string;
|
|
86
88
|
/** True when the session belongs to a synthetic scenario actor — lets audits/analytics address synthetic traffic */
|
|
87
89
|
actor?: boolean;
|
|
@@ -16,6 +16,18 @@ export type WireAddonConfig = {
|
|
|
16
16
|
* and is typed against the addon's function names.
|
|
17
17
|
*/
|
|
18
18
|
mcp?: boolean | string[];
|
|
19
|
+
/**
|
|
20
|
+
* Serves this addon's MCP tools on an endpoint of their own rather than
|
|
21
|
+
* folding them into the project's single `/mcp`. `true` mounts them at
|
|
22
|
+
* `/mcp/<name>`; a string is the path, used as given.
|
|
23
|
+
*
|
|
24
|
+
* This is what lets one project expose several connectors: each wired
|
|
25
|
+
* instance becomes its own MCP server, with its own tool list, so a client
|
|
26
|
+
* pointed at one never sees another's tools. Leaving it unset keeps the
|
|
27
|
+
* addon's tools on the shared endpoint, which is where they have always
|
|
28
|
+
* been.
|
|
29
|
+
*/
|
|
30
|
+
mcpEndpoint?: boolean | string;
|
|
19
31
|
/** Filters this addon in and out of a build — see the `tags` option on `pikku all`. It has no effect at runtime. */
|
|
20
32
|
tags?: string[];
|
|
21
33
|
/** Required of every function in the addon, on top of the function's own. */
|
|
@@ -12,11 +12,13 @@ export type WireRemoteAddonConfig = {
|
|
|
12
12
|
name: string;
|
|
13
13
|
/** Must be installed as a devDependency — `pikku verify` enforces this. */
|
|
14
14
|
package: string;
|
|
15
|
+
/** Where the addon is deployed, e.g. `https://registry.example.com`. A function when it varies per environment. */
|
|
15
16
|
serverUrl: string | ((services: CoreServices) => string | Promise<string>);
|
|
16
17
|
/** Omit when the addon declares its remote surface public. */
|
|
17
18
|
auth?: RemoteAddonAuth;
|
|
18
19
|
/** Map a consumer-facing fn name → the remote fn name, when they differ (rare). */
|
|
19
20
|
remoteName?: (fn: string) => string;
|
|
21
|
+
/** Applied to every function the addon contributes, so tag middleware and permissions reach them. */
|
|
20
22
|
tags?: string[];
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
@@ -68,6 +68,7 @@ export type CoreChannel<ChannelData, Channel extends string, ChannelConnect = Co
|
|
|
68
68
|
func?: ChannelConnect;
|
|
69
69
|
middleware?: PikkuMiddleware[];
|
|
70
70
|
};
|
|
71
|
+
/** Runs once after the socket closes, however it closed. Nothing it returns reaches the client. */
|
|
71
72
|
onDisconnect?: ChannelDisconnect | {
|
|
72
73
|
func?: ChannelDisconnect;
|
|
73
74
|
middleware?: PikkuMiddleware[];
|
|
@@ -21,12 +21,24 @@ export type MCPResourceMeta = Record<string, Omit<CoreMCPResource, 'func' | 'mid
|
|
|
21
21
|
inputSchema: string | null;
|
|
22
22
|
outputSchema: string | null;
|
|
23
23
|
middleware?: MiddlewareMetadata[];
|
|
24
|
+
/**
|
|
25
|
+
* The MCP endpoint this belongs to, when the project serves more than one.
|
|
26
|
+
* Absent means the project's default endpoint. Codegen splits the metadata
|
|
27
|
+
* by this key, so each endpoint's manifest lists only its own.
|
|
28
|
+
*/
|
|
29
|
+
surface?: string;
|
|
24
30
|
}>;
|
|
25
31
|
export type MCPToolMeta = Record<string, Omit<CoreMCPTool, 'func' | 'middleware'> & {
|
|
26
32
|
pikkuFuncId: string;
|
|
27
33
|
inputSchema: string | null;
|
|
28
34
|
outputSchema: string | null;
|
|
29
35
|
middleware?: MiddlewareMetadata[];
|
|
36
|
+
/**
|
|
37
|
+
* The MCP endpoint this belongs to, when the project serves more than one.
|
|
38
|
+
* Absent means the project's default endpoint. Codegen splits the metadata
|
|
39
|
+
* by this key, so each endpoint's manifest lists only its own.
|
|
40
|
+
*/
|
|
41
|
+
surface?: string;
|
|
30
42
|
}>;
|
|
31
43
|
export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func' | 'middleware'> & {
|
|
32
44
|
pikkuFuncId: string;
|
|
@@ -38,6 +50,12 @@ export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func' | 'middlew
|
|
|
38
50
|
required: boolean;
|
|
39
51
|
}>;
|
|
40
52
|
middleware?: MiddlewareMetadata[];
|
|
53
|
+
/**
|
|
54
|
+
* The MCP endpoint this belongs to, when the project serves more than one.
|
|
55
|
+
* Absent means the project's default endpoint. Codegen splits the metadata
|
|
56
|
+
* by this key, so each endpoint's manifest lists only its own.
|
|
57
|
+
*/
|
|
58
|
+
surface?: string;
|
|
41
59
|
}>;
|
|
42
60
|
export type CoreMCPResource<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
43
61
|
/** How the client addresses this resource. `{name}` marks a parameter, and every parameter must be a key of the function's input schema. */
|
|
@@ -6,7 +6,7 @@ import type { ScenarioPersonas } from '../../services/personas-service.js';
|
|
|
6
6
|
import type { ScenarioBrowserProvider, ScenarioEnvironment, ScenarioSurface } from './scenario-step.types.js';
|
|
7
7
|
import type { PikkuWorkflowWire, WorkflowQueueOptions } from './workflow.types.js';
|
|
8
8
|
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
9
|
-
export type { CoreFeature, CoreFeatureScenario, FeatureMeta, FeaturesMeta, PikkuBrowserWire, PikkuScenarioWire, ScenarioBrowserFailure, ScenarioBrowserProvider, ScenarioEnvironment, ScenarioStepKind, ScenarioStepMeta, ScenarioStepOptions, ScenarioStepPhase, ScenarioSurface, TestIdSelector, } from './scenario.types.js';
|
|
9
|
+
export type { CoreFeature, CoreFeatureScenario, FeatureMeta, FeaturesMeta, PikkuBrowserWire, PikkuScenarioWire, ScenarioBrowserFailure, ScenarioBrowserProvider, ScenarioEnvironment, ScenarioStepKind, ScenarioStepMeta, ScenarioStepOptions, ScenarioScreenshotOptions, ScenarioStepPhase, ScenarioSurface, TestIdSelector, } from './scenario.types.js';
|
|
10
10
|
export type { ScenarioArtifact, ScenarioFailureDetail, ScenarioResult, ScenarioRunRecord, ScenarioRunReport, ScenarioRunStatus, ScenarioRunStore, ScenarioRunSummary, ScenarioStepRow, } from './scenario-run.types.js';
|
|
11
11
|
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
12
12
|
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
@@ -92,6 +92,9 @@ export declare class ScenarioActorRequired extends PikkuError {
|
|
|
92
92
|
* coverage gap. (A `then` that ran somewhere, just not on the run surface, is
|
|
93
93
|
* the coverage gap; it is reported rather than thrown. See
|
|
94
94
|
* {@link ScenarioNoWitness} for one that ran nowhere.)
|
|
95
|
+
*
|
|
96
|
+
* A strict run also throws it for a step that *could* fall back, because there
|
|
97
|
+
* the fallback is the thing being refused.
|
|
95
98
|
*/
|
|
96
99
|
export declare class ScenarioNoSurfaceBinding extends PikkuError {
|
|
97
100
|
readonly stepFunc: string;
|
|
@@ -114,6 +117,22 @@ export declare class ScenarioNoWitness extends PikkuError {
|
|
|
114
117
|
readonly runSurface: ScenarioSurface;
|
|
115
118
|
constructor(stepFunc: string, declared: ScenarioSurface[], runSurface: ScenarioSurface);
|
|
116
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* An assertion ran, but not on the surface the run targets.
|
|
122
|
+
*
|
|
123
|
+
* The sibling of {@link ScenarioNoWitness}: that one checked nothing anywhere,
|
|
124
|
+
* this one checked the system of record while the prose claims the actor saw it
|
|
125
|
+
* on the page. Ordinary runs count it as a coverage gap; a strict run refuses
|
|
126
|
+
* it, because a flow that cannot be observed end to end on the run surface is
|
|
127
|
+
* not a flow that surface can be documented from.
|
|
128
|
+
*/
|
|
129
|
+
export declare class ScenarioUnwitnessedAssertion extends PikkuError {
|
|
130
|
+
readonly stepFunc: string;
|
|
131
|
+
readonly declared: ScenarioSurface[];
|
|
132
|
+
readonly runSurface: ScenarioSurface;
|
|
133
|
+
readonly witnessedOn: ScenarioSurface[];
|
|
134
|
+
constructor(stepFunc: string, declared: ScenarioSurface[], runSurface: ScenarioSurface, witnessedOn: ScenarioSurface[]);
|
|
135
|
+
}
|
|
117
136
|
/**
|
|
118
137
|
* The scenario capability, layered onto a workflow service rather than being
|
|
119
138
|
* one.
|
|
@@ -145,13 +164,21 @@ export declare class PikkuScenarioService implements WorkflowRunExtension {
|
|
|
145
164
|
private scenarioBrowserProvider?;
|
|
146
165
|
private scenarioEnvironment?;
|
|
147
166
|
private runSurface;
|
|
167
|
+
private strictSurface;
|
|
148
168
|
constructor(engine: WorkflowRunEngine);
|
|
149
169
|
/**
|
|
150
170
|
* The surface every actor drives the system through for this run, set once by
|
|
151
171
|
* the runner from `--run`. `default` is the server-side path — the fast suite.
|
|
172
|
+
*
|
|
173
|
+
* `strict` removes both routes off that surface: an action may not fall back
|
|
174
|
+
* to its `default` binding, and a `then` may not be witnessed anywhere else.
|
|
175
|
+
* It is what lets a run stand as evidence of the whole flow on one surface,
|
|
176
|
+
* which is what generating documentation from a run requires. On a `default`
|
|
177
|
+
* run it changes nothing, because nothing there can fall back.
|
|
152
178
|
*/
|
|
153
|
-
setRunSurface(surface: ScenarioSurface): void;
|
|
179
|
+
setRunSurface(surface: ScenarioSurface, strict?: boolean): void;
|
|
154
180
|
getRunSurface(): ScenarioSurface;
|
|
181
|
+
isStrictSurface(): boolean;
|
|
155
182
|
/**
|
|
156
183
|
* Registered by `@pikku/playwright` (or any other driver) before a scenario
|
|
157
184
|
* runs. Absent means browser steps cannot run, which the CLI checks up front
|
|
@@ -132,14 +132,21 @@ addError(ScenarioActorRequired, {
|
|
|
132
132
|
* coverage gap. (A `then` that ran somewhere, just not on the run surface, is
|
|
133
133
|
* the coverage gap; it is reported rather than thrown. See
|
|
134
134
|
* {@link ScenarioNoWitness} for one that ran nowhere.)
|
|
135
|
+
*
|
|
136
|
+
* A strict run also throws it for a step that *could* fall back, because there
|
|
137
|
+
* the fallback is the thing being refused.
|
|
135
138
|
*/
|
|
136
139
|
export class ScenarioNoSurfaceBinding extends PikkuError {
|
|
137
140
|
stepFunc;
|
|
138
141
|
declared;
|
|
139
142
|
runSurface;
|
|
140
143
|
constructor(stepFunc, declared, runSurface) {
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
const declares = `(declares: ${declared.join(', ') || 'nothing'})`;
|
|
145
|
+
super(declared.includes('default')
|
|
146
|
+
? `[scenario] step '${stepFunc}' has no binding for '${runSurface}' and a strict run will not let it ` +
|
|
147
|
+
`fall back to 'default' ${declares}. Add a '${runSurface}' binding, or drop --strict.`
|
|
148
|
+
: `[scenario] step '${stepFunc}' declares no binding for '${runSurface}' and no 'default' to fall back to ` +
|
|
149
|
+
`${declares}.`);
|
|
143
150
|
this.stepFunc = stepFunc;
|
|
144
151
|
this.declared = declared;
|
|
145
152
|
this.runSurface = runSurface;
|
|
@@ -175,6 +182,34 @@ addError(ScenarioNoWitness, {
|
|
|
175
182
|
status: 500,
|
|
176
183
|
message: 'Assertion has no witness for this surface.',
|
|
177
184
|
});
|
|
185
|
+
/**
|
|
186
|
+
* An assertion ran, but not on the surface the run targets.
|
|
187
|
+
*
|
|
188
|
+
* The sibling of {@link ScenarioNoWitness}: that one checked nothing anywhere,
|
|
189
|
+
* this one checked the system of record while the prose claims the actor saw it
|
|
190
|
+
* on the page. Ordinary runs count it as a coverage gap; a strict run refuses
|
|
191
|
+
* it, because a flow that cannot be observed end to end on the run surface is
|
|
192
|
+
* not a flow that surface can be documented from.
|
|
193
|
+
*/
|
|
194
|
+
export class ScenarioUnwitnessedAssertion extends PikkuError {
|
|
195
|
+
stepFunc;
|
|
196
|
+
declared;
|
|
197
|
+
runSurface;
|
|
198
|
+
witnessedOn;
|
|
199
|
+
constructor(stepFunc, declared, runSurface, witnessedOn) {
|
|
200
|
+
super(`[scenario] assertion '${stepFunc}' was checked on ${witnessedOn.join(', ')}, not on '${runSurface}'. ` +
|
|
201
|
+
`It held there — but nothing looked at '${runSurface}', which is what the step's prose claims the actor saw. ` +
|
|
202
|
+
`Add a '${runSurface}' witness (declares: ${declared.join(', ') || 'nothing'}), or drop --strict.`);
|
|
203
|
+
this.stepFunc = stepFunc;
|
|
204
|
+
this.declared = declared;
|
|
205
|
+
this.runSurface = runSurface;
|
|
206
|
+
this.witnessedOn = witnessedOn;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
addError(ScenarioUnwitnessedAssertion, {
|
|
210
|
+
status: 500,
|
|
211
|
+
message: 'Assertion was checked, but not on the run surface.',
|
|
212
|
+
});
|
|
178
213
|
/**
|
|
179
214
|
* The scenario capability, layered onto a workflow service rather than being
|
|
180
215
|
* one.
|
|
@@ -211,19 +246,30 @@ export class PikkuScenarioService {
|
|
|
211
246
|
scenarioBrowserProvider;
|
|
212
247
|
scenarioEnvironment;
|
|
213
248
|
runSurface = 'default';
|
|
249
|
+
strictSurface = false;
|
|
214
250
|
constructor(engine) {
|
|
215
251
|
this.engine = engine;
|
|
216
252
|
}
|
|
217
253
|
/**
|
|
218
254
|
* The surface every actor drives the system through for this run, set once by
|
|
219
255
|
* the runner from `--run`. `default` is the server-side path — the fast suite.
|
|
256
|
+
*
|
|
257
|
+
* `strict` removes both routes off that surface: an action may not fall back
|
|
258
|
+
* to its `default` binding, and a `then` may not be witnessed anywhere else.
|
|
259
|
+
* It is what lets a run stand as evidence of the whole flow on one surface,
|
|
260
|
+
* which is what generating documentation from a run requires. On a `default`
|
|
261
|
+
* run it changes nothing, because nothing there can fall back.
|
|
220
262
|
*/
|
|
221
|
-
setRunSurface(surface) {
|
|
263
|
+
setRunSurface(surface, strict = false) {
|
|
222
264
|
this.runSurface = surface;
|
|
265
|
+
this.strictSurface = strict;
|
|
223
266
|
}
|
|
224
267
|
getRunSurface() {
|
|
225
268
|
return this.runSurface;
|
|
226
269
|
}
|
|
270
|
+
isStrictSurface() {
|
|
271
|
+
return this.strictSurface;
|
|
272
|
+
}
|
|
227
273
|
/**
|
|
228
274
|
* Registered by `@pikku/playwright` (or any other driver) before a scenario
|
|
229
275
|
* runs. Absent means browser steps cannot run, which the CLI checks up front
|
|
@@ -605,7 +651,8 @@ export class PikkuScenarioService {
|
|
|
605
651
|
});
|
|
606
652
|
};
|
|
607
653
|
if (resolution.kind === 'action') {
|
|
608
|
-
if (resolution.fellBack &&
|
|
654
|
+
if (resolution.fellBack &&
|
|
655
|
+
(this.strictSurface || !declared.includes('default'))) {
|
|
609
656
|
throw new ScenarioNoSurfaceBinding(resolvedStepFunc, declared, this.runSurface);
|
|
610
657
|
}
|
|
611
658
|
return await runOnSurface(resolution.surface);
|
|
@@ -613,6 +660,9 @@ export class PikkuScenarioService {
|
|
|
613
660
|
if (resolution.surfaces.length === 0) {
|
|
614
661
|
throw new ScenarioNoWitness(resolvedStepFunc, declared, this.runSurface);
|
|
615
662
|
}
|
|
663
|
+
if (this.strictSurface && resolution.unwitnessed) {
|
|
664
|
+
throw new ScenarioUnwitnessedAssertion(resolvedStepFunc, declared, this.runSurface, resolution.surfaces);
|
|
665
|
+
}
|
|
616
666
|
// A `then` runs every witness it has and they must agree. The surface
|
|
617
667
|
// witness runs first so that when the page is the thing that is wrong,
|
|
618
668
|
// it is the failure that surfaces.
|
|
@@ -31,6 +31,15 @@ export interface ScenarioArtifact {
|
|
|
31
31
|
actor?: string;
|
|
32
32
|
/** The caption the scenario author took a screenshot under. */
|
|
33
33
|
name?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The same shot across runs, under one key. `path` carries the order the run
|
|
36
|
+
* happened in, so it moves whenever a step is inserted before it — nothing
|
|
37
|
+
* that outlives a run (a caption override, a diff against last week) can key
|
|
38
|
+
* off it.
|
|
39
|
+
*/
|
|
40
|
+
id?: string;
|
|
41
|
+
/** Fit to show outside the run: a marketing card, a docs page, a gallery. */
|
|
42
|
+
showcase?: boolean;
|
|
34
43
|
}
|
|
35
44
|
/** One step of a run, already joined to the prose that declared it. */
|
|
36
45
|
export interface ScenarioStepRow {
|
|
@@ -166,11 +166,22 @@ export interface TestIdSelector {
|
|
|
166
166
|
* interface via `declare module`, so `wire.browser.page` is a fully typed
|
|
167
167
|
* Playwright `Page` in a project that installs it.
|
|
168
168
|
*/
|
|
169
|
+
/** How one deliberate screenshot is taken, and what it is for. */
|
|
170
|
+
export interface ScenarioScreenshotOptions {
|
|
171
|
+
/**
|
|
172
|
+
* Publish this one outside the run — a marketing card, a docs page, a
|
|
173
|
+
* gallery. Declared at the call site because only the author of the step
|
|
174
|
+
* knows the page is at a moment worth showing a stranger.
|
|
175
|
+
*/
|
|
176
|
+
showcase?: boolean;
|
|
177
|
+
/** Photograph the whole scrollable page rather than the viewport. */
|
|
178
|
+
fullPage?: boolean;
|
|
179
|
+
}
|
|
169
180
|
export interface PikkuBrowserWire {
|
|
170
181
|
/** The actor whose browser context this is */
|
|
171
182
|
readonly actor: string;
|
|
172
183
|
goto(url: string): Promise<void>;
|
|
173
|
-
screenshot(name?: string): Promise<Uint8Array>;
|
|
184
|
+
screenshot(name?: string, options?: ScenarioScreenshotOptions): Promise<Uint8Array>;
|
|
174
185
|
}
|
|
175
186
|
/**
|
|
176
187
|
* What one actor's window looked like at the moment a scenario failed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CorePikkuFunctionConfig, CorePikkuFunctionHook } from '../../function/functions.types.js';
|
|
2
2
|
export type { ScenarioStepInvocation, ScenarioStepMeta, PikkuScenarioWire, } from './dsl/workflow-dsl.types.js';
|
|
3
|
-
export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, PikkuBrowserWire, TestIdSelector, ScenarioBrowserProvider, ScenarioBrowserFailure, } from './scenario-step.types.js';
|
|
3
|
+
export type { ScenarioStepPhase, ScenarioStepKind, ScenarioStepOptions, PikkuScenarioStepWire, ScenarioEnvironment, ScenarioSurface, ScenarioSurfaceResolution, PikkuBrowserWire, ScenarioScreenshotOptions, TestIdSelector, ScenarioBrowserProvider, ScenarioBrowserFailure, } from './scenario-step.types.js';
|
|
4
4
|
export type CoreFeatureScenario = CorePikkuFunctionConfig<any, any, any> | {
|
|
5
5
|
scenario: CorePikkuFunctionConfig<any, any, any>;
|
|
6
6
|
data: unknown;
|
package/package.json
CHANGED