@definitely-fine/playwright 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 definitely-fine contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @definitely-fine/playwright
2
+
3
+ `@definitely-fine/playwright` helps Playwright tests activate `definitely-fine` scenarios in browser-driven flows.
4
+
5
+ It extends the core scenario builder with request headers plus helpers for creating browser contexts and pages that already send the active scenario id.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pnpm add -D @playwright/test @definitely-fine/playwright
11
+ pnpm add definitely-fine
12
+ ```
13
+
14
+ ## What This Package Adds
15
+
16
+ `createScenario()` from this package builds on the core `definitely-fine` scenario builder and adds:
17
+
18
+ - `headerName`
19
+ - `headers`
20
+ - `createContext(browser, options?)`
21
+ - `createPage(browser, options?)`
22
+
23
+ ```mermaid
24
+ flowchart LR
25
+ subgraph P[Playwright worker process]
26
+ P1[createScenario]
27
+ P2[define rules and save scenario]
28
+ P3[createContext or createPage]
29
+ end
30
+
31
+ subgraph B[Browser process]
32
+ B1[request with scenario header]
33
+ end
34
+
35
+ subgraph A[Application server process]
36
+ A1[@definitely-fine/nextjs or @definitely-fine/hono]
37
+ A2[runWithRuntimeScenarioContext]
38
+ A3[definitely-fine runtime]
39
+ end
40
+
41
+ subgraph S[Shared scenario storage]
42
+ S1[(persisted scenario JSON)]
43
+ end
44
+
45
+ P1 --> P2 --> S1
46
+ P2 --> P3
47
+ P3 --> B1
48
+ B1 --> A1 --> A2 --> A3
49
+ A3 -- load active scenario --> S1
50
+ ```
51
+
52
+ ## Example
53
+
54
+ ```ts
55
+ import { test, expect } from "@playwright/test";
56
+ import { DEFINITELY_FINE_SCENARIO_HEADER } from "@definitely-fine/nextjs";
57
+ import { createScenario } from "@definitely-fine/playwright";
58
+
59
+ type DemoContract = {
60
+ services: {
61
+ counter: {
62
+ incrementApi(): number;
63
+ incrementAction(): number;
64
+ };
65
+ };
66
+ functions: Record<string, never>;
67
+ errors: Record<string, never>;
68
+ };
69
+
70
+ test("uses a scenario-backed browser context", async ({ browser }) => {
71
+ const scenario = createScenario<DemoContract>({
72
+ headerName: DEFINITELY_FINE_SCENARIO_HEADER,
73
+ });
74
+
75
+ scenario.service("counter").method("incrementApi").onCall(1).returns(10);
76
+ await scenario.save();
77
+
78
+ const context = await scenario.createContext(browser);
79
+
80
+ try {
81
+ const page = await context.newPage();
82
+
83
+ await page.goto("/");
84
+ await page
85
+ .getByRole("button", { name: "Increment with API route" })
86
+ .click();
87
+ await expect(page.locator("#api-count")).toHaveText("10");
88
+ } finally {
89
+ await context.close();
90
+ await scenario.dispose();
91
+ }
92
+ });
93
+ ```
94
+
95
+ ## Header Behavior
96
+
97
+ The helper merges the scenario header into `extraHTTPHeaders` when it creates a new browser context.
98
+
99
+ That means requests from pages created through that context automatically activate the saved scenario in your server-side runtime, as long as your app reads the same header.
100
+
101
+ ## Explicit Directory Override
102
+
103
+ The built-in JSON storage can infer its directory automatically, so this is often enough:
104
+
105
+ ```ts
106
+ const scenario = createScenario({
107
+ headerName: "x-definitely-fine-scenario-id",
108
+ });
109
+ ```
110
+
111
+ If you need scenarios in a known local path, you can still override the directory:
112
+
113
+ ```ts
114
+ const scenario = createScenario({
115
+ directory: ".definitely-fine",
116
+ headerName: "x-definitely-fine-scenario-id",
117
+ });
118
+ ```
119
+
120
+ ## Typical Setup
121
+
122
+ 1. Build a scenario in the test.
123
+ 2. Save it before navigating.
124
+ 3. Create a browser context or page through the scenario helper.
125
+ 4. Run your browser flow.
126
+ 5. Let your app's `definitely-fine` runtime intercept the targeted calls.
127
+
128
+ ## Related Packages
129
+
130
+ - [`definitely-fine`](../definitely-fine/README.md) provides the runtime and storage model.
131
+ - [`@definitely-fine/nextjs`](../nextjs/README.md) reads the scenario header inside Next.js route handlers and server actions.
@@ -0,0 +1,420 @@
1
+ import { BrowserContextOptions } from "@playwright/test";
2
+
3
+ //#region ../definitely-fine/src/types.d.ts
4
+ /**
5
+ * Generic callable shape used throughout definitely-fine contracts.
6
+ * @public
7
+ */
8
+ /**
9
+ * Generic callable shape used throughout definitely-fine contracts.
10
+ * @public
11
+ */
12
+ type UnknownFunction = (...args: never[]) => unknown;
13
+ /**
14
+ * Contract shape for a named service whose values are callable methods.
15
+ * @public
16
+ */
17
+ type ServiceContract = Record<string, UnknownFunction>;
18
+ /**
19
+ * Map of named services available to a system under test.
20
+ * @public
21
+ */
22
+ type ServiceContracts = Record<string, ServiceContract>;
23
+ /**
24
+ * Map of named top-level functions available to a system under test.
25
+ * @public
26
+ */
27
+ type FunctionContracts = Record<string, UnknownFunction>;
28
+ /**
29
+ * Map of named error factory functions available to a runtime.
30
+ * @public
31
+ */
32
+ type ErrorFactoryContracts = Record<string, UnknownFunction>;
33
+ /**
34
+ * Full contract definition for services, functions, and error factories.
35
+ * @public
36
+ */
37
+ type SutContract = {
38
+ /**
39
+ * Named service contracts whose members are callable methods.
40
+ */
41
+ services: ServiceContracts;
42
+ /**
43
+ * Named top-level function contracts.
44
+ */
45
+ functions: FunctionContracts;
46
+ /**
47
+ * Named error factory contracts available to runtime rules.
48
+ */
49
+ errors: ErrorFactoryContracts;
50
+ };
51
+ /**
52
+ * Union of valid service names from a contract.
53
+ * @public
54
+ */
55
+ type ContractServiceName<TContract extends SutContract> = Extract<keyof TContract["services"], string>;
56
+ /**
57
+ * Union of valid function names from a contract.
58
+ * @public
59
+ */
60
+ type ContractFunctionName<TContract extends SutContract> = Extract<keyof TContract["functions"], string>;
61
+ /**
62
+ * Union of valid error factory names from a contract.
63
+ * @public
64
+ */
65
+ type ContractErrorFactoryName<TContract extends SutContract> = Extract<keyof TContract["errors"], string>;
66
+ /**
67
+ * Union of valid method names for a service in a contract.
68
+ * @public
69
+ */
70
+ type ContractServiceMethodName<TContract extends SutContract, TServiceName extends ContractServiceName<TContract>> = Extract<keyof TContract["services"][TServiceName], string>;
71
+ /**
72
+ * Function type for a named contract function.
73
+ * @public
74
+ */
75
+ type ContractFunction<TContract extends SutContract, TFunctionName extends ContractFunctionName<TContract>> = TContract["functions"][TFunctionName];
76
+ /**
77
+ * Method type for a named service method in a contract.
78
+ * @public
79
+ */
80
+ type ContractServiceMethod<TContract extends SutContract, TServiceName extends ContractServiceName<TContract>, TMethodName extends ContractServiceMethodName<TContract, TServiceName>> = TContract["services"][TServiceName][TMethodName];
81
+ /**
82
+ * Error factory type for a named contract factory.
83
+ * @public
84
+ */
85
+ type ContractErrorFactory<TContract extends SutContract, TFactoryName extends ContractErrorFactoryName<TContract>> = TContract["errors"][TFactoryName];
86
+ /**
87
+ * Resolved return value for a named contract function.
88
+ * @public
89
+ */
90
+ type ContractFunctionReturnValue<TContract extends SutContract, TFunctionName extends ContractFunctionName<TContract>> = Awaited<ReturnType<ContractFunction<TContract, TFunctionName>>>;
91
+ /**
92
+ * Resolved return value for a named contract service method.
93
+ * @public
94
+ */
95
+ type ContractServiceMethodReturnValue<TContract extends SutContract, TServiceName extends ContractServiceName<TContract>, TMethodName extends ContractServiceMethodName<TContract, TServiceName>> = Awaited<ReturnType<ContractServiceMethod<TContract, TServiceName, TMethodName>>>;
96
+ /**
97
+ * First input argument accepted by a named contract error factory.
98
+ * @public
99
+ */
100
+ type ContractErrorFactoryInput<TContract extends SutContract, TFactoryName extends ContractErrorFactoryName<TContract>> = Parameters<ContractErrorFactory<TContract, TFactoryName>>[0];
101
+ type ContractErrorFactoryParameters<TContract extends SutContract, TFactoryName extends ContractErrorFactoryName<TContract>> = Parameters<ContractErrorFactory<TContract, TFactoryName>>;
102
+ /**
103
+ * Tuple form accepted when configuring a thrown error factory action.
104
+ * @public
105
+ */
106
+ type ContractErrorFactoryArguments<TContract extends SutContract, TFactoryName extends ContractErrorFactoryName<TContract>> = ContractErrorFactoryParameters<TContract, TFactoryName>["length"] extends 0 ? [] : 0 extends ContractErrorFactoryParameters<TContract, TFactoryName>["length"] ? [input?: ContractErrorFactoryInput<TContract, TFactoryName>] : [input: ContractErrorFactoryInput<TContract, TFactoryName>];
107
+ /**
108
+ * Serialized identifier for a wrapped function or service method target.
109
+ * @public
110
+ */
111
+ type SerializedTarget = {
112
+ /**
113
+ * Marks the target as a service method.
114
+ */
115
+ kind: "service-method";
116
+ /**
117
+ * Service name that owns the intercepted method.
118
+ */
119
+ service: string;
120
+ /**
121
+ * Method name being intercepted on the service.
122
+ */
123
+ method: string;
124
+ } | {
125
+ /**
126
+ * Marks the target as a top-level function.
127
+ */
128
+ kind: "function";
129
+ /**
130
+ * Function name being intercepted.
131
+ */
132
+ function: string;
133
+ };
134
+ /**
135
+ * Serialized scenario action executed when a rule matches.
136
+ * @public
137
+ */
138
+ type SerializedAction = {
139
+ /**
140
+ * Marks the action as returning a value.
141
+ */
142
+ kind: "return";
143
+ /**
144
+ * Value returned to the caller when the rule matches.
145
+ */
146
+ value: unknown;
147
+ } | {
148
+ /**
149
+ * Marks the action as throwing a plain error message.
150
+ */
151
+ kind: "throw-message";
152
+ /**
153
+ * Error message thrown when the rule matches.
154
+ */
155
+ message: string;
156
+ } | {
157
+ /**
158
+ * Marks the action as invoking an error factory.
159
+ */
160
+ kind: "throw-factory";
161
+ /**
162
+ * Registered error factory name to invoke.
163
+ */
164
+ factory: string;
165
+ /**
166
+ * Optional input passed to the error factory.
167
+ */
168
+ input?: unknown;
169
+ };
170
+ /**
171
+ * Serialized interception rule for a single call target and call number.
172
+ * @public
173
+ */
174
+ type SerializedRule = {
175
+ /**
176
+ * Target that this rule applies to.
177
+ */
178
+ target: SerializedTarget;
179
+ /**
180
+ * One-based call number that activates this rule.
181
+ */
182
+ callNumber: number;
183
+ /**
184
+ * Action executed when the target is called at the matching count.
185
+ */
186
+ action: SerializedAction;
187
+ };
188
+ /**
189
+ * Serialized scenario document persisted by storage adapters.
190
+ * @public
191
+ */
192
+ type SerializedScenario = {
193
+ /**
194
+ * Stable identifier for the persisted scenario.
195
+ */
196
+ id: string;
197
+ /**
198
+ * Persisted schema version for the scenario document.
199
+ */
200
+ version: 1;
201
+ /**
202
+ * ISO timestamp when the scenario was created.
203
+ */
204
+ createdAt: string;
205
+ /**
206
+ * Ordered list of interception rules stored in the scenario.
207
+ */
208
+ rules: SerializedRule[];
209
+ };
210
+ /**
211
+ * Async-local context that carries the active runtime scenario id.
212
+ * @public
213
+ */
214
+
215
+ /**
216
+ * Builder API for assigning an action to a single intercepted call.
217
+ * @public
218
+ */
219
+ type ScenarioActionBuilder<TContract extends SutContract, TReturnValue> = {
220
+ returns(value: TReturnValue): ScenarioBuilder<TContract>;
221
+ throwsMessage(message: string): ScenarioBuilder<TContract>;
222
+ throwsFactory<TFactoryName extends ContractErrorFactoryName<TContract>>(factory: TFactoryName, ...args: ContractErrorFactoryArguments<TContract, TFactoryName>): ScenarioBuilder<TContract>;
223
+ };
224
+ /**
225
+ * Builder API for configuring rules for a named function.
226
+ * @public
227
+ */
228
+ type ScenarioFunctionRuleBuilder<TContract extends SutContract, TFunctionName extends ContractFunctionName<TContract>> = {
229
+ onCall(callNumber: number): ScenarioActionBuilder<TContract, ContractFunctionReturnValue<TContract, TFunctionName>>;
230
+ };
231
+ /**
232
+ * Builder API for configuring rules for a named service method.
233
+ * @public
234
+ */
235
+ type ScenarioServiceMethodRuleBuilder<TContract extends SutContract, TServiceName extends ContractServiceName<TContract>, TMethodName extends ContractServiceMethodName<TContract, TServiceName>> = {
236
+ onCall(callNumber: number): ScenarioActionBuilder<TContract, ContractServiceMethodReturnValue<TContract, TServiceName, TMethodName>>;
237
+ };
238
+ /**
239
+ * Builder API for selecting a method on a named service.
240
+ * @public
241
+ */
242
+ type ScenarioServiceRuleBuilder<TContract extends SutContract, TServiceName extends ContractServiceName<TContract>> = {
243
+ method<TMethodName extends ContractServiceMethodName<TContract, TServiceName>>(method: TMethodName): ScenarioServiceMethodRuleBuilder<TContract, TServiceName, TMethodName>;
244
+ };
245
+ /**
246
+ * Scenario builder API used to author and persist interception rules.
247
+ * @public
248
+ */
249
+ type ScenarioBuilder<TContract extends SutContract> = {
250
+ /**
251
+ * Phantom contract marker that preserves generic inference.
252
+ */
253
+ readonly __contract?: TContract;
254
+ /**
255
+ * Scenario id that will be used for persistence and runtime lookup.
256
+ */
257
+ readonly id: string;
258
+ service<TServiceName extends ContractServiceName<TContract>>(service: TServiceName): ScenarioServiceRuleBuilder<TContract, TServiceName>;
259
+ fn<TFunctionName extends ContractFunctionName<TContract>>(fn: TFunctionName): ScenarioFunctionRuleBuilder<TContract, TFunctionName>;
260
+ save(): Promise<void>;
261
+ dispose(): Promise<void>;
262
+ };
263
+
264
+ //#endregion
265
+ //#region ../definitely-fine/src/services/interfaces/IScenarioStorageAdapter.d.ts
266
+ /**
267
+ * Runtime API for wrapping contract functions and services.
268
+ * @public
269
+ */
270
+ /**
271
+ * Storage contract for loading, saving, and deleting persisted scenarios.
272
+ * @public
273
+ */
274
+ interface IScenarioStorageAdapter {
275
+ saveScenario(scenario: SerializedScenario): Promise<void>;
276
+ loadScenario(scenarioId: string): SerializedScenario | undefined;
277
+ deleteScenario(scenarioId: string): Promise<void>;
278
+ describeScenarioLocation?(scenarioId: string): string;
279
+ }
280
+
281
+ //#endregion
282
+ //#region ../definitely-fine/src/index.d.ts
283
+ //# sourceMappingURL=IScenarioStorageAdapter.d.ts.map
284
+
285
+ /**
286
+ * Storage options for persisting scenarios.
287
+ *
288
+ * Pass an explicit `adapter` to route persistence through custom storage, or a
289
+ * `directory` to keep using the built-in file adapter behavior.
290
+ *
291
+ * @example
292
+ * ```ts
293
+ * import {
294
+ * JsonScenarioStorageAdapter,
295
+ * createScenario,
296
+ * type IScenarioStorageAdapter,
297
+ * } from "definitely-fine";
298
+ *
299
+ * const customAdapter: IScenarioStorageAdapter = {
300
+ * async deleteScenario(_scenarioId) {},
301
+ * loadScenario(_scenarioId) {
302
+ * return undefined;
303
+ * },
304
+ * async saveScenario(_scenario) {},
305
+ * };
306
+ *
307
+ * createScenario({
308
+ * adapter: new JsonScenarioStorageAdapter({}),
309
+ * });
310
+ *
311
+ * createScenario({
312
+ * adapter: customAdapter,
313
+ * });
314
+ * ```
315
+ * @public
316
+ */
317
+ type ScenarioOptions = {
318
+ /**
319
+ * Omit storage options to use the built-in JSON adapter with its inferred default directory.
320
+ */
321
+ adapter?: never;
322
+ /**
323
+ * Omit storage options to use the built-in JSON adapter with its inferred default directory.
324
+ */
325
+ directory?: never;
326
+ } | {
327
+ /**
328
+ * Directory where the scenario should be saved.
329
+ */
330
+ directory: string;
331
+ /**
332
+ * Explicit adapters cannot be combined with a directory.
333
+ */
334
+ adapter?: never;
335
+ } | {
336
+ /**
337
+ * Custom adapter used to persist the scenario.
338
+ */
339
+ adapter: IScenarioStorageAdapter;
340
+ /**
341
+ * Explicit adapters cannot be combined with a directory.
342
+ */
343
+ directory?: never;
344
+ };
345
+
346
+ //#endregion
347
+ //#region src/index.d.ts
348
+ type BrowserContextFactory<TBrowserContext> = {
349
+ newContext(options?: BrowserContextOptions): Promise<TBrowserContext>;
350
+ };
351
+ type BrowserPageFactory<TPage> = {
352
+ newPage(): Promise<TPage>;
353
+ };
354
+ /**
355
+ * Playwright-aware scenario builder that can create preconfigured browser contexts.
356
+ * @public
357
+ */
358
+ type PlaywrightScenario<TContract extends SutContract> = ScenarioBuilder<TContract> & {
359
+ /**
360
+ * Header name used to activate the persisted scenario at runtime.
361
+ */
362
+ readonly headerName: string;
363
+ /**
364
+ * Request headers that activate the persisted scenario in browser-driven tests.
365
+ */
366
+ readonly headers: Record<string, string>;
367
+ /**
368
+ * Creates a new browser context with the scenario header already applied.
369
+ */
370
+ createContext<TBrowserContext>(browser: BrowserContextFactory<TBrowserContext>, options?: BrowserContextOptions): Promise<TBrowserContext>;
371
+ /**
372
+ * Creates a new page from a browser context that already includes the scenario header.
373
+ */
374
+ createPage<TPage, TBrowserContext extends BrowserPageFactory<TPage>>(browser: BrowserContextFactory<TBrowserContext>, options?: BrowserContextOptions): Promise<TPage>;
375
+ };
376
+ /**
377
+ * Options for creating a Playwright-aware definitely-fine scenario.
378
+ * @public
379
+ *
380
+ * @example
381
+ * ```ts
382
+ * import { createScenario } from "@definitely-fine/playwright";
383
+ *
384
+ * const scenario = createScenario({
385
+ * headerName: "x-scenario-id",
386
+ * });
387
+ * ```
388
+ */
389
+ type CreatePlaywrightScenarioOptions = ScenarioOptions & {
390
+ /**
391
+ * Header name used to activate the saved scenario in browser requests.
392
+ */
393
+ headerName: string;
394
+ };
395
+ /**
396
+ * Creates a definitely-fine scenario builder with helpers for Playwright browser contexts.
397
+ * @public
398
+ *
399
+ * @example
400
+ * ```ts
401
+ * import { test } from "@playwright/test";
402
+ * import { createScenario } from "@definitely-fine/playwright";
403
+ *
404
+ * test("uses a scenario-backed page", async ({ browser }) => {
405
+ * const scenario = createScenario({
406
+ * headerName: "x-scenario-id",
407
+ * });
408
+ *
409
+ * await scenario.save();
410
+ *
411
+ * const page = await scenario.createPage(browser);
412
+ * await page.goto("/");
413
+ * });
414
+ * ```
415
+ */
416
+ declare function createScenario<TContract extends SutContract>(options: CreatePlaywrightScenarioOptions): PlaywrightScenario<TContract>;
417
+
418
+ //#endregion
419
+ export { CreatePlaywrightScenarioOptions, PlaywrightScenario, createScenario };
420
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../definitely-fine/src/types.ts","../../definitely-fine/src/services/interfaces/IScenarioStorageAdapter.ts","../../definitely-fine/src/index.ts","../src/index.ts"],"sourcesContent":null,"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ import { createScenario as createScenario$1 } from "definitely-fine";
2
+
3
+ //#region src/index.ts
4
+ function mergeScenarioHeaders(extraHTTPHeaders, scenarioHeaders) {
5
+ const mergedHeaders = new Headers(extraHTTPHeaders);
6
+ for (const [name, value] of Object.entries(scenarioHeaders)) mergedHeaders.set(name, value);
7
+ return Object.fromEntries(mergedHeaders.entries());
8
+ }
9
+ function createPlaywrightScenarioHelpers(scenario, headerName) {
10
+ const headers = { [headerName]: scenario.id };
11
+ return {
12
+ headerName,
13
+ headers,
14
+ async createContext(browser, options = {}) {
15
+ return browser.newContext({
16
+ ...options,
17
+ extraHTTPHeaders: mergeScenarioHeaders(options.extraHTTPHeaders, headers)
18
+ });
19
+ },
20
+ async createPage(browser, options = {}) {
21
+ const context = await this.createContext(browser, options);
22
+ return context.newPage();
23
+ }
24
+ };
25
+ }
26
+ /**
27
+ * Creates a definitely-fine scenario builder with helpers for Playwright browser contexts.
28
+ * @public
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { test } from "@playwright/test";
33
+ * import { createScenario } from "@definitely-fine/playwright";
34
+ *
35
+ * test("uses a scenario-backed page", async ({ browser }) => {
36
+ * const scenario = createScenario({
37
+ * headerName: "x-scenario-id",
38
+ * });
39
+ *
40
+ * await scenario.save();
41
+ *
42
+ * const page = await scenario.createPage(browser);
43
+ * await page.goto("/");
44
+ * });
45
+ * ```
46
+ */
47
+ function createScenario(options) {
48
+ const scenario = createScenario$1(options);
49
+ return Object.assign(scenario, createPlaywrightScenarioHelpers(scenario, options.headerName));
50
+ }
51
+
52
+ //#endregion
53
+ export { createScenario };
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["extraHTTPHeaders: BrowserContextOptions[\"extraHTTPHeaders\"]","scenarioHeaders: Record<string, string>","scenario: ScenarioBuilder<TContract>","headerName: string","browser: BrowserContextFactory<TBrowserContext>","options: BrowserContextOptions","options: CreatePlaywrightScenarioOptions"],"sources":["../src/index.ts"],"sourcesContent":["import { createScenario as createCoreScenario } from \"definitely-fine\";\n\nimport type { BrowserContextOptions } from \"@playwright/test\";\nimport type {\n ScenarioBuilder,\n ScenarioOptions,\n SutContract,\n} from \"definitely-fine\";\n\ntype BrowserContextFactory<TBrowserContext> = {\n newContext(options?: BrowserContextOptions): Promise<TBrowserContext>;\n};\n\ntype BrowserPageFactory<TPage> = {\n newPage(): Promise<TPage>;\n};\n\n/**\n * Playwright-aware scenario builder that can create preconfigured browser contexts.\n * @public\n */\nexport type PlaywrightScenario<TContract extends SutContract> =\n ScenarioBuilder<TContract> & {\n /**\n * Header name used to activate the persisted scenario at runtime.\n */\n readonly headerName: string;\n /**\n * Request headers that activate the persisted scenario in browser-driven tests.\n */\n readonly headers: Record<string, string>;\n /**\n * Creates a new browser context with the scenario header already applied.\n */\n createContext<TBrowserContext>(\n browser: BrowserContextFactory<TBrowserContext>,\n options?: BrowserContextOptions,\n ): Promise<TBrowserContext>;\n /**\n * Creates a new page from a browser context that already includes the scenario header.\n */\n createPage<TPage, TBrowserContext extends BrowserPageFactory<TPage>>(\n browser: BrowserContextFactory<TBrowserContext>,\n options?: BrowserContextOptions,\n ): Promise<TPage>;\n };\n\n/**\n * Options for creating a Playwright-aware definitely-fine scenario.\n * @public\n *\n * @example\n * ```ts\n * import { createScenario } from \"@definitely-fine/playwright\";\n *\n * const scenario = createScenario({\n * headerName: \"x-scenario-id\",\n * });\n * ```\n */\nexport type CreatePlaywrightScenarioOptions = ScenarioOptions & {\n /**\n * Header name used to activate the saved scenario in browser requests.\n */\n headerName: string;\n};\n\nfunction mergeScenarioHeaders(\n extraHTTPHeaders: BrowserContextOptions[\"extraHTTPHeaders\"],\n scenarioHeaders: Record<string, string>,\n): Record<string, string> {\n const mergedHeaders = new Headers(extraHTTPHeaders);\n\n for (const [name, value] of Object.entries(scenarioHeaders)) {\n mergedHeaders.set(name, value);\n }\n\n return Object.fromEntries(mergedHeaders.entries());\n}\n\nfunction createPlaywrightScenarioHelpers<TContract extends SutContract>(\n scenario: ScenarioBuilder<TContract>,\n headerName: string,\n) {\n const headers = {\n [headerName]: scenario.id,\n };\n\n return {\n headerName,\n headers,\n async createContext<TBrowserContext>(\n browser: BrowserContextFactory<TBrowserContext>,\n options: BrowserContextOptions = {},\n ): Promise<TBrowserContext> {\n return browser.newContext({\n ...options,\n extraHTTPHeaders: mergeScenarioHeaders(\n options.extraHTTPHeaders,\n headers,\n ),\n });\n },\n async createPage<TPage, TBrowserContext extends BrowserPageFactory<TPage>>(\n browser: BrowserContextFactory<TBrowserContext>,\n options: BrowserContextOptions = {},\n ): Promise<TPage> {\n const context = await this.createContext(browser, options);\n\n return context.newPage();\n },\n };\n}\n\n/**\n * Creates a definitely-fine scenario builder with helpers for Playwright browser contexts.\n * @public\n *\n * @example\n * ```ts\n * import { test } from \"@playwright/test\";\n * import { createScenario } from \"@definitely-fine/playwright\";\n *\n * test(\"uses a scenario-backed page\", async ({ browser }) => {\n * const scenario = createScenario({\n * headerName: \"x-scenario-id\",\n * });\n *\n * await scenario.save();\n *\n * const page = await scenario.createPage(browser);\n * await page.goto(\"/\");\n * });\n * ```\n */\nexport function createScenario<TContract extends SutContract>(\n options: CreatePlaywrightScenarioOptions,\n): PlaywrightScenario<TContract> {\n const scenario = createCoreScenario<TContract>(options);\n\n return Object.assign(\n scenario,\n createPlaywrightScenarioHelpers(scenario, options.headerName),\n );\n}\n"],"mappings":";;;AAmEA,SAAS,qBACPA,kBACAC,iBACwB;CACxB,MAAM,gBAAgB,IAAI,QAAQ;AAElC,MAAK,MAAM,CAAC,MAAM,MAAM,IAAI,OAAO,QAAQ,gBAAgB,CACzD,eAAc,IAAI,MAAM,MAAM;AAGhC,QAAO,OAAO,YAAY,cAAc,SAAS,CAAC;AACnD;AAED,SAAS,gCACPC,UACAC,YACA;CACA,MAAM,UAAU,GACb,aAAa,SAAS,GACxB;AAED,QAAO;EACL;EACA;EACA,MAAM,cACJC,SACAC,UAAiC,CAAE,GACT;AAC1B,UAAO,QAAQ,WAAW;IACxB,GAAG;IACH,kBAAkB,qBAChB,QAAQ,kBACR,QACD;GACF,EAAC;EACH;EACD,MAAM,WACJD,SACAC,UAAiC,CAAE,GACnB;GAChB,MAAM,UAAU,MAAM,KAAK,cAAc,SAAS,QAAQ;AAE1D,UAAO,QAAQ,SAAS;EACzB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBD,SAAgB,eACdC,SAC+B;CAC/B,MAAM,WAAW,iBAA8B,QAAQ;AAEvD,QAAO,OAAO,OACZ,UACA,gCAAgC,UAAU,QAAQ,WAAW,CAC9D;AACF"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@definitely-fine/playwright",
3
+ "version": "0.1.0",
4
+ "description": "Playwright helpers for definitely-fine.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "dependencies": {
20
+ "definitely-fine": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@playwright/test": "^1.56.1"
24
+ },
25
+ "peerDependencies": {
26
+ "@playwright/test": ">=1.56"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown src/index.ts --format esm --dts --publint",
36
+ "typecheck": "tsc --project tsconfig.json"
37
+ }
38
+ }