@glubean/sdk 0.1.0
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/dist/configure.d.ts +141 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +535 -0
- package/dist/configure.js.map +1 -0
- package/dist/data.d.ts +232 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +543 -0
- package/dist/data.js.map +1 -0
- package/dist/expect.d.ts +511 -0
- package/dist/expect.d.ts.map +1 -0
- package/dist/expect.js +763 -0
- package/dist/expect.js.map +1 -0
- package/dist/index.d.ts +718 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1015 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +39 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +52 -0
- package/dist/internal.js.map +1 -0
- package/dist/plugin.d.ts +56 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +57 -0
- package/dist/plugin.js.map +1 -0
- package/dist/types.d.ts +1971 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +54 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
import type { EachTestFunction, ExtensionFn, ResolveExtensions, SimpleTestFunction, StepMeta, Test, TestContext, TestMeta } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Glubean SDK spec version.
|
|
4
|
+
*
|
|
5
|
+
* This defines the API contract between the SDK, Scanner, and Runner.
|
|
6
|
+
* - Major version: Breaking changes
|
|
7
|
+
* - Minor version: New features (backward compatible)
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { SPEC_VERSION } from "@glubean/sdk";
|
|
12
|
+
* console.log("SDK spec version:", SPEC_VERSION);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare const SPEC_VERSION = "2.0";
|
|
16
|
+
/**
|
|
17
|
+
* Builder class for creating tests with a fluent API.
|
|
18
|
+
*
|
|
19
|
+
* @template S The state type for multi-step tests
|
|
20
|
+
* @template Ctx The context type (defaults to TestContext; augmented by test.extend())
|
|
21
|
+
*
|
|
22
|
+
* @example Simple test (quick mode)
|
|
23
|
+
* ```ts
|
|
24
|
+
* export const login = test("login", async (ctx) => {
|
|
25
|
+
* ctx.assert(true, "works");
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* @example Multi-step test (builder mode)
|
|
30
|
+
* ```ts
|
|
31
|
+
* export const checkout = test("checkout")
|
|
32
|
+
* .meta({ tags: ["e2e"] })
|
|
33
|
+
* .setup(async (ctx) => ({ cart: await createCart() }))
|
|
34
|
+
* .step("Add to cart", async (ctx, state) => {
|
|
35
|
+
* await addItem(state.cart, "item-1");
|
|
36
|
+
* return state;
|
|
37
|
+
* })
|
|
38
|
+
* .step("Checkout", async (ctx, state) => {
|
|
39
|
+
* await checkout(state.cart);
|
|
40
|
+
* return state;
|
|
41
|
+
* })
|
|
42
|
+
* .teardown(async (ctx, state) => {
|
|
43
|
+
* await cleanup(state.cart);
|
|
44
|
+
* })
|
|
45
|
+
* .build();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare class TestBuilder<S = unknown, Ctx extends TestContext = TestContext> {
|
|
49
|
+
private _meta;
|
|
50
|
+
private _setup?;
|
|
51
|
+
private _teardown?;
|
|
52
|
+
private _steps;
|
|
53
|
+
private _built;
|
|
54
|
+
_fixtures?: Record<string, ExtensionFn<any>>;
|
|
55
|
+
/**
|
|
56
|
+
* Marker property so the runner can detect un-built TestBuilder exports
|
|
57
|
+
* without importing the SDK. The runner checks this string to auto-build.
|
|
58
|
+
*/
|
|
59
|
+
readonly __glubean_type: "builder";
|
|
60
|
+
constructor(id: string, fixtures?: Record<string, ExtensionFn<any>>);
|
|
61
|
+
/**
|
|
62
|
+
* Set additional metadata for the test.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* test("my-test")
|
|
67
|
+
* .meta({ tags: ["smoke"], description: "A smoke test" })
|
|
68
|
+
* .step(...)
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
meta(meta: Omit<TestMeta, "id">): TestBuilder<S, Ctx>;
|
|
72
|
+
/**
|
|
73
|
+
* Mark this test as focused.
|
|
74
|
+
*
|
|
75
|
+
* Focused tests are intended for local debugging sessions. When any tests in
|
|
76
|
+
* a run are marked as `only`, non-focused tests may be excluded by discovery
|
|
77
|
+
* tooling/orchestrators. If `skip` is also set on the same test, `skip`
|
|
78
|
+
* still wins during run selection.
|
|
79
|
+
*/
|
|
80
|
+
only(): TestBuilder<S, Ctx>;
|
|
81
|
+
/**
|
|
82
|
+
* Mark this test as skipped.
|
|
83
|
+
*
|
|
84
|
+
* Skip takes precedence over `only` when both are present.
|
|
85
|
+
*/
|
|
86
|
+
skip(): TestBuilder<S, Ctx>;
|
|
87
|
+
/**
|
|
88
|
+
* Set the setup function that runs before all steps.
|
|
89
|
+
* The returned state is passed to all steps and teardown.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* test("auth")
|
|
94
|
+
* .setup(async (ctx) => {
|
|
95
|
+
* const baseUrl = ctx.vars.require("BASE_URL");
|
|
96
|
+
* const apiKey = ctx.secrets.require("API_KEY");
|
|
97
|
+
* const { token } = await ctx.http.post(`${baseUrl}/auth/token`, {
|
|
98
|
+
* headers: { "X-API-Key": apiKey },
|
|
99
|
+
* }).json();
|
|
100
|
+
* return { token };
|
|
101
|
+
* })
|
|
102
|
+
* .step("verify", async (ctx, { token }) => { ... })
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
setup<NewS>(fn: (ctx: Ctx) => Promise<NewS>): TestBuilder<NewS, Ctx>;
|
|
106
|
+
/**
|
|
107
|
+
* Set the teardown function that runs after all steps (even on failure).
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* test("db-test")
|
|
112
|
+
* .setup(async (ctx) => ({ conn: await connect() }))
|
|
113
|
+
* .step(...)
|
|
114
|
+
* .teardown(async (ctx, { conn }) => {
|
|
115
|
+
* await conn.close();
|
|
116
|
+
* })
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
teardown(fn: (ctx: Ctx, state: S) => Promise<void>): TestBuilder<S, Ctx>;
|
|
120
|
+
/**
|
|
121
|
+
* Add a step that does not return state (void).
|
|
122
|
+
* The state type is preserved for subsequent steps.
|
|
123
|
+
*
|
|
124
|
+
* @param name Step name (displayed in reports)
|
|
125
|
+
* @param fn Step function that performs assertions/side-effects without returning state
|
|
126
|
+
*/
|
|
127
|
+
step(name: string, fn: (ctx: Ctx, state: S) => Promise<void>): TestBuilder<S, Ctx>;
|
|
128
|
+
/**
|
|
129
|
+
* Add a step that returns new state, replacing the current state type.
|
|
130
|
+
*
|
|
131
|
+
* The returned value becomes the `state` argument for subsequent steps.
|
|
132
|
+
* This enables fully type-safe chained steps without needing `.setup()`.
|
|
133
|
+
*
|
|
134
|
+
* @param name Step name (displayed in reports)
|
|
135
|
+
* @param fn Step function receiving context and current state, returning new state
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* test("auth-flow")
|
|
140
|
+
* .step("login", async (ctx) => {
|
|
141
|
+
* const data = await ctx.http.post("/auth/login", { json: creds }).json<{ token: string }>();
|
|
142
|
+
* return { token: data.token };
|
|
143
|
+
* })
|
|
144
|
+
* .step("get profile", async (ctx, { token }) => {
|
|
145
|
+
* // token is inferred as string ✓
|
|
146
|
+
* const profile = await ctx.http.get("/auth/me", {
|
|
147
|
+
* headers: { Authorization: `Bearer ${token}` },
|
|
148
|
+
* }).json<{ name: string }>();
|
|
149
|
+
* return { token, name: profile.name };
|
|
150
|
+
* })
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
step<NewS>(name: string, fn: (ctx: Ctx, state: S) => Promise<NewS>): TestBuilder<NewS, Ctx>;
|
|
154
|
+
/**
|
|
155
|
+
* Add a step with options (void return).
|
|
156
|
+
*/
|
|
157
|
+
step(name: string, options: Omit<StepMeta, "name">, fn: (ctx: Ctx, state: S) => Promise<void>): TestBuilder<S, Ctx>;
|
|
158
|
+
/**
|
|
159
|
+
* Add a step with additional options, returning new state.
|
|
160
|
+
*/
|
|
161
|
+
step<NewS>(name: string, options: Omit<StepMeta, "name">, fn: (ctx: Ctx, state: S) => Promise<NewS>): TestBuilder<NewS, Ctx>;
|
|
162
|
+
/**
|
|
163
|
+
* Apply a builder transform function for step composition.
|
|
164
|
+
*
|
|
165
|
+
* Reusable step sequences are just plain functions that take a builder
|
|
166
|
+
* and return a builder. `.use()` applies such a function to the current
|
|
167
|
+
* chain, preserving state flow.
|
|
168
|
+
*
|
|
169
|
+
* @param fn Transform function that receives this builder and returns a (possibly re-typed) builder
|
|
170
|
+
*
|
|
171
|
+
* @example Reusable step sequence
|
|
172
|
+
* ```ts
|
|
173
|
+
* // Define once — just a function
|
|
174
|
+
* const withAuth = (b: TestBuilder<unknown>) => b
|
|
175
|
+
* .step("login", async (ctx) => {
|
|
176
|
+
* const data = await ctx.http.post("/login", { json: creds }).json<{ token: string }>();
|
|
177
|
+
* return { token: data.token };
|
|
178
|
+
* });
|
|
179
|
+
*
|
|
180
|
+
* // Reuse across tests
|
|
181
|
+
* export const testA = test("test-a").use(withAuth).step("act", async (ctx, { token }) => { ... });
|
|
182
|
+
* export const testB = test("test-b").use(withAuth).step("verify", async (ctx, { token }) => { ... });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
use<NewS>(fn: (builder: TestBuilder<S, Ctx>) => TestBuilder<NewS, Ctx>): TestBuilder<NewS, Ctx>;
|
|
186
|
+
/**
|
|
187
|
+
* Apply a builder transform and tag all newly added steps with a group ID.
|
|
188
|
+
*
|
|
189
|
+
* Works exactly like `.use()`, but every step added by `fn` is marked with
|
|
190
|
+
* `group` metadata for visual grouping in reports and dashboards.
|
|
191
|
+
*
|
|
192
|
+
* @param id Group identifier (displayed in reports as a section header)
|
|
193
|
+
* @param fn Transform function that adds steps to the builder
|
|
194
|
+
*
|
|
195
|
+
* @example Reusable steps with grouping
|
|
196
|
+
* ```ts
|
|
197
|
+
* const withAuth = (b: TestBuilder<unknown>) => b
|
|
198
|
+
* .step("login", async (ctx) => ({ token: "..." }))
|
|
199
|
+
* .step("verify", async (ctx, { token }) => ({ token, verified: true }));
|
|
200
|
+
*
|
|
201
|
+
* export const checkout = test("checkout")
|
|
202
|
+
* .group("auth", withAuth)
|
|
203
|
+
* .step("pay", async (ctx, { token }) => { ... });
|
|
204
|
+
*
|
|
205
|
+
* // Report output:
|
|
206
|
+
* // checkout
|
|
207
|
+
* // ├─ [auth]
|
|
208
|
+
* // │ ├─ login ✓
|
|
209
|
+
* // │ └─ verify ✓
|
|
210
|
+
* // └─ pay ✓
|
|
211
|
+
* ```
|
|
212
|
+
*
|
|
213
|
+
* @example Inline grouping (no reuse, just organization)
|
|
214
|
+
* ```ts
|
|
215
|
+
* export const e2e = test("e2e")
|
|
216
|
+
* .group("setup", b => b
|
|
217
|
+
* .step("seed db", async (ctx) => ({ dbId: "..." }))
|
|
218
|
+
* .step("create user", async (ctx, { dbId }) => ({ dbId, userId: "..." }))
|
|
219
|
+
* )
|
|
220
|
+
* .step("verify", async (ctx, { dbId, userId }) => { ... });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
group<NewS>(id: string, fn: (builder: TestBuilder<S, Ctx>) => TestBuilder<NewS, Ctx>): TestBuilder<NewS, Ctx>;
|
|
224
|
+
/**
|
|
225
|
+
* Finalize and register the test in the global registry.
|
|
226
|
+
* Called automatically via microtask if not explicitly invoked via build().
|
|
227
|
+
* Idempotent — safe to call multiple times.
|
|
228
|
+
* @internal
|
|
229
|
+
*/
|
|
230
|
+
private _finalize;
|
|
231
|
+
/**
|
|
232
|
+
* Build and register the test. Returns a plain `Test<S>` object.
|
|
233
|
+
*
|
|
234
|
+
* **Optional** — if omitted, the builder auto-finalizes via microtask
|
|
235
|
+
* after all synchronous chaining completes, and the runner will
|
|
236
|
+
* auto-detect the builder export. Calling `.build()` explicitly is
|
|
237
|
+
* still supported for backward compatibility.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* // With .build() (explicit — backward compatible)
|
|
242
|
+
* export const myTest = test("my-test")
|
|
243
|
+
* .step("step-1", async (ctx) => { ... })
|
|
244
|
+
* .build();
|
|
245
|
+
*
|
|
246
|
+
* // Without .build() (auto-finalized — recommended)
|
|
247
|
+
* export const myTest = test("my-test")
|
|
248
|
+
* .step("step-1", async (ctx) => { ... });
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
build(): Test<S>;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Create a new test.
|
|
255
|
+
*
|
|
256
|
+
* This is the unified entry point for all test definitions.
|
|
257
|
+
* Supports both quick mode (single function) and builder mode (multi-step).
|
|
258
|
+
*
|
|
259
|
+
* @example Quick mode (simple test)
|
|
260
|
+
* ```ts
|
|
261
|
+
* import { test } from "@glubean/sdk";
|
|
262
|
+
*
|
|
263
|
+
* export const login = test("login", async (ctx) => {
|
|
264
|
+
* const res = await ctx.http.get(`${ctx.vars.require("BASE_URL")}/login`);
|
|
265
|
+
* ctx.assert(res.ok, "Login should succeed");
|
|
266
|
+
* });
|
|
267
|
+
* ```
|
|
268
|
+
*
|
|
269
|
+
* @example Quick mode with metadata
|
|
270
|
+
* ```ts
|
|
271
|
+
* export const login = test(
|
|
272
|
+
* { id: "login", tags: ["auth", "smoke"] },
|
|
273
|
+
* async (ctx) => {
|
|
274
|
+
* ctx.assert(true, "works");
|
|
275
|
+
* }
|
|
276
|
+
* );
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* @example Builder mode (multi-step) — .build() is optional
|
|
280
|
+
* ```ts
|
|
281
|
+
* export const checkout = test("checkout")
|
|
282
|
+
* .meta({ tags: ["e2e"] })
|
|
283
|
+
* .setup(async (ctx) => ({ cart: await createCart() }))
|
|
284
|
+
* .step("Add item", async (ctx, state) => { ... })
|
|
285
|
+
* .step("Pay", async (ctx, state) => { ... })
|
|
286
|
+
* .teardown(async (ctx, state) => { ... });
|
|
287
|
+
* ```
|
|
288
|
+
*
|
|
289
|
+
* @param idOrMeta Test ID (string) or full metadata object
|
|
290
|
+
* @param fn Optional test function (quick mode)
|
|
291
|
+
* @returns Test object (quick mode) or TestBuilder (builder mode)
|
|
292
|
+
*/
|
|
293
|
+
export declare function test<S = unknown>(idOrMeta: string | TestMeta): TestBuilder<S>;
|
|
294
|
+
export declare function test(idOrMeta: string | TestMeta, fn: SimpleTestFunction): Test;
|
|
295
|
+
/**
|
|
296
|
+
* Step function for data-driven builder tests.
|
|
297
|
+
* Receives context, current state, and the data row for this test.
|
|
298
|
+
*
|
|
299
|
+
* @template S The state type passed between steps
|
|
300
|
+
* @template T The data row type
|
|
301
|
+
* @template Ctx The context type (defaults to TestContext)
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* const stepFn: EachStepFunction<{ token: string }, { userId: number }> =
|
|
306
|
+
* async (ctx, state, row) => {
|
|
307
|
+
* const res = await ctx.http.get(`/users/${row.userId}`);
|
|
308
|
+
* ctx.assert(res.ok, `user ${row.userId} found`);
|
|
309
|
+
* return state; // pass state to next step
|
|
310
|
+
* };
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
export type EachStepFunction<S, T, Ctx extends TestContext = TestContext> = (ctx: Ctx, state: S, row: T) => Promise<S | void>;
|
|
314
|
+
/**
|
|
315
|
+
* Setup function for data-driven builder tests.
|
|
316
|
+
* Receives context and the data row, returns initial state.
|
|
317
|
+
*
|
|
318
|
+
* @template S The state type to return
|
|
319
|
+
* @template T The data row type
|
|
320
|
+
* @template Ctx The context type (defaults to TestContext)
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* ```ts
|
|
324
|
+
* const setupFn: EachSetupFunction<{ api: HttpClient }, { env: string }> =
|
|
325
|
+
* async (ctx, row) => {
|
|
326
|
+
* const api = ctx.http.extend({ prefixUrl: row.env });
|
|
327
|
+
* return { api };
|
|
328
|
+
* };
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
export type EachSetupFunction<S, T, Ctx extends TestContext = TestContext> = (ctx: Ctx, row: T) => Promise<S>;
|
|
332
|
+
/**
|
|
333
|
+
* Teardown function for data-driven builder tests.
|
|
334
|
+
*
|
|
335
|
+
* @template S The state type received from setup
|
|
336
|
+
* @template T The data row type
|
|
337
|
+
* @template Ctx The context type (defaults to TestContext)
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* const teardownFn: EachTeardownFunction<{ sessionId: string }, { userId: number }> =
|
|
342
|
+
* async (ctx, state, row) => {
|
|
343
|
+
* await ctx.http.delete(`/sessions/${state.sessionId}`);
|
|
344
|
+
* ctx.log(`cleaned up session for user ${row.userId}`);
|
|
345
|
+
* };
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
export type EachTeardownFunction<S, T, Ctx extends TestContext = TestContext> = (ctx: Ctx, state: S, row: T) => Promise<void>;
|
|
349
|
+
/**
|
|
350
|
+
* Builder for data-driven tests with multi-step workflow support.
|
|
351
|
+
*
|
|
352
|
+
* Created by `test.each(table)(idTemplate)` (without a callback).
|
|
353
|
+
* Provides the same fluent `.step()` / `.setup()` / `.teardown()` API
|
|
354
|
+
* as `TestBuilder`, but each step/setup/teardown also receives the
|
|
355
|
+
* data row for the current test.
|
|
356
|
+
*
|
|
357
|
+
* On finalization, creates one `Test` per row in the table, each with
|
|
358
|
+
* full step definitions visible in `glubean scan` metadata and dashboards.
|
|
359
|
+
*
|
|
360
|
+
* @template S The state type for multi-step tests
|
|
361
|
+
* @template T The data row type
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* export const userFlows = test.each([
|
|
366
|
+
* { userId: 1 },
|
|
367
|
+
* { userId: 2 },
|
|
368
|
+
* ])("user-flow-$userId")
|
|
369
|
+
* .step("fetch user", async (ctx, state, { userId }) => {
|
|
370
|
+
* const res = await ctx.http.get(`/users/${userId}`);
|
|
371
|
+
* ctx.assert(res.ok, "user exists");
|
|
372
|
+
* return { user: await res.json() };
|
|
373
|
+
* })
|
|
374
|
+
* .step("verify posts", async (ctx, { user }) => {
|
|
375
|
+
* const res = await ctx.http.get(`/users/${user.id}/posts`);
|
|
376
|
+
* ctx.assert(res.ok, "posts accessible");
|
|
377
|
+
* });
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
export declare class EachBuilder<S = unknown, T extends Record<string, unknown> = Record<string, unknown>, Ctx extends TestContext = TestContext> {
|
|
381
|
+
private _baseMeta;
|
|
382
|
+
private _table;
|
|
383
|
+
private _setup?;
|
|
384
|
+
private _teardown?;
|
|
385
|
+
private _steps;
|
|
386
|
+
private _built;
|
|
387
|
+
_fixtures?: Record<string, ExtensionFn<any>>;
|
|
388
|
+
/**
|
|
389
|
+
* Marker property so the runner and scanner can detect EachBuilder exports.
|
|
390
|
+
*/
|
|
391
|
+
readonly __glubean_type: "each-builder";
|
|
392
|
+
constructor(baseMeta: TestMeta, table: readonly T[], fixtures?: Record<string, ExtensionFn<any>>);
|
|
393
|
+
/**
|
|
394
|
+
* Set additional metadata for all generated tests.
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ```ts
|
|
398
|
+
* test.each(table)("user-$userId")
|
|
399
|
+
* .meta({ tags: ["smoke"], timeout: 10000 })
|
|
400
|
+
* .step("fetch", async (ctx, state, row) => { ... });
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
meta(meta: Omit<TestMeta, "id">): EachBuilder<S, T, Ctx>;
|
|
404
|
+
/**
|
|
405
|
+
* Mark all generated tests from this data set as focused.
|
|
406
|
+
* If `skip` is also set, skipped tests are still excluded.
|
|
407
|
+
*/
|
|
408
|
+
only(): EachBuilder<S, T, Ctx>;
|
|
409
|
+
/**
|
|
410
|
+
* Mark all generated tests from this data set as skipped.
|
|
411
|
+
* Skip takes precedence over `only` when both are present.
|
|
412
|
+
*/
|
|
413
|
+
skip(): EachBuilder<S, T, Ctx>;
|
|
414
|
+
/**
|
|
415
|
+
* Set the setup function. Receives context and data row, returns state.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* test.each(table)("id-$key")
|
|
420
|
+
* .setup(async (ctx, row) => {
|
|
421
|
+
* const api = ctx.http.extend({ headers: { "X-User": row.userId } });
|
|
422
|
+
* return { api };
|
|
423
|
+
* })
|
|
424
|
+
* .step("use api", async (ctx, { api }) => { ... });
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
setup<NewS>(fn: (ctx: Ctx, row: T) => Promise<NewS>): EachBuilder<NewS, T, Ctx>;
|
|
428
|
+
/**
|
|
429
|
+
* Set the teardown function. Runs after all steps (even on failure).
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```ts
|
|
433
|
+
* test.each(table)("user-$userId")
|
|
434
|
+
* .setup(async (ctx, row) => ({ token: await login(ctx, row) }))
|
|
435
|
+
* .step("test", async (ctx, { token }) => { ... })
|
|
436
|
+
* .teardown(async (ctx, state, row) => {
|
|
437
|
+
* await ctx.http.post("/logout", { body: { token: state.token } });
|
|
438
|
+
* });
|
|
439
|
+
* ```
|
|
440
|
+
*/
|
|
441
|
+
teardown(fn: (ctx: Ctx, state: S, row: T) => Promise<void>): EachBuilder<S, T, Ctx>;
|
|
442
|
+
/**
|
|
443
|
+
* Add a step that does not return state (void).
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```ts
|
|
447
|
+
* test.each(users)("user-$id")
|
|
448
|
+
* .step("verify", async (ctx, state, row) => {
|
|
449
|
+
* const res = await ctx.http.get(`/users/${row.id}`);
|
|
450
|
+
* ctx.expect(res.status).toBe(200);
|
|
451
|
+
* });
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
step(name: string, fn: (ctx: Ctx, state: S, row: T) => Promise<void>): EachBuilder<S, T, Ctx>;
|
|
455
|
+
/**
|
|
456
|
+
* Add a step that returns new state, replacing the current state type.
|
|
457
|
+
*/
|
|
458
|
+
step<NewS>(name: string, fn: (ctx: Ctx, state: S, row: T) => Promise<NewS>): EachBuilder<NewS, T, Ctx>;
|
|
459
|
+
/**
|
|
460
|
+
* Add a step with options (void return).
|
|
461
|
+
*/
|
|
462
|
+
step(name: string, options: Omit<StepMeta, "name">, fn: (ctx: Ctx, state: S, row: T) => Promise<void>): EachBuilder<S, T, Ctx>;
|
|
463
|
+
/**
|
|
464
|
+
* Add a step with options, returning new state.
|
|
465
|
+
*/
|
|
466
|
+
step<NewS>(name: string, options: Omit<StepMeta, "name">, fn: (ctx: Ctx, state: S, row: T) => Promise<NewS>): EachBuilder<NewS, T, Ctx>;
|
|
467
|
+
/**
|
|
468
|
+
* Apply a builder transform function for step composition.
|
|
469
|
+
*
|
|
470
|
+
* Works the same as `TestBuilder.use()` — reusable step sequences
|
|
471
|
+
* are plain functions that take a builder and return a builder.
|
|
472
|
+
*
|
|
473
|
+
* @param fn Transform function that receives this builder and returns a (possibly re-typed) builder
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* const withVerify = (b: EachBuilder<{ id: string }, { userId: number }>) => b
|
|
478
|
+
* .step("verify", async (ctx, { id }, row) => {
|
|
479
|
+
* ctx.expect(id).toBeTruthy();
|
|
480
|
+
* });
|
|
481
|
+
*
|
|
482
|
+
* export const users = test.each(table)("user-$userId")
|
|
483
|
+
* .setup(async (ctx, row) => ({ id: String(row.userId) }))
|
|
484
|
+
* .use(withVerify);
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
use<NewS>(fn: (builder: EachBuilder<S, T, Ctx>) => EachBuilder<NewS, T, Ctx>): EachBuilder<NewS, T, Ctx>;
|
|
488
|
+
/**
|
|
489
|
+
* Apply a builder transform and tag all newly added steps with a group ID.
|
|
490
|
+
*
|
|
491
|
+
* Works the same as `TestBuilder.group()` — steps added by `fn` are marked
|
|
492
|
+
* with `group` metadata for visual grouping in reports.
|
|
493
|
+
*
|
|
494
|
+
* @param id Group identifier (displayed in reports as a section header)
|
|
495
|
+
* @param fn Transform function that adds steps to the builder
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* export const users = test.each(table)("user-$userId")
|
|
500
|
+
* .group("setup", b => b
|
|
501
|
+
* .step("init", async (ctx, state, row) => ({ id: String(row.userId) }))
|
|
502
|
+
* )
|
|
503
|
+
* .step("verify", async (ctx, { id }) => { ... });
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
group<NewS>(id: string, fn: (builder: EachBuilder<S, T, Ctx>) => EachBuilder<NewS, T, Ctx>): EachBuilder<NewS, T, Ctx>;
|
|
507
|
+
/**
|
|
508
|
+
* Get the filtered table (apply filter callback if present).
|
|
509
|
+
* @internal
|
|
510
|
+
*/
|
|
511
|
+
private _filteredTable;
|
|
512
|
+
/**
|
|
513
|
+
* Compute tags for a specific row (static tags + tagFields).
|
|
514
|
+
* @internal
|
|
515
|
+
*/
|
|
516
|
+
private _tagsForRow;
|
|
517
|
+
/**
|
|
518
|
+
* Finalize and register all tests in the global registry.
|
|
519
|
+
* Called automatically via microtask if not explicitly invoked via build().
|
|
520
|
+
* Idempotent — safe to call multiple times.
|
|
521
|
+
* @internal
|
|
522
|
+
*/
|
|
523
|
+
private _finalize;
|
|
524
|
+
/**
|
|
525
|
+
* Build and register all tests. Returns a `Test[]` array.
|
|
526
|
+
*
|
|
527
|
+
* **Optional** — if omitted, the builder auto-finalizes via microtask
|
|
528
|
+
* and the runner will auto-detect the EachBuilder export.
|
|
529
|
+
*/
|
|
530
|
+
build(): Test<S>[];
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* An extended `test` function created by `test.extend()`.
|
|
534
|
+
*
|
|
535
|
+
* Behaves identically to the base `test()` but augments the context type
|
|
536
|
+
* with fixture properties. Supports quick mode, builder mode, `.each()`,
|
|
537
|
+
* `.pick()`, and chained `.extend()`.
|
|
538
|
+
*
|
|
539
|
+
* @template Ctx The augmented context type (TestContext & extensions)
|
|
540
|
+
*/
|
|
541
|
+
export interface ExtendedTest<Ctx extends TestContext> {
|
|
542
|
+
/** Quick mode: single-function test with augmented context. */
|
|
543
|
+
(idOrMeta: string | TestMeta, fn: (ctx: Ctx) => Promise<void>): Test;
|
|
544
|
+
/** Builder mode: multi-step test with augmented context. */
|
|
545
|
+
<S = unknown>(idOrMeta: string | TestMeta): TestBuilder<S, Ctx>;
|
|
546
|
+
/**
|
|
547
|
+
* Chain another set of extensions on top of the current ones.
|
|
548
|
+
* The returned test function has `Ctx & NewExtensions` as its context type.
|
|
549
|
+
*/
|
|
550
|
+
extend<E extends Record<string, ExtensionFn<unknown>>>(extensions: E): ExtendedTest<Ctx & ResolveExtensions<E>>;
|
|
551
|
+
/** Data-driven tests with augmented context. */
|
|
552
|
+
each<T extends Record<string, unknown>>(table: readonly T[]): {
|
|
553
|
+
(idOrMeta: string | TestMeta, fn: (ctx: Ctx, data: T) => Promise<void>): Test[];
|
|
554
|
+
(idOrMeta: string | TestMeta): EachBuilder<unknown, T, Ctx>;
|
|
555
|
+
};
|
|
556
|
+
/** Example-selection tests with augmented context. */
|
|
557
|
+
pick<T extends Record<string, unknown>>(examples: Record<string, T>, count?: number): {
|
|
558
|
+
(idOrMeta: string | TestMeta, fn: (ctx: Ctx, data: T & {
|
|
559
|
+
_pick: string;
|
|
560
|
+
}) => Promise<void>): Test[];
|
|
561
|
+
(idOrMeta: string | TestMeta): EachBuilder<unknown, T & {
|
|
562
|
+
_pick: string;
|
|
563
|
+
}, Ctx>;
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
export declare namespace test {
|
|
567
|
+
/**
|
|
568
|
+
* Mark a test definition as focused (`only: true`).
|
|
569
|
+
*
|
|
570
|
+
* Works in both quick mode and builder mode.
|
|
571
|
+
* If `skip` is also set on the same test, `skip` takes precedence.
|
|
572
|
+
*
|
|
573
|
+
* @example Quick mode
|
|
574
|
+
* ```ts
|
|
575
|
+
* export const focused = test.only("focused-login", async (ctx) => {
|
|
576
|
+
* ctx.expect(true).toBeTruthy();
|
|
577
|
+
* });
|
|
578
|
+
* ```
|
|
579
|
+
*
|
|
580
|
+
* @example Builder mode
|
|
581
|
+
* ```ts
|
|
582
|
+
* export const focusedFlow = test.only("focused-flow")
|
|
583
|
+
* .step("run", async (ctx) => {
|
|
584
|
+
* ctx.expect(true).toBeTruthy();
|
|
585
|
+
* });
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
function only<S = unknown>(idOrMeta: string | TestMeta): TestBuilder<S>;
|
|
589
|
+
function only(idOrMeta: string | TestMeta, fn: SimpleTestFunction): Test;
|
|
590
|
+
/**
|
|
591
|
+
* Mark a test definition as skipped (`skip: true`).
|
|
592
|
+
*
|
|
593
|
+
* Works in both quick mode and builder mode.
|
|
594
|
+
* Skip takes precedence over `only` when both are present.
|
|
595
|
+
*/
|
|
596
|
+
function skip<S = unknown>(idOrMeta: string | TestMeta): TestBuilder<S>;
|
|
597
|
+
function skip(idOrMeta: string | TestMeta, fn: SimpleTestFunction): Test;
|
|
598
|
+
function each<T extends Record<string, unknown>>(table: readonly T[]): {
|
|
599
|
+
(idOrMeta: string, fn: EachTestFunction<T>): Test[];
|
|
600
|
+
(idOrMeta: TestMeta, fn: EachTestFunction<T>): Test[];
|
|
601
|
+
(idOrMeta: string): EachBuilder<unknown, T>;
|
|
602
|
+
(idOrMeta: TestMeta): EachBuilder<unknown, T>;
|
|
603
|
+
};
|
|
604
|
+
/**
|
|
605
|
+
* Example-selection API — randomly picks N examples from a named map.
|
|
606
|
+
*
|
|
607
|
+
* `test.pick` is a thin wrapper over `test.each`. It selects a subset of
|
|
608
|
+
* examples from a `Record<string, T>`, injects a `_pick` field containing
|
|
609
|
+
* the example key name, and delegates to `test.each`.
|
|
610
|
+
*
|
|
611
|
+
* Because the return value is identical to `test.each`, all `test.each`
|
|
612
|
+
* options (`filter`, `tagFields`, `tags`) work transparently with `test.pick`.
|
|
613
|
+
*
|
|
614
|
+
* **Default behavior (no CLI override):** randomly selects `count` examples
|
|
615
|
+
* (default 1). This provides lightweight fuzz / smoke-test coverage.
|
|
616
|
+
*
|
|
617
|
+
* **CLI override:** `--pick key1,key2` (or env var `GLUBEAN_PICK`) selects
|
|
618
|
+
* specific examples by name, overriding random selection.
|
|
619
|
+
*
|
|
620
|
+
* **Run all:** `--pick all` or `--pick '*'` runs every example.
|
|
621
|
+
* Recommended for CI where you want full coverage.
|
|
622
|
+
*
|
|
623
|
+
* **Glob patterns:** `--pick 'us-*'` selects all keys matching the pattern.
|
|
624
|
+
* Useful when examples are grouped by prefix (e.g. regions, tenants).
|
|
625
|
+
*
|
|
626
|
+
* **VSCode integration:** CodeLens buttons let users click a specific
|
|
627
|
+
* example to run, which passes `--pick <key>` under the hood.
|
|
628
|
+
*
|
|
629
|
+
* Use `$_pick` in the ID template to include the example key in the test ID.
|
|
630
|
+
*
|
|
631
|
+
* @param examples A named map of example data rows
|
|
632
|
+
* @param count Number of examples to randomly select (default 1)
|
|
633
|
+
* @returns Same as `test.each` — a function accepting ID template and callback
|
|
634
|
+
*
|
|
635
|
+
* @example Inline examples
|
|
636
|
+
* ```ts
|
|
637
|
+
* export const createUser = test.pick({
|
|
638
|
+
* "normal": { name: "Alice", age: 25 },
|
|
639
|
+
* "edge-case": { name: "", age: -1 },
|
|
640
|
+
* "admin": { name: "Admin", role: "admin" },
|
|
641
|
+
* })("create-user-$_pick", async (ctx, example) => {
|
|
642
|
+
* await ctx.http.post("/api/users", { json: example });
|
|
643
|
+
* });
|
|
644
|
+
* ```
|
|
645
|
+
*
|
|
646
|
+
* @example With filter and tagFields (inherited from test.each)
|
|
647
|
+
* ```ts
|
|
648
|
+
* export const regionTests = test.pick(allRegions)({
|
|
649
|
+
* id: "region-$_pick",
|
|
650
|
+
* tagFields: ["currency", "_pick"],
|
|
651
|
+
* filter: (row) => row.currency === "USD",
|
|
652
|
+
* }, async (ctx, data) => {
|
|
653
|
+
* const res = await ctx.http.get(data.endpoint);
|
|
654
|
+
* ctx.expect(res).toHaveStatus(200);
|
|
655
|
+
* });
|
|
656
|
+
* ```
|
|
657
|
+
*
|
|
658
|
+
* @example CLI usage
|
|
659
|
+
* ```bash
|
|
660
|
+
* glubean run file.ts # random example (default)
|
|
661
|
+
* glubean run file.ts --pick normal # specific example
|
|
662
|
+
* glubean run file.ts --pick normal,admin # multiple examples
|
|
663
|
+
* glubean run file.ts --pick all # every example (CI)
|
|
664
|
+
* glubean run file.ts --pick 'us-*' # glob pattern
|
|
665
|
+
* ```
|
|
666
|
+
*/
|
|
667
|
+
function pick<T extends Record<string, unknown>>(examples: Record<string, T>, count?: number): ReturnType<typeof each<T & {
|
|
668
|
+
_pick: string;
|
|
669
|
+
}>>;
|
|
670
|
+
/**
|
|
671
|
+
* Create an extended `test` function with augmented context.
|
|
672
|
+
*
|
|
673
|
+
* Inspired by Playwright's `test.extend()`. Returns a new test function
|
|
674
|
+
* where `ctx` includes the resolved fixture properties alongside the
|
|
675
|
+
* base `TestContext` methods.
|
|
676
|
+
*
|
|
677
|
+
* @example Define shared fixtures
|
|
678
|
+
* ```ts
|
|
679
|
+
* // tests/fixtures.ts
|
|
680
|
+
* import { test as base } from "@glubean/sdk";
|
|
681
|
+
*
|
|
682
|
+
* export const test = base.extend({
|
|
683
|
+
* auth: (ctx) => createAuth(ctx.vars.require("AUTH_URL")),
|
|
684
|
+
* db: async (ctx, use) => {
|
|
685
|
+
* const db = await connect(ctx.vars.require("DB_URL"));
|
|
686
|
+
* await use(db);
|
|
687
|
+
* await db.disconnect();
|
|
688
|
+
* },
|
|
689
|
+
* });
|
|
690
|
+
* ```
|
|
691
|
+
*
|
|
692
|
+
* @example Use in tests
|
|
693
|
+
* ```ts
|
|
694
|
+
* // tests/users.test.ts
|
|
695
|
+
* import { test } from "./fixtures.js";
|
|
696
|
+
*
|
|
697
|
+
* export const myTest = test("my-test", async (ctx) => {
|
|
698
|
+
* ctx.auth; // full autocomplete
|
|
699
|
+
* ctx.db; // full autocomplete
|
|
700
|
+
* });
|
|
701
|
+
* ```
|
|
702
|
+
*
|
|
703
|
+
* @example Chained extend
|
|
704
|
+
* ```ts
|
|
705
|
+
* import { test as withAuth } from "./auth-fixtures.js";
|
|
706
|
+
* export const test = withAuth.extend({ db: ... });
|
|
707
|
+
* ```
|
|
708
|
+
*/
|
|
709
|
+
function extend<E extends Record<string, ExtensionFn<unknown>>>(extensions: E): ExtendedTest<TestContext & ResolveExtensions<E>>;
|
|
710
|
+
}
|
|
711
|
+
export * from "./types.js";
|
|
712
|
+
export { fromCsv, fromDir, fromJsonl, fromYaml, toArray } from "./data.js";
|
|
713
|
+
export type { FromCsvOptions, FromDirConcatOptions, FromDirOptions, FromYamlOptions } from "./data.js";
|
|
714
|
+
export { configure, resolveTemplate } from "./configure.js";
|
|
715
|
+
export { definePlugin } from "./plugin.js";
|
|
716
|
+
export { Expectation, ExpectFailError } from "./expect.js";
|
|
717
|
+
export type { AssertEmitter, AssertionEmission, CustomMatchers, MatcherFn, MatcherResult } from "./expect.js";
|
|
718
|
+
//# sourceMappingURL=index.d.ts.map
|