@systemfsoftware/xstate-test 0.0.0-stage → 2.0.0-beta.2
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 +9 -0
- package/LICENSE +21 -0
- package/README.md +1821 -2
- package/dist/adapter-Dd4Go0U3.d.ts +58 -0
- package/dist/effect-schema.d.ts +28 -0
- package/dist/effect-schema.mjs +40 -0
- package/dist/finalizerError-DOptAIDs.mjs +8 -0
- package/dist/index-CSbyUxJr.d.ts +2062 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.mjs +178 -0
- package/dist/playwright.d.ts +204 -0
- package/dist/playwright.mjs +287 -0
- package/dist/propertyTest-5bO0RsUu.mjs +4660 -0
- package/dist/propertyTest-BHnBV8ax.d.ts +147 -0
- package/dist/schema.d.ts +59 -0
- package/dist/schema.mjs +822 -0
- package/dist/vitest.d.ts +74 -0
- package/dist/vitest.mjs +139 -0
- package/package.json +88 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { $ as TestReference, A as ParallelPropertyCommandsResult, B as TestActorOutcome, C as formatTestStatistics, D as LinearizabilityOptions, E as LinearizabilityModel, G as TestEventGenerators, H as TestCampaignError, I as PropertyScenarioRunner, J as TestFailureStore, K as TestFailureExtras, L as PropertyTargetObservation, M as runParallelPropertyCommands, N as ModelTestFailure, O as LinearizabilityResult, P as PropertyExecutionConfig, Q as TestMode, S as formatTestCoverageJUnit, T as LinearizabilityEntry, U as TestCommand, V as TestAdapter, W as TestEventDescriptor, X as TestInvariant, Y as TestFixture, Z as TestInvariantContext, _ as TestCoverageJSON, _t as TestCoverage, at as TestStopCondition, b as formatTestCoverage, bt as AnyTestEventDescriptor, c as TestSuiteReplayResult, ct as TestSutSendContext, d as replayTestSuite, dt as TestTrace, et as TestReferenceSession, f as replayTestSuiteFixture, ft as replayTest, g as FormatTestCoverageOptions, h as FormatTestCoverageJUnitOptions, i as DescribeTestSuiteOptions, it as TestStep, j as checkLinearizable, k as ParallelPropertyCommandsOptions, l as describeTestSuite, lt as TestSutSession, m as FormatTestCoverageHTMLOptions, nt as TestStateAssertion, o as ReplayTestSuiteOptions, ot as TestSut, p as serializeTestSuite, pt as serializeTestTrace, q as TestFailureFormatOptions, r as TestPathsResult, rt as TestStateAssertions, s as TestSuite, st as TestSutContext, t as TestPathRunResult, tt as TestReplayMetadata, u as parseTestSuite, ut as TestTemporal, v as TestCoverageThresholds, vt as TestCoverageDimension, w as testCoverageToJSON, x as formatTestCoverageHTML, xt as TestPickDescriptor, y as assertTestCoverage, yt as TestExplorationBounds, z as ReplayNotReproducedError } from "./index-CSbyUxJr.js";
|
|
2
|
+
import { a as extractReplayPath, i as FastCheckSchedulerReport, n as FastCheckGeneratorKind, o as fastCheckAdapter, r as FastCheckSchedulerOptions, t as FastCheckAdapterOptions } from "./adapter-Dd4Go0U3.js";
|
|
3
|
+
import { a as propertyTest, c as FailuresOption, i as generateTestSuite, l as createFailureDatabase, n as FastCheckPropertyTestOptions, o as testPaths, r as FastCheckTestPathsOptions, s as FailureDatabaseOptions, t as FastCheckGenerateTestSuiteOptions } from "./propertyTest-BHnBV8ax.js";
|
|
4
|
+
import { EventsFromSchemasOptions, SchemaConverter, arbitraryFromSchema, eventsFromSchemas, mergeEventGenerators } from "./schema.js";
|
|
5
|
+
import { EventObject, Snapshot } from "@systemfsoftware/xstate";
|
|
6
|
+
import * as fc from "fast-check";
|
|
7
|
+
/**
|
|
8
|
+
* An event case whose payload refers to something in the current snapshot,
|
|
9
|
+
* such as an item that is already in a cart. `select` lists the candidates;
|
|
10
|
+
* the case is inapplicable when it returns none. `toPayload` turns the picked
|
|
11
|
+
* candidate into the event payload, and defaults to using it as the payload.
|
|
12
|
+
*
|
|
13
|
+
* The index into the candidates is generated with `fc.nat()`, so a failing
|
|
14
|
+
* run shrinks towards the first candidate. The snapshot type is inferred from
|
|
15
|
+
* the `events` map it is assigned to.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* events: {
|
|
19
|
+
* REMOVE: pick(
|
|
20
|
+
* (snapshot) => Object.keys(snapshot.context.items),
|
|
21
|
+
* (sku) => ({ sku })
|
|
22
|
+
* )
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
export declare function pick<TSnapshot extends Snapshot<unknown>, TPayload>(select: (snapshot: TSnapshot) => readonly TPayload[]): TestPickDescriptor<fc.Arbitrary<number>, TSnapshot, TPayload>;
|
|
29
|
+
export declare function pick<TSnapshot extends Snapshot<unknown>, TItem, TPayload>(select: (snapshot: TSnapshot) => readonly TItem[], toPayload: (item: TItem, snapshot: TSnapshot) => TPayload): TestPickDescriptor<fc.Arbitrary<number>, TSnapshot, TPayload>;
|
|
30
|
+
/**
|
|
31
|
+
* The scheduler fast-check generated for the run currently in flight, or
|
|
32
|
+
* `undefined` when the adapter was not configured with `scheduler`.
|
|
33
|
+
*
|
|
34
|
+
* Only defined while a scheduled run is executing, which is exactly when a
|
|
35
|
+
* system under test is created and driven.
|
|
36
|
+
*
|
|
37
|
+
* @experimental
|
|
38
|
+
*/
|
|
39
|
+
export declare function getCurrentScheduler(): fc.Scheduler | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Wraps a {@link TestSut} so its asynchronous boundaries (`send`, `read`,
|
|
42
|
+
* `settle`, `advance`) resolve in an order chosen by the run's fast-check
|
|
43
|
+
* scheduler instead of in plain microtask order.
|
|
44
|
+
*
|
|
45
|
+
* The wrapper binds to {@link getCurrentScheduler} when the session is created,
|
|
46
|
+
* so it is inert unless the adapter was configured with `scheduler`.
|
|
47
|
+
*
|
|
48
|
+
* @experimental
|
|
49
|
+
*/
|
|
50
|
+
export declare function withScheduledSut<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject>(sut: TestSut<TSnapshot, TEvent>): TestSut<TSnapshot, TEvent>;
|
|
51
|
+
/**
|
|
52
|
+
* Wraps a reference oracle the way {@link withScheduledSut} wraps a system
|
|
53
|
+
* under test: its `transition` and `read` calls resolve under the run's
|
|
54
|
+
* scheduler.
|
|
55
|
+
*
|
|
56
|
+
* @experimental
|
|
57
|
+
*/
|
|
58
|
+
export declare function withScheduledReference<TSnapshot extends Snapshot<unknown>, TEvent extends EventObject>(reference: TestReference<TSnapshot, TEvent>): TestReference<TSnapshot, TEvent>;
|
|
59
|
+
export { type AnyTestEventDescriptor, type DescribeTestSuiteOptions, type EventsFromSchemasOptions, type FailureDatabaseOptions, type FailuresOption, type FastCheckAdapterOptions, type FastCheckGenerateTestSuiteOptions, type FastCheckGeneratorKind, type FastCheckPropertyTestOptions, type FastCheckSchedulerOptions, type FastCheckSchedulerReport, type FastCheckTestPathsOptions, type FormatTestCoverageHTMLOptions, type FormatTestCoverageJUnitOptions, type FormatTestCoverageOptions, type LinearizabilityEntry, type LinearizabilityModel, type LinearizabilityOptions, type LinearizabilityResult, ModelTestFailure, type ParallelPropertyCommandsOptions, type ParallelPropertyCommandsResult, type PropertyExecutionConfig, PropertyScenarioRunner, type PropertyTargetObservation, ReplayNotReproducedError, type ReplayTestSuiteOptions, type SchemaConverter, type TestActorOutcome, type TestAdapter, TestCampaignError, type TestCommand, type TestCoverage, type TestCoverageDimension, type TestCoverageJSON, type TestCoverageThresholds, type TestEventDescriptor, type TestEventGenerators, type TestExplorationBounds, type TestFailureExtras, type TestFailureFormatOptions, type TestFailureStore, type TestFixture, type TestInvariant, type TestInvariantContext, type TestMode, type TestPathRunResult, type TestPathsResult, type TestPickDescriptor, type TestReference, type TestReferenceSession, type TestReplayMetadata, type TestStateAssertion, type TestStateAssertions, type TestStep, type TestStopCondition, type TestSuite, type TestSuiteReplayResult, type TestSut, type TestSutContext, type TestSutSendContext, type TestSutSession, type TestTemporal, type TestTrace, arbitraryFromSchema, assertTestCoverage, checkLinearizable, createFailureDatabase, describeTestSuite, eventsFromSchemas, extractReplayPath, fastCheckAdapter, formatTestCoverage, formatTestCoverageHTML, formatTestCoverageJUnit, formatTestStatistics, generateTestSuite, mergeEventGenerators, parseTestSuite, propertyTest, replayTest, replayTestSuite, replayTestSuiteFixture, runParallelPropertyCommands, serializeTestSuite, serializeTestTrace, testCoverageToJSON, testPaths };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { D as formatTestStatistics, E as formatTestCoverageJUnit, O as testCoverageToJSON, S as assertTestCoverage, T as formatTestCoverageHTML, _ as ReplayNotReproducedError, a as extractReplayPath, b as replayTest, c as withScheduledReference, d as parseTestSuite, f as replayTestSuite, g as PropertyScenarioRunner, h as ModelTestFailure, i as createFailureDatabase, k as createPickDescriptor, l as withScheduledSut, m as serializeTestSuite, n as propertyTest, o as fastCheckAdapter, p as replayTestSuiteFixture, r as testPaths, s as getCurrentScheduler, t as generateTestSuite, u as describeTestSuite, v as TestCampaignError, w as formatTestCoverage, x as serializeTestTrace, y as defaultEquivalent } from "./propertyTest-5bO0RsUu.mjs";
|
|
2
|
+
import { t as ensuringFinalizerWins } from "./finalizerError-DOptAIDs.mjs";
|
|
3
|
+
import { arbitraryFromSchema, eventsFromSchemas, mergeEventGenerators } from "./schema.mjs";
|
|
4
|
+
import { dual } from "effect/Function";
|
|
5
|
+
import { initialTransition, transition } from "@systemfsoftware/xstate";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Ref from "effect/Ref";
|
|
8
|
+
import * as fc from "fast-check";
|
|
9
|
+
// src/engine/propertyLinearizability.ts
|
|
10
|
+
/**
|
|
11
|
+
* Awaits `value` as an Effect. A value and a thenable are both accepted, and a
|
|
12
|
+
* rejection stays the same error object when the effect is run.
|
|
13
|
+
*/
|
|
14
|
+
const awaited = (value) => Effect.promise(() => Promise.resolve(value));
|
|
15
|
+
const DEFAULT_MAXIMUM_EXPLORED = 1e5;
|
|
16
|
+
function defaultSerializeState(state) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(state);
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** Identity of a model snapshot: everything a transition can depend on. */
|
|
24
|
+
function serializeSnapshotIdentity(state) {
|
|
25
|
+
if (state === null || typeof state !== "object") return defaultSerializeState(state);
|
|
26
|
+
const snapshot = state;
|
|
27
|
+
return defaultSerializeState({
|
|
28
|
+
value: snapshot.value,
|
|
29
|
+
context: snapshot.context,
|
|
30
|
+
status: snapshot.status
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Decides whether a concurrent `history` is linearizable against a sequential
|
|
35
|
+
* `model`: whether some total order of the operations, consistent with the
|
|
36
|
+
* real-time order of non-overlapping operations, produces exactly the observed
|
|
37
|
+
* responses.
|
|
38
|
+
*
|
|
39
|
+
* The search is the Wing–Gong just-in-time linearization: depth-first over the
|
|
40
|
+
* operations that could come next (those starting no later than the earliest
|
|
41
|
+
* end time still outstanding), backtracking whenever a response disagrees with
|
|
42
|
+
* the model, and memoizing `(state, completed set)` pairs so equivalent
|
|
43
|
+
* branches are explored once.
|
|
44
|
+
* @experimental
|
|
45
|
+
*/
|
|
46
|
+
const checkLinearizable = dual((args) => args.length >= 3 || Array.isArray(args[0]), function checkLinearizable(history, model, options = {}) {
|
|
47
|
+
const maxExplored = options.maxExplored ?? DEFAULT_MAXIMUM_EXPLORED;
|
|
48
|
+
const equalResponse = model.equalResponse ?? defaultEquivalent;
|
|
49
|
+
const serializeState = model.serializeState ?? defaultSerializeState;
|
|
50
|
+
const entries = history.slice();
|
|
51
|
+
const remaining = entries.map(() => true);
|
|
52
|
+
const witness = [];
|
|
53
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54
|
+
let explored = 0;
|
|
55
|
+
let truncated = false;
|
|
56
|
+
function memoKey(state) {
|
|
57
|
+
const serializedState = serializeState(state);
|
|
58
|
+
if (serializedState === void 0) return;
|
|
59
|
+
return `${entries.map((_, index) => remaining[index] === true ? "0" : "1").join("")}|${serializedState}`;
|
|
60
|
+
}
|
|
61
|
+
function search(state) {
|
|
62
|
+
if (remaining.every((isRemaining) => !isRemaining)) return true;
|
|
63
|
+
const key = memoKey(state);
|
|
64
|
+
if (key !== void 0) {
|
|
65
|
+
if (seen.has(key)) return false;
|
|
66
|
+
seen.add(key);
|
|
67
|
+
}
|
|
68
|
+
let earliestEnd = Infinity;
|
|
69
|
+
for (let index = 0; index < entries.length; index++) {
|
|
70
|
+
const entry = entries[index];
|
|
71
|
+
if (entry === void 0) continue;
|
|
72
|
+
if (remaining[index] === true && entry.end < earliestEnd) earliestEnd = entry.end;
|
|
73
|
+
}
|
|
74
|
+
for (let index = 0; index < entries.length; index++) {
|
|
75
|
+
const entry = entries[index];
|
|
76
|
+
if (entry === void 0) continue;
|
|
77
|
+
if (remaining[index] !== true || entry.start > earliestEnd) continue;
|
|
78
|
+
if (explored >= maxExplored) {
|
|
79
|
+
truncated = true;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
explored++;
|
|
83
|
+
const applied = model.apply(state, entry.invocation);
|
|
84
|
+
if (!equalResponse(applied.response, entry.response)) continue;
|
|
85
|
+
remaining[index] = false;
|
|
86
|
+
witness.push(entry);
|
|
87
|
+
if (search(applied.state)) return true;
|
|
88
|
+
witness.pop();
|
|
89
|
+
remaining[index] = true;
|
|
90
|
+
if (truncated) return false;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const linearizable = search(model.initial);
|
|
95
|
+
return {
|
|
96
|
+
linearizable,
|
|
97
|
+
witness: linearizable ? witness.slice() : void 0,
|
|
98
|
+
explored,
|
|
99
|
+
truncated
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* Runs a sequential prefix and then N branches concurrently against a system
|
|
104
|
+
* under test, records the resulting concurrent history, and checks it against
|
|
105
|
+
* the machine's own pure `transition()` as the sequential specification.
|
|
106
|
+
*
|
|
107
|
+
* This is the analogue of `parallel_commands` in QuickCheck State Machine and
|
|
108
|
+
* PropEr: the prefix puts the system in an interesting state, the branches
|
|
109
|
+
* race, and linearizability decides whether the observed responses could have
|
|
110
|
+
* come from any sequential interleaving.
|
|
111
|
+
* @experimental
|
|
112
|
+
*/
|
|
113
|
+
const runParallelPropertyCommands = dual(2, function runParallelPropertyCommands(logic, options) {
|
|
114
|
+
return Effect.runPromise(Effect.gen(function* () {
|
|
115
|
+
const projectSut = options.sut.projectSut ?? ((observed) => observed);
|
|
116
|
+
const session = yield* awaited(options.sut.create({
|
|
117
|
+
logic,
|
|
118
|
+
input: options.input,
|
|
119
|
+
snapshot: void 0,
|
|
120
|
+
label: () => {},
|
|
121
|
+
classify: () => {},
|
|
122
|
+
target: () => {}
|
|
123
|
+
}));
|
|
124
|
+
const clock = yield* Ref.make(0);
|
|
125
|
+
const history = [];
|
|
126
|
+
const invoke = (event) => Effect.gen(function* () {
|
|
127
|
+
const sent = yield* awaited(session.send(event));
|
|
128
|
+
if (sent !== void 0) return projectSut(sent);
|
|
129
|
+
return projectSut(session.read !== void 0 ? yield* awaited(session.read()) : void 0);
|
|
130
|
+
});
|
|
131
|
+
const nextClock = Ref.getAndUpdate(clock, (value) => value + 1);
|
|
132
|
+
const run = Effect.gen(function* () {
|
|
133
|
+
for (const event of options.prefix ?? []) yield* invoke(event);
|
|
134
|
+
yield* Effect.forEach(options.branches, (branch, branchIndex) => Effect.gen(function* () {
|
|
135
|
+
for (const [eventIndex, event] of branch.entries()) {
|
|
136
|
+
if (event === void 0) continue;
|
|
137
|
+
const start = yield* nextClock;
|
|
138
|
+
const response = yield* invoke(event);
|
|
139
|
+
const end = yield* nextClock;
|
|
140
|
+
history.push({
|
|
141
|
+
id: `${branchIndex}:${eventIndex}`,
|
|
142
|
+
actor: `branch-${branchIndex}`,
|
|
143
|
+
invocation: event,
|
|
144
|
+
response,
|
|
145
|
+
start,
|
|
146
|
+
end
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}), {
|
|
150
|
+
concurrency: "unbounded",
|
|
151
|
+
discard: true
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
yield* ensuringFinalizerWins(run, Effect.promise(() => Promise.resolve(session.dispose?.())));
|
|
155
|
+
const initialModelState = initialTransition(logic, options.input)[0];
|
|
156
|
+
const modelState = (options.prefix ?? []).reduce((state, event) => transition(logic, state, event)[0], initialModelState);
|
|
157
|
+
return {
|
|
158
|
+
...checkLinearizable(history, {
|
|
159
|
+
initial: modelState,
|
|
160
|
+
apply: (state, event) => {
|
|
161
|
+
const [next] = transition(logic, state, event);
|
|
162
|
+
return {
|
|
163
|
+
state: next,
|
|
164
|
+
response: options.sut.projectModel(next)
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
equalResponse: options.equalResponse,
|
|
168
|
+
serializeState: (state) => options.serializeState !== void 0 ? options.serializeState(state) : serializeSnapshotIdentity(state)
|
|
169
|
+
}, { maxExplored: options.maxExplored }),
|
|
170
|
+
history
|
|
171
|
+
};
|
|
172
|
+
}));
|
|
173
|
+
});
|
|
174
|
+
// src/pick.ts
|
|
175
|
+
function pick(select, toPayload) {
|
|
176
|
+
return createPickDescriptor(fc.nat(), select, toPayload);
|
|
177
|
+
}
|
|
178
|
+
export { ModelTestFailure, PropertyScenarioRunner, ReplayNotReproducedError, TestCampaignError, arbitraryFromSchema, assertTestCoverage, checkLinearizable, createFailureDatabase, describeTestSuite, eventsFromSchemas, extractReplayPath, fastCheckAdapter, formatTestCoverage, formatTestCoverageHTML, formatTestCoverageJUnit, formatTestStatistics, generateTestSuite, getCurrentScheduler, mergeEventGenerators, parseTestSuite, pick, propertyTest, replayTest, replayTestSuite, replayTestSuiteFixture, runParallelPropertyCommands, serializeTestSuite, serializeTestTrace, testCoverageToJSON, testPaths, withScheduledReference, withScheduledSut };
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { ot as TestSut } from "./index-CSbyUxJr.js";
|
|
2
|
+
import { EventObject, Snapshot } from "@systemfsoftware/xstate";
|
|
3
|
+
/**
|
|
4
|
+
* The subset of the Playwright `Page` API this package uses.
|
|
5
|
+
*
|
|
6
|
+
* A real `Page` from `playwright` or `@playwright/test` is assignable to this
|
|
7
|
+
* type, so no Playwright import is needed at runtime or at type-check time.
|
|
8
|
+
* Everything is optional because only the parts your configuration reaches for
|
|
9
|
+
* are used.
|
|
10
|
+
*
|
|
11
|
+
* @experimental
|
|
12
|
+
*/
|
|
13
|
+
export interface PlaywrightPage {
|
|
14
|
+
readonly waitForLoadState?: (state?: any, options?: any) => Promise<void>;
|
|
15
|
+
readonly screenshot?: (options?: any) => Promise<any>;
|
|
16
|
+
readonly route?: (url: any, handler: any, options?: any) => Promise<unknown>;
|
|
17
|
+
readonly unroute?: (url: any, handler?: any) => Promise<void>;
|
|
18
|
+
readonly clock?: {
|
|
19
|
+
readonly runFor?: (ticks: any) => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
readonly context?: () => {
|
|
22
|
+
readonly tracing?: {
|
|
23
|
+
readonly start?: (options?: any) => Promise<void>;
|
|
24
|
+
readonly stop?: (options?: any) => Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
readonly on?: (event: any, listener: any) => unknown;
|
|
28
|
+
readonly off?: (event: any, listener: any) => unknown;
|
|
29
|
+
readonly addInitScript?: (script: any, arg?: any) => Promise<unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The subset of Playwright's `TestInfo` this package uses. The `testInfo`
|
|
33
|
+
* fixture of `@playwright/test` is assignable to it.
|
|
34
|
+
*
|
|
35
|
+
* @experimental
|
|
36
|
+
*/
|
|
37
|
+
export interface PlaywrightTestInfo {
|
|
38
|
+
readonly attach: (name: string, options: {
|
|
39
|
+
/** A string or a `Buffer`. */
|
|
40
|
+
readonly body?: any;
|
|
41
|
+
readonly path?: string;
|
|
42
|
+
readonly contentType?: string;
|
|
43
|
+
}) => Promise<void>;
|
|
44
|
+
readonly outputPath: (...pathSegments: string[]) => string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The page-level oracles `createPlaywrightSut()` checks after every stable
|
|
48
|
+
* step. Keys left out are off.
|
|
49
|
+
*
|
|
50
|
+
* @experimental
|
|
51
|
+
*/
|
|
52
|
+
export interface PlaywrightOracles {
|
|
53
|
+
/** Fails on uncaught exceptions in the page (`pageerror`). */
|
|
54
|
+
readonly pageError?: boolean;
|
|
55
|
+
/** Fails on console messages of this level or above. */
|
|
56
|
+
readonly console?: 'error' | 'warn' | false;
|
|
57
|
+
/**
|
|
58
|
+
* Fails on responses with at least this status code. Responses to requests
|
|
59
|
+
* a `mocks` route handled are ignored.
|
|
60
|
+
*/
|
|
61
|
+
readonly http?: number | false;
|
|
62
|
+
/** Fails on unhandled promise rejections in the page. */
|
|
63
|
+
readonly unhandledRejection?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Thrown by a session's `check()` when a page-level oracle fails.
|
|
67
|
+
*
|
|
68
|
+
* @experimental
|
|
69
|
+
*/
|
|
70
|
+
declare const PlaywrightOracleErrorBase: new (args: {
|
|
71
|
+
readonly message: string;
|
|
72
|
+
readonly cause: readonly string[];
|
|
73
|
+
}) => import("effect/Cause").YieldableError & {
|
|
74
|
+
readonly _tag: "@systemfsoftware/xstate-test/playwright/PlaywrightOracleError";
|
|
75
|
+
} & Readonly<{
|
|
76
|
+
readonly message: string;
|
|
77
|
+
readonly cause: readonly string[];
|
|
78
|
+
}>;
|
|
79
|
+
export declare class PlaywrightOracleError extends PlaywrightOracleErrorBase {
|
|
80
|
+
readonly messages: readonly string[];
|
|
81
|
+
readonly name = "PlaywrightOracleError";
|
|
82
|
+
constructor(messages: readonly string[]);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A Playwright action bound to a generated event.
|
|
86
|
+
*
|
|
87
|
+
* @experimental
|
|
88
|
+
*/
|
|
89
|
+
export type PlaywrightEventAction<TPage extends PlaywrightPage, TEvent extends EventObject> = (page: TPage, event: TEvent) => void | Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Per-event-case network stubbing, applied before the event action runs.
|
|
92
|
+
*
|
|
93
|
+
* @experimental
|
|
94
|
+
*/
|
|
95
|
+
export type PlaywrightMock<TPage extends PlaywrightPage> = (page: TPage) => void | Promise<unknown>;
|
|
96
|
+
/** @experimental */
|
|
97
|
+
export interface PlaywrightSutConfig<TPage extends PlaywrightPage, TSnapshot extends Snapshot<unknown>, TEvent extends EventObject> {
|
|
98
|
+
/** Performs each event against the page. */
|
|
99
|
+
readonly events: { readonly [TType in TEvent['type']]?: PlaywrightEventAction<TPage, TEvent extends {
|
|
100
|
+
type: TType;
|
|
101
|
+
} ? TEvent : never>; };
|
|
102
|
+
/**
|
|
103
|
+
* Projects the DOM to a value comparable with the model projection. Omit it
|
|
104
|
+
* (together with `projectModel`) to assert through `states` instead.
|
|
105
|
+
*/
|
|
106
|
+
readonly read?: (page: TPage) => unknown | Promise<unknown>;
|
|
107
|
+
/** Projects the model snapshot to a value comparable with the page projection. */
|
|
108
|
+
readonly projectModel?: (snapshot: TSnapshot) => unknown;
|
|
109
|
+
/**
|
|
110
|
+
* Per-state assertions run after every stable step, keyed by state value (or
|
|
111
|
+
* `'#id'`), with `'*'` as the fallthrough.
|
|
112
|
+
*/
|
|
113
|
+
readonly states?: {
|
|
114
|
+
readonly [stateKey: string]: (page: TPage, snapshot: TSnapshot) => void | Promise<void>;
|
|
115
|
+
};
|
|
116
|
+
/** Normalizes the value read from the page before comparison. */
|
|
117
|
+
readonly projectSut?: (observed: unknown) => unknown;
|
|
118
|
+
/** Compares model and page projections. Defaults to deep equality. */
|
|
119
|
+
readonly equivalent?: (model: unknown, sut: unknown) => boolean | Promise<boolean>;
|
|
120
|
+
/**
|
|
121
|
+
* Waits for the page to become quiescent before every comparison. Defaults to
|
|
122
|
+
* `page.waitForLoadState('load')` followed by a microtask flush. Pass a
|
|
123
|
+
* function to wait for something specific, such as a locator.
|
|
124
|
+
*/
|
|
125
|
+
readonly settle?: (page: TPage) => void | Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Advances page time. Defaults to `page.clock.runFor(milliseconds)` when
|
|
128
|
+
* Playwright's clock API is installed, and returns no events.
|
|
129
|
+
*/
|
|
130
|
+
readonly advance?: (page: TPage, milliseconds: number) => readonly TEvent[] | Promise<readonly TEvent[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Records a checkpoint. Defaults to a screenshot written to the
|
|
133
|
+
* configured screenshot directory.
|
|
134
|
+
*/
|
|
135
|
+
readonly checkpoint?: (page: TPage, label: string) => void | Promise<void>;
|
|
136
|
+
/** Directory for default checkpoint screenshots. */
|
|
137
|
+
readonly screenshotDir?: string;
|
|
138
|
+
/** Runs once when a scenario session is created. */
|
|
139
|
+
readonly reset?: (page: TPage) => void | Promise<void>;
|
|
140
|
+
/** Runs when a scenario stops. */
|
|
141
|
+
readonly stop?: (page: TPage) => void | Promise<void>;
|
|
142
|
+
/** Runs when a scenario session is disposed. */
|
|
143
|
+
readonly dispose?: (page: TPage) => void | Promise<void>;
|
|
144
|
+
/**
|
|
145
|
+
* Per-case `page.route()` setup. When the property runner supplies the
|
|
146
|
+
* generated event case, the key is looked up as `"<type>.<case>"` first and
|
|
147
|
+
* then as `"<case>"`; otherwise (prefix, clock and replayed events) the key
|
|
148
|
+
* is the case resolved by `caseOf`. Use it to steer an invoked service to
|
|
149
|
+
* success or failure on different generated paths.
|
|
150
|
+
*
|
|
151
|
+
* Routes installed by a mock are unrouted before another case's mock is
|
|
152
|
+
* applied and when the scenario session is disposed, so handlers do not
|
|
153
|
+
* leak across cases or runs.
|
|
154
|
+
*/
|
|
155
|
+
readonly mocks?: {
|
|
156
|
+
readonly [caseId: string]: PlaywrightMock<TPage>;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Resolves the mock case for an event. Defaults to `event.case` when present,
|
|
160
|
+
* otherwise `event.type`.
|
|
161
|
+
*/
|
|
162
|
+
readonly caseOf?: (event: TEvent) => string | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* Page-level oracles checked after every stable step. `'defaults'` (the
|
|
165
|
+
* default) fails on uncaught exceptions, console errors, unhandled
|
|
166
|
+
* rejections, and responses with a status of 400 or above. `false` turns
|
|
167
|
+
* them all off.
|
|
168
|
+
*/
|
|
169
|
+
readonly oracles?: 'defaults' | false | PlaywrightOracles;
|
|
170
|
+
/**
|
|
171
|
+
* Where failure artifacts are attached. Pass Playwright's `testInfo`
|
|
172
|
+
* fixture. Required when `trace` or `screenshots` is on.
|
|
173
|
+
*/
|
|
174
|
+
readonly testInfo?: PlaywrightTestInfo;
|
|
175
|
+
/**
|
|
176
|
+
* Records a Playwright trace per run. `'retain-on-failure'` attaches the
|
|
177
|
+
* trace of the failing run; `'on'` also attaches the last run's trace when
|
|
178
|
+
* the campaign passes. Defaults to `'retain-on-failure'` with `testInfo`,
|
|
179
|
+
* and `'off'` without.
|
|
180
|
+
*/
|
|
181
|
+
readonly trace?: 'off' | 'retain-on-failure' | 'on';
|
|
182
|
+
/**
|
|
183
|
+
* `'on-failure'` attaches a screenshot of the page when the failing run
|
|
184
|
+
* ends; `'every-step'` attaches one per stable step of the failing run.
|
|
185
|
+
* Defaults to `'on-failure'` with `testInfo`, and `'off'` without.
|
|
186
|
+
*/
|
|
187
|
+
readonly screenshots?: 'off' | 'on-failure' | 'every-step';
|
|
188
|
+
/**
|
|
189
|
+
* Wraps each event action, such as Playwright's `test.step`, so the report
|
|
190
|
+
* shows one step per event.
|
|
191
|
+
*/
|
|
192
|
+
readonly step?: (name: string, body: () => Promise<void>) => Promise<unknown>;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Creates a `TestSut` that drives a Playwright page as the system under
|
|
196
|
+
* test for `propertyTest()` and `testPaths()`.
|
|
197
|
+
*
|
|
198
|
+
* @experimental
|
|
199
|
+
*/
|
|
200
|
+
export declare const createPlaywrightSut: {
|
|
201
|
+
<TPage extends PlaywrightPage, TSnapshot extends Snapshot<unknown> = Snapshot<unknown>, TEvent extends EventObject = EventObject>(config: PlaywrightSutConfig<TPage, TSnapshot, TEvent>): (page: TPage) => TestSut<TSnapshot, TEvent>;
|
|
202
|
+
<TPage extends PlaywrightPage, TSnapshot extends Snapshot<unknown> = Snapshot<unknown>, TEvent extends EventObject = EventObject>(page: TPage, config: PlaywrightSutConfig<TPage, TSnapshot, TEvent>): TestSut<TSnapshot, TEvent>;
|
|
203
|
+
};
|
|
204
|
+
export {};
|