@@ -127,6 +127,298 @@ declare module "cloudflare:test" {
127127 migrationsTableName ?: string
128128 ) : Promise < void > ;
129129
130+ /**
131+ * Creates an **introspector** for a specific Workflow instance, used to
132+ * **modify** its behavior, **await** outcomes, and **clean up** its state during tests.
133+ * This is the primary entry point for testing individual Workflow instances.
134+ *
135+ * @param workflow - The Workflow binding, e.g., `env.MY_WORKFLOW`.
136+ * @param instanceId - The known ID of the Workflow instance to target.
137+ * @returns A `WorkflowInstanceIntrospector` to control the instance behavior.
138+ *
139+ * @example Full test of a Workflow instance with a known ID:
140+ * ```ts
141+ * it("should disable all sleeps and complete", async () => {
142+ * // 1. CONFIGURATION
143+ * const instance = await introspectWorkflowInstance(env.MY_WORKFLOW, "123456");
144+ * await instance.modify(async (m) => {
145+ * await m.disableSleeps();
146+ * });
147+ *
148+ * // 2. EXECUTION
149+ * await env.MY_WORKFLOW.create({ id: "123456" });
150+ *
151+ * // 3. ASSERTION
152+ * await instance.waitForStatus("complete");
153+ *
154+ * // 4. CLEANUP
155+ * await instance.cleanUp();
156+ * });
157+ * ```
158+ */
159+ export function introspectWorkflowInstance (
160+ workflow : Workflow ,
161+ instanceId : string
162+ ) : Promise < WorkflowInstanceIntrospector > ;
163+
164+ /**
165+ * Provides methods to control a single Workflow instance.
166+ */
167+ export interface WorkflowInstanceIntrospector {
168+ /**
169+ * Applies modifications to the Workflow instance's behavior.
170+ * Takes a callback function to apply modifications.
171+ *
172+ * @param fn - An async callback that receives a `WorkflowInstanceModifier` object.
173+ * @returns The `WorkflowInstanceIntrospector` instance for chaining.
174+ */
175+ modify (
176+ fn : ( m : WorkflowInstanceModifier ) => Promise < void >
177+ ) : Promise < WorkflowInstanceIntrospector > ;
178+
179+ /**
180+ * Waits for a specific step to complete and return a result.
181+ * If the step has already completed, this promise resolves immediately.
182+ *
183+ * @param step - An object specifying the step `name` and optional `index` (1-based).
184+ * If multiple steps share the same name, `index` targets a specific one.
185+ * Defaults to the first step found (`index: 1`).
186+ * @returns A promise that resolves with the step's result,
187+ * or rejects with an error if the step fails.
188+ */
189+ waitForStepResult ( step : { name : string ; index ?: number } ) : Promise < unknown > ;
190+
191+ /**
192+ * Waits for the Workflow instance to reach a specific InstanceStatus status
193+ * (e.g., 'running', 'complete').
194+ * If the instance is already in the target status, this promise resolves immediately.
195+ * Throws an error if the Workflow instance reaches a finite state
196+ * (e.g., complete, errored) that is different from the target status.
197+ *
198+ * @param status - The target `InstanceStatus` to wait for.
199+ */
200+ waitForStatus ( status : InstanceStatus [ "status" ] ) : Promise < void > ;
201+
202+ /**
203+ * Cleans up the Workflow instance's state.
204+ * This is crucial for ensuring test isolation by preventing state from
205+ * leaking between tests. It's best practice to call this in an `afterEach`
206+ * hook or at the end of every test.
207+ */
208+ cleanUp ( ) : Promise < void > ;
209+ }
210+
211+ /**
212+ * Provides methods to mock or alter the behavior of a Workflow instance's
213+ * steps, events, and sleeps.
214+ */
215+ interface WorkflowInstanceModifier {
216+ /**
217+ * Disables sleeps, causing `step.sleep()` and `step.sleepUntil()` to
218+ * resolve immediately.
219+ *
220+ * @example Disable all sleeps:
221+ * ```ts
222+ * await instance.modify(m => {
223+ * m.disableSleeps();
224+ * });
225+ *
226+ * @example Disable a specific set of sleeps by their step names:
227+ * ```ts
228+ * await instance.modify(m => {
229+ * m.disableSleeps([{ name: "sleep1" }, { name: "sleep5" }, { name: "sleep7" }]);
230+ * });
231+ *
232+ * @param steps - Optional array of specific steps to disable sleeps for.
233+ * If omitted, **all sleeps** in the Workflow will be disabled.
234+ * A step is an object specifying the step `name` and optional `index` (1-based).
235+ * If multiple steps share the same name, `index` targets a specific one.
236+ * Defaults to the first step found (`index: 1`).
237+ */
238+ disableSleeps ( steps ?: { name : string ; index ?: number } [ ] ) : Promise < void > ;
239+
240+ /**
241+ * Mocks the result of a `step.do()`, causing it to return a specified
242+ * value instantly without executing the step's actual implementation.
243+ *
244+ * If called multiple times for the same step, an error will be thrown.
245+ *
246+ * @param step - An object specifying the step `name` and optional `index` (1-based).
247+ * If multiple steps share the same name, `index` targets a specific one.
248+ * Defaults to the first step found (`index: 1`).
249+ * @param stepResult - The mock value to be returned by the step.
250+ *
251+ * @example Mock the result of the third step named "fetch-data":
252+ * ```ts
253+ * await instance.modify(m => {
254+ * m.mockStepResult(
255+ * { name: "fetch-data", index: 3 },
256+ * { success: true, data: [1, 2, 3] }
257+ * );
258+ * });
259+ * ```
260+ */
261+ mockStepResult (
262+ step : { name : string ; index ?: number } ,
263+ stepResult : unknown
264+ ) : Promise < void > ;
265+
266+ /**
267+ * Forces a `step.do()` to throw an error, simulating a failure without
268+ * executing the step's actual implementation. Useful for testing retry logic
269+ * and error handling.
270+ *
271+ * @example Mock a step that errors 3 times before succeeding:
272+ * ```ts
273+ * // This example assumes the "fetch-data" step is configured with at least 3 retries.
274+ * await instance.modify(m => {
275+ * m.mockStepError(
276+ * { name: "fetch-data" },
277+ * new Error("Failed!"),
278+ * 3
279+ * );
280+ * m.mockStepResult(
281+ * { name: "fetch-data" },
282+ * { success: true, data: [1, 2, 3] }
283+ * );
284+ * });
285+ *
286+ * @param step - An object specifying the step `name` and optional `index` (1-based).
287+ * If multiple steps share the same name, `index` targets a specific one.
288+ * Defaults to the first step found (`index: 1`).
289+ * @param error - The `Error` object to be thrown.
290+ * @param times - Optional number of times to throw the error. If a step has
291+ * retries configured, it will fail this many times before potentially
292+ * succeeding on a subsequent attempt. If omitted, it will throw on **every attempt**.
293+ */
294+ mockStepError (
295+ step : { name : string ; index ?: number } ,
296+ error : Error ,
297+ times ?: number
298+ ) : Promise < void > ;
299+
300+ /**
301+ * Forces a `step.do()` to fail by timing out immediately, without executing
302+ * the step's actual implementation. Default step timeout is 10 minutes.
303+ *
304+ * @example Mock a step that times out 3 times before succeeding:
305+ * ```ts
306+ * // This example assumes the "fetch-data" step is configured with at least 3 retries.
307+ * await instance.modify(m => {
308+ * m.forceStepTimeout(
309+ * { name: "fetch-data" },
310+ * 3
311+ * );
312+ * m.mockStepResult(
313+ * { name: "fetch-data" },
314+ * { success: true, data: [1, 2, 3] }
315+ * );
316+ * });
317+ *
318+ * @param step - An object specifying the step `name` and optional `index` (1-based).
319+ * If multiple steps share the same name, `index` targets a specific one.
320+ * Defaults to the first step found (`index: 1`).
321+ * @param times - Optional number of times the step will time out. Useful for
322+ * testing retry logic. If omitted, it will time out on **every attempt**.
323+ */
324+ forceStepTimeout ( step : { name : string ; index ?: number } , times ?: number ) ;
325+
326+ /**
327+ * Sends a mock event to the Workflow instance. This causes a `step.waitForEvent()`
328+ * to resolve with the provided payload, as long as the step's timeout has not
329+ * yet expired. Default event timeout is 24 hours.
330+ *
331+ * @example Mock a step event:
332+ * ```ts
333+ * await instance.modify(m => {
334+ * m.mockEvent(
335+ * { type: "user-approval", payload: { approved: true } },
336+ * );
337+ *
338+ * @param event - The event to send, including its `type` and `payload`.
339+ */
340+ mockEvent ( event : { type : string ; payload : unknown } ) : Promise < void > ;
341+
342+ /**
343+ * Forces a `step.waitForEvent()` to time out instantly, causing the step to fail.
344+ * This simulates a scenario where an expected event never arrives.
345+ * Default event timeout is 24 hours.
346+ *
347+ * @example Mock a step to time out:
348+ * ```ts
349+ * await instance.modify(m => {
350+ * m.forceEventTimeout(
351+ * { name: "user-approval" },
352+ * );
353+ *
354+ * @param step - An object specifying the step `name` and optional `index` (1-based).
355+ * If multiple steps share the same name, `index` targets a specific one.
356+ * Defaults to the first step found (`index: 1`).
357+ */
358+ forceEventTimeout ( step : { name : string ; index ?: number } ) : Promise < void > ;
359+ }
360+
361+ /**
362+ * Creates an **introspector** for a Workflow, where instance IDs are unknown
363+ * beforehand. This allows for defining modifications that will apply to
364+ * **all subsequently created instances**.
365+ *
366+ * This is the primary entry point for testing Workflow instances where the id is unknown before their creation.
367+ *
368+ * @param workflow - The Workflow binding, e.g., `env.MY_WORKFLOW`.
369+ * @returns A `WorkflowIntrospector` to control the instances behavior.
370+ *
371+ * @example Full test of a Workflow instance with a unknown ID:
372+ * ```ts
373+ * it("should disable all sleeps and complete", async () => {
374+ * // 1. CONFIGURATION
375+ * const introspector = await introspectWorkflow(env.MY_WORKFLOW);
376+ * await introspector.modifyAll(async (m) => {
377+ * await m.disableSleeps();
378+ * });
379+ *
380+ * // 2. EXECUTION
381+ * await env.MY_WORKFLOW.create();
382+ *
383+ * // 3. ASSERTION & CLEANUP
384+ * const instances = introspector.get();
385+ * for(const instance of instances) {
386+ * await instance.waitForStatus("complete");
387+ * await instance.cleanUp();
388+ * }
389+ * });
390+ * ```
391+ */
392+ export function introspectWorkflow (
393+ workflow : Workflow
394+ ) : Promise < WorkflowIntrospector > ;
395+
396+ /**
397+ * Provides methods to control all instances created by a Worflow.
398+ */
399+ export interface WorkflowIntrospector {
400+ /**
401+ * Applies modifications to all Workflow instances created after calling
402+ * `introspectWorkflow`. Takes a callback function to apply modifications.
403+ *
404+ * @param fn - An async callback that receives a `WorkflowInstanceModifier` object.
405+ */
406+ modifyAll ( fn : ( m : WorkflowInstanceModifier ) => Promise < void > ) : void ;
407+ /**
408+ * Returns all `WorkflowInstanceIntrospectors` from Workflow instances
409+ * created after calling `introspectWorkflow`.
410+ */
411+ get ( ) : WorkflowInstanceIntrospector [ ] ;
412+
413+ /**
414+ * Cleans up the Workflow introspector state.
415+ * This is crucial for ensuring that the introspection of a Workflow does
416+ * not get persisted across tests.
417+ * Call this in an `afterEach` hook or at the end of every test.
418+ */
419+ cleanUp ( ) : void ;
420+ }
421+
130422 // Only require `params` and `data` to be specified if they're non-empty
131423 interface EventContextInitBase {
132424 request : Request < unknown , IncomingRequestCfProperties > ;
0 commit comments