@esimplicitylabs/katalyst-xspec 0.6.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.
Files changed (29) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +69 -0
  3. package/bin/katalyst-xspec.cjs +54 -0
  4. package/cli/init.cjs +679 -0
  5. package/cli/stubs.cjs +365 -0
  6. package/cli/upgrade.cjs +1014 -0
  7. package/dist/chunk-ACAXOGKZ.js +1611 -0
  8. package/dist/index.d.ts +881 -0
  9. package/dist/index.js +1091 -0
  10. package/dist/steps/index.d.ts +151 -0
  11. package/dist/steps/index.js +50 -0
  12. package/package.json +80 -0
  13. package/scripts/postinstall.cjs +85 -0
  14. package/skills/katalyst-bdd-architecture/SKILL.md +517 -0
  15. package/skills/katalyst-bdd-architecture/references/adapters.md +310 -0
  16. package/skills/katalyst-bdd-architecture/references/custom-steps.md +360 -0
  17. package/skills/katalyst-bdd-architecture/references/ports.md +256 -0
  18. package/skills/katalyst-bdd-create-test/SKILL.md +366 -0
  19. package/skills/katalyst-bdd-create-test/references/api-patterns.md +371 -0
  20. package/skills/katalyst-bdd-create-test/references/hybrid-patterns.md +420 -0
  21. package/skills/katalyst-bdd-create-test/references/tui-patterns.md +458 -0
  22. package/skills/katalyst-bdd-create-test/references/ui-patterns.md +415 -0
  23. package/skills/katalyst-bdd-quickstart/SKILL.md +292 -0
  24. package/skills/katalyst-bdd-step-reference/SKILL.md +147 -0
  25. package/skills/katalyst-bdd-step-reference/references/api-steps.md +247 -0
  26. package/skills/katalyst-bdd-step-reference/references/shared-steps.md +340 -0
  27. package/skills/katalyst-bdd-step-reference/references/tui-steps.md +483 -0
  28. package/skills/katalyst-bdd-step-reference/references/ui-steps.md +521 -0
  29. package/skills/katalyst-bdd-troubleshooting/SKILL.md +449 -0
@@ -0,0 +1,1611 @@
1
+ // src/steps/api.http.ts
2
+ import { createBdd } from "playwright-bdd";
3
+
4
+ // src/utils.ts
5
+ import { expect } from "@playwright/test";
6
+ function interpolate(template, vars) {
7
+ return String(template).replace(/\{([a-zA-Z0-9_-]+)\}/g, (_, key) => {
8
+ const v = vars[key];
9
+ return v === void 0 ? `{${key}}` : v;
10
+ });
11
+ }
12
+ function tryParseJson(text) {
13
+ try {
14
+ return JSON.parse(text);
15
+ } catch {
16
+ return void 0;
17
+ }
18
+ }
19
+ function tokenize(path) {
20
+ const out = [];
21
+ let i = 0;
22
+ while (i < path.length) {
23
+ if (path[i] === ".") {
24
+ i++;
25
+ continue;
26
+ }
27
+ if (path[i] === "[") {
28
+ const j2 = path.indexOf("]", i + 1);
29
+ if (j2 < 0) throw new Error(`Invalid path: ${path}`);
30
+ const idxStr = path.slice(i + 1, j2);
31
+ const idx = Number(idxStr);
32
+ if (!Number.isInteger(idx)) throw new Error(`Invalid array index '${idxStr}' in path '${path}'`);
33
+ out.push(idx);
34
+ i = j2 + 1;
35
+ continue;
36
+ }
37
+ let j = i;
38
+ while (j < path.length && /[a-zA-Z0-9_-]/.test(path[j])) j++;
39
+ const key = path.slice(i, j);
40
+ out.push(key);
41
+ i = j;
42
+ }
43
+ return out;
44
+ }
45
+ function selectPath(root, path) {
46
+ if (!path) return root;
47
+ let cur = root;
48
+ for (const token of tokenize(path)) {
49
+ if (typeof token === "number") {
50
+ if (!Array.isArray(cur)) {
51
+ const gotType = cur === null ? "null" : Array.isArray(cur) ? "array" : typeof cur;
52
+ throw new Error(`Path '${path}': expected array but got ${gotType}`);
53
+ }
54
+ cur = cur[token];
55
+ } else {
56
+ if (cur == null || typeof cur !== "object" || Array.isArray(cur)) {
57
+ const gotType = cur === null ? "null" : Array.isArray(cur) ? "array" : typeof cur;
58
+ throw new Error(`Path '${path}': expected object but got ${gotType}`);
59
+ }
60
+ cur = cur[token];
61
+ }
62
+ }
63
+ return cur;
64
+ }
65
+ function parseExpected(input, world) {
66
+ const s = interpolate(input, world.vars);
67
+ if (s === "null") return null;
68
+ if (s === "true") return true;
69
+ if (s === "false") return false;
70
+ if (!Number.isNaN(Number(s)) && s.trim() !== "") return Number(s);
71
+ return s;
72
+ }
73
+ function assertMasked(val) {
74
+ expect(val).toBe("****");
75
+ }
76
+ function registerCleanup(world, item) {
77
+ const method = item.method ?? "DELETE";
78
+ if (world.cleanup.some((c) => c.method === method && c.path === item.path)) return;
79
+ world.cleanup.push({ method, path: item.path, body: item.body });
80
+ }
81
+
82
+ // src/steps/api.http.ts
83
+ function registerApiHttpSteps(test) {
84
+ const { When } = createBdd(test);
85
+ async function send(args, method, pathTemplate, body) {
86
+ const { api, world } = args;
87
+ const path = interpolate(pathTemplate, world.vars);
88
+ const result = await api.sendJson(method, path, body, world.headers);
89
+ world.lastStatus = result.status;
90
+ world.lastText = result.text;
91
+ world.lastJson = result.json;
92
+ world.lastHeaders = result.headers;
93
+ world.lastContentType = result.contentType;
94
+ world.lastResponse = result.response;
95
+ }
96
+ When("I GET {string}", { tags: "@api or @hybrid" }, async ({ api, world }, path) => {
97
+ await send({ api, world }, "GET", path);
98
+ });
99
+ When("I DELETE {string}", { tags: "@api or @hybrid" }, async ({ api, world }, path) => {
100
+ await send({ api, world }, "DELETE", path);
101
+ });
102
+ When("I POST {string} with JSON body:", { tags: "@api or @hybrid" }, async ({ api, world }, path, docString) => {
103
+ const interpolated = interpolate(docString ?? "", world.vars);
104
+ const parsed = tryParseJson(interpolated) ?? interpolated;
105
+ await send({ api, world }, "POST", path, parsed);
106
+ });
107
+ When("I PATCH {string} with JSON body:", { tags: "@api or @hybrid" }, async ({ api, world }, path, docString) => {
108
+ const interpolated = interpolate(docString ?? "", world.vars);
109
+ const parsed = tryParseJson(interpolated) ?? interpolated;
110
+ await send({ api, world }, "PATCH", path, parsed);
111
+ });
112
+ When("I PUT {string} with JSON body:", { tags: "@api or @hybrid" }, async ({ api, world }, path, docString) => {
113
+ const interpolated = interpolate(docString ?? "", world.vars);
114
+ const parsed = tryParseJson(interpolated) ?? interpolated;
115
+ await send({ api, world }, "PUT", path, parsed);
116
+ });
117
+ }
118
+
119
+ // src/steps/api.assertion.ts
120
+ import { createBdd as createBdd2 } from "playwright-bdd";
121
+ import { expect as expect2 } from "@playwright/test";
122
+ function registerApiAssertionSteps(test) {
123
+ const { Then } = createBdd2(test);
124
+ Then("the response status should be {int}", { tags: "@api or @hybrid" }, async ({ world }, code) => {
125
+ expect2(world.lastStatus, `Unexpected status. Body: ${world.lastText}`).toBe(code);
126
+ });
127
+ Then("the response should be a JSON array", { tags: "@api or @hybrid" }, async ({ world }) => {
128
+ expect2(Array.isArray(world.lastJson), `Expected JSON array. Body: ${world.lastText}`).toBe(true);
129
+ });
130
+ Then("the response should be a JSON object", { tags: "@api or @hybrid" }, async ({ world }) => {
131
+ const v = world.lastJson;
132
+ expect2(v !== null && typeof v === "object" && !Array.isArray(v), `Expected JSON object. Body: ${world.lastText}`).toBe(true);
133
+ });
134
+ Then("the value at {string} should equal {string}", { tags: "@api or @hybrid" }, async ({ world }, path, expectedRaw) => {
135
+ const actual = selectPath(world.lastJson, path);
136
+ const expected = parseExpected(expectedRaw, world);
137
+ expect2(actual).toEqual(expected);
138
+ });
139
+ Then("I store the value at {string} as {string}", { tags: "@api or @hybrid" }, async ({ world, cleanup }, path, varName) => {
140
+ const val = selectPath(world.lastJson, path);
141
+ world.vars[varName] = String(val);
142
+ cleanup.registerFromVar(world, varName, val, world.lastJson);
143
+ });
144
+ }
145
+
146
+ // src/steps/api.auth.ts
147
+ import { createBdd as createBdd3 } from "playwright-bdd";
148
+ function registerApiAuthSteps(test) {
149
+ const { Given } = createBdd3(test);
150
+ Given("I am authenticated as an admin via API", { tags: "@api or @hybrid" }, async ({ auth, world }) => {
151
+ await auth.apiLoginAsAdmin(world);
152
+ });
153
+ Given("I am authenticated as a user via API", { tags: "@api or @hybrid" }, async ({ auth, world }) => {
154
+ await auth.apiLoginAsUser(world);
155
+ });
156
+ Given("I set bearer token from variable {string}", { tags: "@api or @hybrid" }, async ({ auth, world }, varName) => {
157
+ const token = world.vars[varName];
158
+ if (!token) throw new Error(`No token found in variable '${varName}'`);
159
+ auth.apiSetBearer(world, token);
160
+ });
161
+ }
162
+
163
+ // src/steps/hybrid.ts
164
+ import { createBdd as createBdd4 } from "playwright-bdd";
165
+ function registerHybridSteps(test) {
166
+ const { Given } = createBdd4(test);
167
+ Given("I navigate to interpolated path {string}", { tags: "@hybrid" }, async ({ ui, world }, path) => {
168
+ await ui.goto(interpolate(path, world.vars));
169
+ });
170
+ }
171
+
172
+ // src/steps/shared.cleanup.ts
173
+ import { createBdd as createBdd5 } from "playwright-bdd";
174
+ function registerSharedCleanupSteps(test) {
175
+ const { Given } = createBdd5(test);
176
+ Given("I register cleanup DELETE {string}", async ({ world }, pathTemplate) => {
177
+ registerCleanup(world, { method: "DELETE", path: interpolate(pathTemplate, world.vars) });
178
+ });
179
+ Given("I register cleanup POST {string}", async ({ world }, pathTemplate) => {
180
+ registerCleanup(world, { method: "POST", path: interpolate(pathTemplate, world.vars) });
181
+ });
182
+ Given("I register cleanup PATCH {string}", async ({ world }, pathTemplate) => {
183
+ registerCleanup(world, { method: "PATCH", path: interpolate(pathTemplate, world.vars) });
184
+ });
185
+ Given("I register cleanup PUT {string}", async ({ world }, pathTemplate) => {
186
+ registerCleanup(world, { method: "PUT", path: interpolate(pathTemplate, world.vars) });
187
+ });
188
+ }
189
+
190
+ // src/steps/shared.vars.ts
191
+ import { createBdd as createBdd6 } from "playwright-bdd";
192
+ import { randomUUID } from "crypto";
193
+ function registerSharedVarSteps(test) {
194
+ const { Given } = createBdd6(test);
195
+ Given("I set variable {string} to {string}", async ({ world }, name, value) => {
196
+ world.vars[name] = value;
197
+ });
198
+ Given("I generate a UUID and store as {string}", async ({ world }, name) => {
199
+ world.vars[name] = randomUUID();
200
+ });
201
+ Given("I set header {string} to {string}", async ({ world }, header, value) => {
202
+ world.headers = { ...world.headers || {}, [header]: value };
203
+ });
204
+ Given("I disable cleanup", async ({ world }) => {
205
+ world.skipCleanup = true;
206
+ });
207
+ }
208
+
209
+ // src/steps/shared.flags.ts
210
+ import { createBdd as createBdd7 } from "playwright-bdd";
211
+ var globalFlags = {};
212
+ function isFlagEnabled(world, flagName) {
213
+ const worldFlags = world._featureFlags || {};
214
+ if (flagName in worldFlags) {
215
+ return worldFlags[flagName];
216
+ }
217
+ return globalFlags[flagName] ?? false;
218
+ }
219
+ function setFlag(world, flagName, enabled) {
220
+ if (!world._featureFlags) {
221
+ world._featureFlags = {};
222
+ }
223
+ world._featureFlags[flagName] = enabled;
224
+ world.vars[`flag_${flagName}`] = String(enabled);
225
+ }
226
+ function registerFlagSteps(test) {
227
+ const { Given, Then } = createBdd7(test);
228
+ Given(
229
+ "the feature flag {string} is enabled",
230
+ async ({ world }, flagName) => {
231
+ setFlag(world, flagName, true);
232
+ console.log(`[BDD] Feature flag "${flagName}" enabled`);
233
+ }
234
+ );
235
+ Given(
236
+ "the feature flag {string} is disabled",
237
+ async ({ world }, flagName) => {
238
+ setFlag(world, flagName, false);
239
+ console.log(`[BDD] Feature flag "${flagName}" disabled`);
240
+ }
241
+ );
242
+ Given(
243
+ "the feature flag {string} is set to {string}",
244
+ async ({ world }, flagName, value) => {
245
+ const enabled = value.toLowerCase() === "true" || value === "1";
246
+ setFlag(world, flagName, enabled);
247
+ console.log(`[BDD] Feature flag "${flagName}" set to ${enabled}`);
248
+ }
249
+ );
250
+ Given(
251
+ "the following feature flags are enabled:",
252
+ async ({ world }, dataTable) => {
253
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1) || [];
254
+ for (const row of rows) {
255
+ const flagName = row.flag || row[0];
256
+ setFlag(world, flagName, true);
257
+ console.log(`[BDD] Feature flag "${flagName}" enabled`);
258
+ }
259
+ }
260
+ );
261
+ Given(
262
+ "the following feature flags are disabled:",
263
+ async ({ world }, dataTable) => {
264
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1) || [];
265
+ for (const row of rows) {
266
+ const flagName = row.flag || row[0];
267
+ setFlag(world, flagName, false);
268
+ console.log(`[BDD] Feature flag "${flagName}" disabled`);
269
+ }
270
+ }
271
+ );
272
+ Then(
273
+ "the feature flag {string} should be enabled",
274
+ async ({ world }, flagName) => {
275
+ const enabled = isFlagEnabled(world, flagName);
276
+ if (!enabled) {
277
+ throw new Error(`Expected feature flag "${flagName}" to be enabled, but it was disabled`);
278
+ }
279
+ }
280
+ );
281
+ Then(
282
+ "the feature flag {string} should be disabled",
283
+ async ({ world }, flagName) => {
284
+ const enabled = isFlagEnabled(world, flagName);
285
+ if (enabled) {
286
+ throw new Error(`Expected feature flag "${flagName}" to be disabled, but it was enabled`);
287
+ }
288
+ }
289
+ );
290
+ Then(
291
+ "I log all feature flags",
292
+ async ({ world }) => {
293
+ const worldFlags = world._featureFlags || {};
294
+ console.log("[DEBUG] Feature flags:");
295
+ for (const [name, enabled] of Object.entries(worldFlags)) {
296
+ console.log(` - ${name}: ${enabled ? "enabled" : "disabled"}`);
297
+ }
298
+ if (Object.keys(worldFlags).length === 0) {
299
+ console.log(" (no flags set)");
300
+ }
301
+ }
302
+ );
303
+ }
304
+
305
+ // src/steps/ui.basic.ts
306
+ import { createBdd as createBdd8 } from "playwright-bdd";
307
+ import { expect as expect4 } from "@playwright/test";
308
+
309
+ // src/helpers/ui-element.ts
310
+ import { expect as expect3 } from "@playwright/test";
311
+ var MIME_TYPES = {
312
+ csv: "text/csv",
313
+ json: "application/json",
314
+ xml: "application/xml"
315
+ };
316
+ function mimeTypeForFileName(fileName) {
317
+ const dot = fileName.lastIndexOf(".");
318
+ if (dot === -1) return "text/plain";
319
+ return MIME_TYPES[fileName.slice(dot + 1).toLowerCase()] ?? "text/plain";
320
+ }
321
+ async function setFileInputContent(page, selector, fileName, content) {
322
+ await page.locator(selector).setInputFiles({
323
+ name: fileName,
324
+ mimeType: mimeTypeForFileName(fileName),
325
+ buffer: Buffer.from(content)
326
+ });
327
+ }
328
+ async function expectElementAttribute(page, selector, attribute, value, options) {
329
+ await expect3(page.locator(selector)).toHaveAttribute(attribute, value, options);
330
+ }
331
+
332
+ // src/steps/ui.basic.ts
333
+ function registerUiBasicSteps(test) {
334
+ const { Given, When, Then } = createBdd8(test);
335
+ Given("I navigate to {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, path) => {
336
+ await ui.goto(interpolate(path, world.vars));
337
+ });
338
+ When("I click the button {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, name) => {
339
+ await ui.clickButton(interpolate(name, world.vars));
340
+ });
341
+ When("I click the {string} button", { tags: "@ui or @hybrid" }, async ({ ui, world }, name) => {
342
+ await ui.clickButton(interpolate(name, world.vars));
343
+ });
344
+ When("I click the link {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, name) => {
345
+ await ui.clickLink(interpolate(name, world.vars));
346
+ });
347
+ When("I fill the placeholder {string} with {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, placeholder, value) => {
348
+ await ui.fillPlaceholder(interpolate(placeholder, world.vars), interpolate(value, world.vars));
349
+ });
350
+ When("I fill the field {string} with {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, label, value) => {
351
+ await ui.fillLabel(interpolate(label, world.vars), interpolate(value, world.vars));
352
+ });
353
+ When("I fill in {string} with {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, label, value) => {
354
+ await ui.fillLabel(interpolate(label, world.vars), interpolate(value, world.vars));
355
+ });
356
+ When("I log in as admin in UI", { tags: "@ui or @hybrid" }, async ({ auth, world }) => {
357
+ await auth.uiLoginAsAdmin(world);
358
+ });
359
+ When("I log in as user in UI", { tags: "@ui or @hybrid" }, async ({ auth, world }) => {
360
+ await auth.uiLoginAsUser(world);
361
+ });
362
+ When("I click the element {string}", { tags: "@ui or @hybrid" }, async ({ page, world }, selector) => {
363
+ await page.locator(interpolate(selector, world.vars)).click();
364
+ });
365
+ When("I select {string} from dropdown {string}", { tags: "@ui or @hybrid" }, async ({ page, world }, option, selector) => {
366
+ await page.locator(interpolate(selector, world.vars)).selectOption({ label: interpolate(option, world.vars) });
367
+ });
368
+ When(
369
+ "I set the file input {string} to a file named {string} with content {string}",
370
+ { tags: "@ui or @hybrid" },
371
+ async ({ page, world }, selector, fileName, content) => {
372
+ await setFileInputContent(
373
+ page,
374
+ interpolate(selector, world.vars),
375
+ interpolate(fileName, world.vars),
376
+ interpolate(content, world.vars)
377
+ );
378
+ }
379
+ );
380
+ Then("I should see text {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, text) => {
381
+ await ui.expectText(interpolate(text, world.vars));
382
+ });
383
+ Then("the URL should contain {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, part) => {
384
+ await ui.expectUrlContains(interpolate(part, world.vars));
385
+ });
386
+ Then("I should be on page {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, path) => {
387
+ await ui.expectUrlContains(interpolate(path, world.vars));
388
+ });
389
+ Then("the element {string} should be visible", { tags: "@ui or @hybrid" }, async ({ page, world }, selector) => {
390
+ await expect4(page.locator(interpolate(selector, world.vars))).toBeVisible();
391
+ });
392
+ Then("the element {string} should not be visible", { tags: "@ui or @hybrid" }, async ({ page, world }, selector) => {
393
+ await expect4(page.locator(interpolate(selector, world.vars))).not.toBeVisible();
394
+ });
395
+ Then("the element {string} should have value {string}", { tags: "@ui or @hybrid" }, async ({ page, world }, selector, value) => {
396
+ await expect4(page.locator(interpolate(selector, world.vars))).toHaveValue(interpolate(value, world.vars));
397
+ });
398
+ Then("the element {string} should have attribute {string} equal to {string}", { tags: "@ui or @hybrid" }, async ({ page, world }, selector, attribute, value) => {
399
+ await expectElementAttribute(
400
+ page,
401
+ interpolate(selector, world.vars),
402
+ interpolate(attribute, world.vars),
403
+ interpolate(value, world.vars)
404
+ );
405
+ });
406
+ Then("the element {string} should be checked", { tags: "@ui or @hybrid" }, async ({ page, world }, selector) => {
407
+ await expect4(page.locator(interpolate(selector, world.vars))).toBeChecked();
408
+ });
409
+ Then("the element {string} should not be checked", { tags: "@ui or @hybrid" }, async ({ page, world }, selector) => {
410
+ await expect4(page.locator(interpolate(selector, world.vars))).not.toBeChecked();
411
+ });
412
+ }
413
+
414
+ // src/steps/ui.wizard.ts
415
+ import { createBdd as createBdd9 } from "playwright-bdd";
416
+ function resolveValue(input, world) {
417
+ const interpolated = interpolate(input, world.vars);
418
+ if (interpolated === input && world.vars[input] !== void 0) {
419
+ return world.vars[input];
420
+ }
421
+ return interpolated;
422
+ }
423
+ function parseNumber(input, world) {
424
+ const resolved = resolveValue(input, world);
425
+ const n = Number.parseFloat(resolved);
426
+ if (!Number.isFinite(n)) {
427
+ throw new Error(`Expected a number but got '${resolved}'`);
428
+ }
429
+ return n;
430
+ }
431
+ function parseOrdinalIndex(input, world) {
432
+ const resolved = resolveValue(input, world);
433
+ const n = Number.parseInt(resolved, 10);
434
+ if (!Number.isFinite(n) || n < 1) {
435
+ throw new Error(`Expected a 1-based index number but got '${resolved}'`);
436
+ }
437
+ return n - 1;
438
+ }
439
+ function locatorByPage(page, method, text) {
440
+ switch (method) {
441
+ case "text":
442
+ return page.getByText(text);
443
+ case "label":
444
+ return page.getByLabel(text);
445
+ case "placeholder":
446
+ return page.getByPlaceholder(text);
447
+ case "role":
448
+ return page.getByRole(text);
449
+ case "test ID":
450
+ return page.getByTestId(text);
451
+ case "alternative text":
452
+ return page.getByAltText(text);
453
+ case "title":
454
+ return page.getByTitle(text);
455
+ case "locator":
456
+ return page.locator(text);
457
+ default: {
458
+ const neverMethod = method;
459
+ throw new Error(`Unsupported locator method '${neverMethod}'`);
460
+ }
461
+ }
462
+ }
463
+ function asClickMode(input) {
464
+ const v = input.trim().toLowerCase();
465
+ if (v === "click" || v === "dispatch click" || v === "force click" || v === "force dispatch click") {
466
+ return v;
467
+ }
468
+ throw new Error(`Unsupported click mode '${input}'`);
469
+ }
470
+ function asInputMode(input) {
471
+ const v = input.trim().toLowerCase();
472
+ if (v === "type" || v === "fill" || v === "choose") {
473
+ return v;
474
+ }
475
+ throw new Error(`Unsupported input mode '${input}'`);
476
+ }
477
+ function asUrlAssertMode(input) {
478
+ const v = input.trim().toLowerCase();
479
+ if (v === "contains" || v === "doesntcontain" || v === "equals") {
480
+ return v === "doesntcontain" ? "doesntContain" : v;
481
+ }
482
+ throw new Error(`Unsupported URL assert mode '${input}'`);
483
+ }
484
+ function asLocatorMethod(input) {
485
+ const v = input.trim().toLowerCase();
486
+ if (v === "text") return "text";
487
+ if (v === "label") return "label";
488
+ if (v === "placeholder") return "placeholder";
489
+ if (v === "role") return "role";
490
+ if (v === "test id" || v === "testid" || v === "test-id") return "test ID";
491
+ if (v === "alternative text" || v === "alt text" || v === "alt") return "alternative text";
492
+ if (v === "title") return "title";
493
+ if (v === "locator") return "locator";
494
+ throw new Error(`Unsupported locator method '${input}'`);
495
+ }
496
+ function asElementState(input) {
497
+ const v = input.trim().toLowerCase();
498
+ if (v === "visible" || v === "hidden" || v === "editable" || v === "disabled" || v === "enabled") return v;
499
+ if (v === "read-only" || v === "readonly" || v === "read only") return "read-only";
500
+ throw new Error(`Unsupported element state '${input}'`);
501
+ }
502
+ function parseRegex(input) {
503
+ const raw = String(input);
504
+ if (raw.startsWith("/") && raw.lastIndexOf("/") > 0) {
505
+ const last = raw.lastIndexOf("/");
506
+ const pattern = raw.slice(1, last);
507
+ const flags = raw.slice(last + 1);
508
+ return new RegExp(pattern, flags);
509
+ }
510
+ return new RegExp(raw);
511
+ }
512
+ function registerWizardSteps(test) {
513
+ const { Given, When, Then } = createBdd9(test);
514
+ Given("I open {string} page", { tags: "@ui or @hybrid" }, async ({ ui, world }, urlOrVar) => {
515
+ await ui.goto(resolveValue(urlOrVar, world));
516
+ });
517
+ Given("I open {string} in the browser", { tags: "@ui or @hybrid" }, async ({ ui, world }, urlOrVar) => {
518
+ await ui.goto(resolveValue(urlOrVar, world));
519
+ });
520
+ When("I go back in the browser", { tags: "@ui or @hybrid" }, async ({ ui }) => {
521
+ await ui.goBack();
522
+ });
523
+ When("I save the current URL as {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, varName) => {
524
+ world.vars[varName] = await ui.getCurrentUrl();
525
+ });
526
+ When("I save the current url as {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, varName) => {
527
+ world.vars[varName] = await ui.getCurrentUrl();
528
+ });
529
+ Then("I zoom to {string} in the browser", { tags: "@ui or @hybrid" }, async ({ ui, world }, scale) => {
530
+ await ui.zoomTo(parseNumber(scale, world));
531
+ });
532
+ When("I reload the page", { tags: "@ui or @hybrid" }, async ({ ui }) => {
533
+ await ui.reload();
534
+ });
535
+ Then("I wait {string} seconds", { tags: "@ui or @hybrid" }, async ({ ui, world }, seconds) => {
536
+ await ui.waitSeconds(parseNumber(seconds, world));
537
+ });
538
+ Then("I wait for the page to load", { tags: "@ui or @hybrid" }, async ({ ui }) => {
539
+ await ui.waitForPageLoad();
540
+ });
541
+ When(
542
+ "I get a part of the URL based on {string} regular expression and save it as {string}",
543
+ { tags: "@ui or @hybrid" },
544
+ async ({ ui, world }, regexString, varName) => {
545
+ const url = await ui.getCurrentUrl();
546
+ const regex = parseRegex(resolveValue(regexString, world));
547
+ const match = regex.exec(url);
548
+ if (!match) throw new Error(`Regex '${regex}' did not match URL '${url}'`);
549
+ world.vars[varName] = match[1] ?? match[0];
550
+ }
551
+ );
552
+ Then("I {string} {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, action, value) => {
553
+ const verb = action.trim().toLowerCase();
554
+ const resolved = resolveValue(value, world);
555
+ if (verb === "type") {
556
+ await ui.typeText(resolved);
557
+ return;
558
+ }
559
+ if (verb === "press") {
560
+ await ui.pressKey(resolved);
561
+ return;
562
+ }
563
+ throw new Error(`Unsupported keyboard action '${action}'. Expected 'type' or 'press'.`);
564
+ });
565
+ When(
566
+ "I {string} the {string} element that contains {string}",
567
+ { tags: "@ui or @hybrid" },
568
+ async ({ ui, world }, clickMode, elementType, text) => {
569
+ await ui.clickElementThatContains(
570
+ asClickMode(resolveValue(clickMode, world)),
571
+ resolveValue(elementType, world),
572
+ resolveValue(text, world)
573
+ );
574
+ }
575
+ );
576
+ When(
577
+ "I {string} the {string} element with {string} {string}",
578
+ { tags: "@ui or @hybrid" },
579
+ async ({ ui, world }, clickMode, ordinal, text, method) => {
580
+ await ui.clickElementWith(
581
+ asClickMode(resolveValue(clickMode, world)),
582
+ resolveValue(ordinal, world),
583
+ resolveValue(text, world),
584
+ asLocatorMethod(resolveValue(method, world))
585
+ );
586
+ }
587
+ );
588
+ When("I click on the top left corner of the page", { tags: "@ui or @hybrid" }, async ({ page }) => {
589
+ await page.mouse.click(0, 0);
590
+ });
591
+ When(
592
+ "If its visible, I {string} the {string} element with {string} {string}",
593
+ { tags: "@ui or @hybrid" },
594
+ async ({ page, ui, world }, clickMode, ordinal, text, method) => {
595
+ const idx = parseOrdinalIndex(ordinal, world);
596
+ const resolvedText = resolveValue(text, world);
597
+ const resolvedMethod = asLocatorMethod(resolveValue(method, world));
598
+ const locator = locatorByPage(page, resolvedMethod, resolvedText).nth(idx);
599
+ if (await locator.count() === 0) return;
600
+ if (!await locator.isVisible()) return;
601
+ await ui.clickElementWith(asClickMode(resolveValue(clickMode, world)), ordinal, resolvedText, resolvedMethod);
602
+ }
603
+ );
604
+ Then("I fill {string} into the {string} dropdown", { tags: "@ui or @hybrid" }, async ({ ui, world }, value, label) => {
605
+ await ui.fillDropdown(resolveValue(value, world), resolveValue(label, world));
606
+ });
607
+ When(
608
+ "I {string} {string} in the {string} element with {string} {string}",
609
+ { tags: "@ui or @hybrid" },
610
+ async ({ ui, world }, action, value, ordinal, text, method) => {
611
+ await ui.inputInElement(
612
+ asInputMode(resolveValue(action, world)),
613
+ resolveValue(value, world),
614
+ resolveValue(ordinal, world),
615
+ resolveValue(text, world),
616
+ asLocatorMethod(resolveValue(method, world))
617
+ );
618
+ }
619
+ );
620
+ Then(
621
+ "I verify if a new tab which URL {string} {string} opens",
622
+ { tags: "@ui or @hybrid" },
623
+ async ({ ui, world }, mode, expected) => {
624
+ await ui.expectNewTabUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
625
+ }
626
+ );
627
+ Then(
628
+ "I verify if a new tab which url {string} {string} opens",
629
+ { tags: "@ui or @hybrid" },
630
+ async ({ ui, world }, mode, expected) => {
631
+ await ui.expectNewTabUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
632
+ }
633
+ );
634
+ Then("I verify if the URL {string} {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, mode, expected) => {
635
+ await ui.expectUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
636
+ });
637
+ Then("I verify if the url {string} {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, mode, expected) => {
638
+ await ui.expectUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
639
+ });
640
+ Then("I verify if the URL {string} {string} opens", { tags: "@ui or @hybrid" }, async ({ ui, world }, mode, expected) => {
641
+ await ui.expectUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
642
+ });
643
+ Then("I verify if the url {string} {string} opens", { tags: "@ui or @hybrid" }, async ({ ui, world }, mode, expected) => {
644
+ await ui.expectUrl(asUrlAssertMode(resolveValue(mode, world)), resolveValue(expected, world));
645
+ });
646
+ Then(
647
+ "I verify that a {string} element with {string} text {string} visible",
648
+ { tags: "@ui or @hybrid" },
649
+ async ({ ui, world }, elementType, text, assertion) => {
650
+ const normalized = resolveValue(assertion, world).trim().toLowerCase();
651
+ const shouldBeVisible = normalized === "is";
652
+ if (!shouldBeVisible && normalized !== "is not") {
653
+ throw new Error(`Expected 'is' or 'is not' but got '${assertion}'`);
654
+ }
655
+ await ui.expectElementWithTextVisible(resolveValue(elementType, world), resolveValue(text, world), shouldBeVisible);
656
+ }
657
+ );
658
+ Then(
659
+ "I verify that {string} element with {string} {string} is {string}",
660
+ { tags: "@ui or @hybrid" },
661
+ async ({ ui, world }, ordinal, text, method, state) => {
662
+ await ui.expectElementState(
663
+ resolveValue(ordinal, world),
664
+ resolveValue(text, world),
665
+ asLocatorMethod(resolveValue(method, world)),
666
+ asElementState(resolveValue(state, world))
667
+ );
668
+ }
669
+ );
670
+ Then(
671
+ "I verify that {string} element with {string} {string} becomes {string} during {string} seconds",
672
+ { tags: "@ui or @hybrid" },
673
+ async ({ ui, world }, ordinal, text, method, state, seconds) => {
674
+ await ui.expectElementStateWithin(
675
+ resolveValue(ordinal, world),
676
+ resolveValue(text, world),
677
+ asLocatorMethod(resolveValue(method, world)),
678
+ asElementState(resolveValue(state, world)),
679
+ parseNumber(seconds, world)
680
+ );
681
+ }
682
+ );
683
+ }
684
+
685
+ // src/steps/ui.form.ts
686
+ import { createBdd as createBdd10 } from "playwright-bdd";
687
+ function registerFormSteps(test) {
688
+ const { Given, When } = createBdd10(test);
689
+ When(
690
+ "I fill the form:",
691
+ { tags: "@ui or @hybrid" },
692
+ async ({ ui, world }, dataTable) => {
693
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
694
+ field: row[0],
695
+ value: row[1]
696
+ })) || [];
697
+ for (const row of rows) {
698
+ const field = interpolate(row.field, world.vars);
699
+ const value = interpolate(row.value, world.vars);
700
+ await ui.fillLabel(field, value);
701
+ }
702
+ }
703
+ );
704
+ When(
705
+ "I fill the form with {string} locators:",
706
+ { tags: "@ui or @hybrid" },
707
+ async ({ ui, world }, locatorMethod, dataTable) => {
708
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
709
+ field: row[0],
710
+ value: row[1]
711
+ })) || [];
712
+ for (const row of rows) {
713
+ const field = interpolate(row.field, world.vars);
714
+ const value = interpolate(row.value, world.vars);
715
+ await ui.inputInElement("fill", value, "1", field, locatorMethod);
716
+ }
717
+ }
718
+ );
719
+ When(
720
+ "I fill and submit the form:",
721
+ { tags: "@ui or @hybrid" },
722
+ async ({ ui, world }, dataTable) => {
723
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
724
+ field: row[0],
725
+ value: row[1]
726
+ })) || [];
727
+ for (const row of rows) {
728
+ const field = interpolate(row.field, world.vars);
729
+ const value = interpolate(row.value, world.vars);
730
+ await ui.fillLabel(field, value);
731
+ }
732
+ await ui.pressKey("Enter");
733
+ }
734
+ );
735
+ Given(
736
+ "I clear and fill the form:",
737
+ { tags: "@ui or @hybrid" },
738
+ async ({ page, world }, dataTable) => {
739
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
740
+ field: row[0],
741
+ value: row[1]
742
+ })) || [];
743
+ for (const row of rows) {
744
+ const field = interpolate(row.field, world.vars);
745
+ const value = interpolate(row.value, world.vars);
746
+ const locator = page.getByLabel(field);
747
+ await locator.clear();
748
+ await locator.fill(value);
749
+ }
750
+ }
751
+ );
752
+ }
753
+
754
+ // src/steps/ui.debug.ts
755
+ import { createBdd as createBdd11 } from "playwright-bdd";
756
+ function registerDebugSteps(test) {
757
+ const { When, Then } = createBdd11(test);
758
+ When(
759
+ "I capture the page HTML as {string}",
760
+ { tags: "@ui or @hybrid" },
761
+ async ({ page, world }, varName) => {
762
+ const content = await page.content();
763
+ world.vars[varName] = content;
764
+ }
765
+ );
766
+ Then(
767
+ "I log the current URL",
768
+ { tags: "@ui or @hybrid" },
769
+ async ({ page }) => {
770
+ console.log(`[DEBUG] Current URL: ${page.url()}`);
771
+ }
772
+ );
773
+ Then(
774
+ "I log the page title",
775
+ { tags: "@ui or @hybrid" },
776
+ async ({ page }) => {
777
+ const title = await page.title();
778
+ console.log(`[DEBUG] Page title: ${title}`);
779
+ }
780
+ );
781
+ When(
782
+ "I save a screenshot as {string}",
783
+ { tags: "@ui or @hybrid" },
784
+ async ({ page, world }, filename) => {
785
+ const resolvedFilename = interpolate(filename, world.vars);
786
+ await page.screenshot({ path: resolvedFilename });
787
+ console.log(`[DEBUG] Screenshot saved: ${resolvedFilename}`);
788
+ }
789
+ );
790
+ When(
791
+ "I save a full page screenshot as {string}",
792
+ { tags: "@ui or @hybrid" },
793
+ async ({ page, world }, filename) => {
794
+ const resolvedFilename = interpolate(filename, world.vars);
795
+ await page.screenshot({ path: resolvedFilename, fullPage: true });
796
+ console.log(`[DEBUG] Full page screenshot saved: ${resolvedFilename}`);
797
+ }
798
+ );
799
+ When(
800
+ "I pause for debugging",
801
+ { tags: "@ui or @hybrid" },
802
+ async ({ page }) => {
803
+ console.log('[DEBUG] Pausing for debugging. Press "Resume" in Playwright Inspector to continue.');
804
+ await page.pause();
805
+ }
806
+ );
807
+ Then(
808
+ "I print visible text",
809
+ { tags: "@ui or @hybrid" },
810
+ async ({ page }) => {
811
+ const text = await page.innerText("body");
812
+ const truncated = text.length > 2e3 ? `${text.substring(0, 2e3)}...(truncated)` : text;
813
+ console.log(`[DEBUG] Visible text:
814
+ ${truncated}`);
815
+ }
816
+ );
817
+ Then(
818
+ "I count elements matching {string}",
819
+ { tags: "@ui or @hybrid" },
820
+ async ({ page, world }, selector) => {
821
+ const resolvedSelector = interpolate(selector, world.vars);
822
+ const count = await page.locator(resolvedSelector).count();
823
+ console.log(`[DEBUG] Found ${count} elements matching "${resolvedSelector}"`);
824
+ }
825
+ );
826
+ Then(
827
+ "I print browser console messages",
828
+ { tags: "@ui or @hybrid" },
829
+ async ({ page }) => {
830
+ console.log("[DEBUG] Browser console messages will be captured from this point.");
831
+ page.on("console", (msg) => {
832
+ console.log(`[BROWSER ${msg.type().toUpperCase()}] ${msg.text()}`);
833
+ });
834
+ }
835
+ );
836
+ When(
837
+ "I capture viewport size",
838
+ { tags: "@ui or @hybrid" },
839
+ async ({ page, world }) => {
840
+ const viewport = page.viewportSize();
841
+ if (viewport) {
842
+ world.vars.viewportWidth = String(viewport.width);
843
+ world.vars.viewportHeight = String(viewport.height);
844
+ console.log(`[DEBUG] Viewport size: ${viewport.width}x${viewport.height}`);
845
+ } else {
846
+ console.log("[DEBUG] Viewport size not available");
847
+ }
848
+ }
849
+ );
850
+ Then(
851
+ "I log all cookies",
852
+ { tags: "@ui or @hybrid" },
853
+ async ({ page }) => {
854
+ const context = page.context();
855
+ const cookies = await context.cookies();
856
+ console.log(`[DEBUG] Cookies (${cookies.length}):`);
857
+ for (const cookie of cookies) {
858
+ console.log(` - ${cookie.name}: ${cookie.value.substring(0, 50)}${cookie.value.length > 50 ? "..." : ""}`);
859
+ }
860
+ }
861
+ );
862
+ Then(
863
+ "I log localStorage",
864
+ { tags: "@ui or @hybrid" },
865
+ async ({ page }) => {
866
+ const storage = await page.evaluate(() => {
867
+ const items = {};
868
+ for (let i = 0; i < localStorage.length; i++) {
869
+ const key = localStorage.key(i);
870
+ if (key) {
871
+ items[key] = localStorage.getItem(key) || "";
872
+ }
873
+ }
874
+ return items;
875
+ });
876
+ console.log("[DEBUG] localStorage:");
877
+ for (const [key, value] of Object.entries(storage)) {
878
+ const truncated = value.length > 100 ? `${value.substring(0, 100)}...` : value;
879
+ console.log(` - ${key}: ${truncated}`);
880
+ }
881
+ }
882
+ );
883
+ When(
884
+ "I highlight element {string}",
885
+ { tags: "@ui or @hybrid" },
886
+ async ({ page, world }, selector) => {
887
+ const resolvedSelector = interpolate(selector, world.vars);
888
+ await page.evaluate((sel) => {
889
+ const elements = document.querySelectorAll(sel);
890
+ elements.forEach((el) => {
891
+ el.style.outline = "3px solid red";
892
+ el.style.outlineOffset = "2px";
893
+ });
894
+ }, resolvedSelector);
895
+ console.log(`[DEBUG] Highlighted ${await page.locator(resolvedSelector).count()} element(s) matching "${resolvedSelector}"`);
896
+ }
897
+ );
898
+ }
899
+
900
+ // src/steps/ui.layout.ts
901
+ import { expect as expect5 } from "@playwright/test";
902
+ import { createBdd as createBdd12 } from "playwright-bdd";
903
+ function registerLayoutSteps(test) {
904
+ const { Given, When, Then } = createBdd12(test);
905
+ Then(
906
+ "I should see the {string} panel",
907
+ { tags: "@ui or @hybrid" },
908
+ async ({ page, world }, panelName) => {
909
+ const resolvedName = interpolate(panelName, world.vars);
910
+ const panel = page.getByTestId(`${resolvedName}-panel`);
911
+ await expect5(panel).toBeVisible();
912
+ }
913
+ );
914
+ Then(
915
+ "I should not see the {string} panel",
916
+ { tags: "@ui or @hybrid" },
917
+ async ({ page, world }, panelName) => {
918
+ const resolvedName = interpolate(panelName, world.vars);
919
+ const panel = page.getByTestId(`${resolvedName}-panel`);
920
+ await expect5(panel).toBeHidden();
921
+ }
922
+ );
923
+ Then(
924
+ "the {string} panel should be {string}",
925
+ { tags: "@ui or @hybrid" },
926
+ async ({ page, world }, panelName, state) => {
927
+ const resolvedName = interpolate(panelName, world.vars);
928
+ const panel = page.getByTestId(`${resolvedName}-panel`);
929
+ if (state === "visible") {
930
+ await expect5(panel).toBeVisible();
931
+ } else if (state === "hidden") {
932
+ await expect5(panel).toBeHidden();
933
+ } else {
934
+ throw new Error(`Unknown panel state: ${state}. Expected 'visible' or 'hidden'.`);
935
+ }
936
+ }
937
+ );
938
+ Then(
939
+ "the {string} panel should be full width",
940
+ { tags: "@ui or @hybrid" },
941
+ async ({ page, world }, panelName) => {
942
+ const resolvedName = interpolate(panelName, world.vars);
943
+ const panel = page.getByTestId(`${resolvedName}-panel`);
944
+ const isFullWidth = await panel.evaluate((el) => {
945
+ return !el.classList.contains("split") && !el.classList.contains("narrow") && !el.classList.contains("split-view-list");
946
+ });
947
+ expect5(isFullWidth, `Expected "${resolvedName}" panel to be full width`).toBe(true);
948
+ }
949
+ );
950
+ Then(
951
+ "the {string} panel should be narrow",
952
+ { tags: "@ui or @hybrid" },
953
+ async ({ page, world }, panelName) => {
954
+ const resolvedName = interpolate(panelName, world.vars);
955
+ const panel = page.getByTestId(`${resolvedName}-panel`);
956
+ const isNarrow = await panel.evaluate((el) => {
957
+ return el.classList.contains("split") || el.classList.contains("narrow") || el.classList.contains("split-view-list");
958
+ });
959
+ expect5(isNarrow, `Expected "${resolvedName}" panel to be narrow`).toBe(true);
960
+ }
961
+ );
962
+ Then(
963
+ "I should see a split view layout",
964
+ { tags: "@ui or @hybrid" },
965
+ async ({ page }) => {
966
+ const splitContainer = page.locator("[data-testid='split-view'], .split-view, [data-split-view]");
967
+ await expect5(splitContainer.first()).toBeVisible();
968
+ }
969
+ );
970
+ Then(
971
+ "I should not see a split view layout",
972
+ { tags: "@ui or @hybrid" },
973
+ async ({ page }) => {
974
+ const splitContainer = page.locator("[data-testid='split-view'], .split-view, [data-split-view]");
975
+ const count = await splitContainer.count();
976
+ expect5(count === 0 || !await splitContainer.first().isVisible()).toBe(true);
977
+ }
978
+ );
979
+ Then(
980
+ "the sidebar should be visible",
981
+ { tags: "@ui or @hybrid" },
982
+ async ({ page }) => {
983
+ const sidebar = page.locator("[data-testid='sidebar'], aside, nav.sidebar, .sidebar");
984
+ await expect5(sidebar.first()).toBeVisible();
985
+ }
986
+ );
987
+ Then(
988
+ "the sidebar should be hidden",
989
+ { tags: "@ui or @hybrid" },
990
+ async ({ page }) => {
991
+ const sidebar = page.locator("[data-testid='sidebar'], aside.sidebar, nav.sidebar");
992
+ const count = await sidebar.count();
993
+ if (count > 0) {
994
+ await expect5(sidebar.first()).toBeHidden();
995
+ }
996
+ }
997
+ );
998
+ Then(
999
+ "the sidebar should be collapsed",
1000
+ { tags: "@ui or @hybrid" },
1001
+ async ({ page }) => {
1002
+ const sidebar = page.locator("[data-testid='sidebar'], aside, nav.sidebar, .sidebar").first();
1003
+ const isCollapsed = await sidebar.evaluate((el) => {
1004
+ return el.classList.contains("collapsed") || el.classList.contains("minimized") || el.getAttribute("data-collapsed") === "true";
1005
+ });
1006
+ expect5(isCollapsed, "Expected sidebar to be collapsed").toBe(true);
1007
+ }
1008
+ );
1009
+ Then(
1010
+ "I should see a modal dialog",
1011
+ { tags: "@ui or @hybrid" },
1012
+ async ({ page }) => {
1013
+ const modal = page.locator("[role='dialog'], [data-testid='modal'], .modal, [aria-modal='true']");
1014
+ await expect5(modal.first()).toBeVisible();
1015
+ }
1016
+ );
1017
+ Then(
1018
+ "I should not see a modal dialog",
1019
+ { tags: "@ui or @hybrid" },
1020
+ async ({ page }) => {
1021
+ const modal = page.locator("[role='dialog'], [data-testid='modal'], .modal, [aria-modal='true']");
1022
+ const count = await modal.count();
1023
+ if (count > 0) {
1024
+ await expect5(modal.first()).toBeHidden();
1025
+ }
1026
+ }
1027
+ );
1028
+ Then(
1029
+ "I should see the {string} modal",
1030
+ { tags: "@ui or @hybrid" },
1031
+ async ({ page, world }, modalName) => {
1032
+ const resolvedName = interpolate(modalName, world.vars);
1033
+ const modal = page.getByTestId(`${resolvedName}-modal`);
1034
+ await expect5(modal).toBeVisible();
1035
+ }
1036
+ );
1037
+ Given(
1038
+ "the viewport is {string} size",
1039
+ { tags: "@ui or @hybrid" },
1040
+ async ({ page }, sizeName) => {
1041
+ const sizes = {
1042
+ mobile: { width: 375, height: 667 },
1043
+ tablet: { width: 768, height: 1024 },
1044
+ desktop: { width: 1280, height: 800 },
1045
+ "large-desktop": { width: 1920, height: 1080 }
1046
+ };
1047
+ const size = sizes[sizeName.toLowerCase()];
1048
+ if (!size) {
1049
+ throw new Error(`Unknown viewport size: ${sizeName}. Available: ${Object.keys(sizes).join(", ")}`);
1050
+ }
1051
+ await page.setViewportSize(size);
1052
+ }
1053
+ );
1054
+ Given(
1055
+ "the viewport is {int}x{int}",
1056
+ { tags: "@ui or @hybrid" },
1057
+ async ({ page }, width, height) => {
1058
+ await page.setViewportSize({ width, height });
1059
+ }
1060
+ );
1061
+ Then(
1062
+ "the layout should be responsive",
1063
+ { tags: "@ui or @hybrid" },
1064
+ async ({ page }) => {
1065
+ const original = page.viewportSize();
1066
+ await page.setViewportSize({ width: 375, height: 667 });
1067
+ await page.waitForTimeout(100);
1068
+ await page.setViewportSize({ width: 1280, height: 800 });
1069
+ await page.waitForTimeout(100);
1070
+ if (original) {
1071
+ await page.setViewportSize(original);
1072
+ }
1073
+ }
1074
+ );
1075
+ Then(
1076
+ "the {string} tab should be active",
1077
+ { tags: "@ui or @hybrid" },
1078
+ async ({ page, world }, tabName) => {
1079
+ const resolvedName = interpolate(tabName, world.vars);
1080
+ const tab = page.getByRole("tab", { name: resolvedName });
1081
+ await expect5(tab).toHaveAttribute("aria-selected", "true");
1082
+ }
1083
+ );
1084
+ Then(
1085
+ "the {string} tab should not be active",
1086
+ { tags: "@ui or @hybrid" },
1087
+ async ({ page, world }, tabName) => {
1088
+ const resolvedName = interpolate(tabName, world.vars);
1089
+ const tab = page.getByRole("tab", { name: resolvedName });
1090
+ await expect5(tab).toHaveAttribute("aria-selected", "false");
1091
+ }
1092
+ );
1093
+ When(
1094
+ "I click the {string} tab",
1095
+ { tags: "@ui or @hybrid" },
1096
+ async ({ page, world }, tabName) => {
1097
+ const resolvedName = interpolate(tabName, world.vars);
1098
+ const tab = page.getByRole("tab", { name: resolvedName });
1099
+ await tab.click();
1100
+ }
1101
+ );
1102
+ }
1103
+
1104
+ // src/steps/ui.auth.ts
1105
+ import { createBdd as createBdd13 } from "playwright-bdd";
1106
+
1107
+ // src/adapters/ui/fetch-intercept-auth.adapter.ts
1108
+ var defaultBypassConfig = {
1109
+ urlPattern: "/api/",
1110
+ headers: {
1111
+ "x-user-id": (data) => data.userId,
1112
+ "x-tenant-id": (data) => data.tenantId || "",
1113
+ "x-user-roles": (data) => data.roles.join(",")
1114
+ },
1115
+ localStorageKey: "mockAuth"
1116
+ };
1117
+ var defaultBearerConfig = {
1118
+ urlPattern: "/api/",
1119
+ headers: {
1120
+ Authorization: (data) => `Bearer ${data.token || ""}`
1121
+ }
1122
+ };
1123
+ async function setupFetchIntercept(page, authData, config = defaultBypassConfig) {
1124
+ const staticHeaders = {};
1125
+ for (const [key, value] of Object.entries(config.headers)) {
1126
+ staticHeaders[key] = typeof value === "function" ? value(authData) : value;
1127
+ }
1128
+ const headers = {};
1129
+ for (const [key, value] of Object.entries(staticHeaders)) {
1130
+ if (value) {
1131
+ headers[key] = value;
1132
+ }
1133
+ }
1134
+ const urlPattern = config.urlPattern instanceof RegExp ? config.urlPattern.source : config.urlPattern || "";
1135
+ await page.addInitScript(
1136
+ (params) => {
1137
+ if (params.localStorageKey) {
1138
+ localStorage.setItem(params.localStorageKey, JSON.stringify(params.authData));
1139
+ }
1140
+ const originalFetch = window.fetch;
1141
+ window.fetch = function(input, init) {
1142
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1143
+ const shouldIntercept = !params.urlPattern || url.includes(params.urlPattern);
1144
+ if (shouldIntercept) {
1145
+ const modifiedInit = init || {};
1146
+ modifiedInit.headers = {
1147
+ ...modifiedInit.headers || {},
1148
+ ...params.headers
1149
+ };
1150
+ return originalFetch.call(this, input, modifiedInit);
1151
+ }
1152
+ return originalFetch.call(this, input, init);
1153
+ };
1154
+ },
1155
+ { authData, headers, urlPattern, localStorageKey: config.localStorageKey }
1156
+ );
1157
+ }
1158
+ async function clearFetchIntercept(page) {
1159
+ await page.reload();
1160
+ }
1161
+ async function setupBypassAuth(page, userId, roles, tenantId) {
1162
+ await setupFetchIntercept(page, { userId, roles, tenantId }, defaultBypassConfig);
1163
+ }
1164
+ async function setupBearerAuth(page, token) {
1165
+ await setupFetchIntercept(
1166
+ page,
1167
+ { userId: "", roles: [], token },
1168
+ defaultBearerConfig
1169
+ );
1170
+ }
1171
+
1172
+ // src/steps/ui.auth.ts
1173
+ function registerUiAuthSteps(test) {
1174
+ const { Given } = createBdd13(test);
1175
+ Given(
1176
+ "I am authenticated in UI as {string}",
1177
+ { tags: "@ui" },
1178
+ async ({ page, world }, rolesStr) => {
1179
+ const roles = rolesStr.split(",").map((r) => r.trim());
1180
+ const testRunId = world.testRunId || world.vars.test_run_id || Date.now();
1181
+ const userId = `test-user-${testRunId}`;
1182
+ const isAdmin = roles.includes("admin");
1183
+ const tenantId = isAdmin ? void 0 : "org-a";
1184
+ await setupBypassAuth(page, userId, roles, tenantId);
1185
+ world.vars.currentUserId = userId;
1186
+ world.vars.currentRoles = roles.join(",");
1187
+ if (tenantId) {
1188
+ world.vars.currentTenantId = tenantId;
1189
+ }
1190
+ console.log(`[BDD] UI authenticated as ${roles.join(", ")} (user: ${userId})`);
1191
+ }
1192
+ );
1193
+ Given(
1194
+ "I am authenticated in UI as {string} for tenant {string}",
1195
+ { tags: "@ui" },
1196
+ async ({ page, world }, rolesStr, tenantId) => {
1197
+ const roles = rolesStr.split(",").map((r) => r.trim());
1198
+ const testRunId = world.testRunId || world.vars.test_run_id || Date.now();
1199
+ const userId = `test-user-${testRunId}`;
1200
+ const resolvedTenantId = interpolate(tenantId, world.vars);
1201
+ await setupBypassAuth(page, userId, roles, resolvedTenantId);
1202
+ world.vars.currentUserId = userId;
1203
+ world.vars.currentTenantId = resolvedTenantId;
1204
+ world.vars.currentRoles = roles.join(",");
1205
+ console.log(`[BDD] UI authenticated as ${roles.join(", ")} for tenant ${resolvedTenantId}`);
1206
+ }
1207
+ );
1208
+ Given(
1209
+ "I am authenticated in UI as {string} with id {string}",
1210
+ { tags: "@ui" },
1211
+ async ({ page, world }, rolesStr, userId) => {
1212
+ const roles = rolesStr.split(",").map((r) => r.trim());
1213
+ const resolvedUserId = interpolate(userId, world.vars);
1214
+ const tenantId = "org-a";
1215
+ await setupBypassAuth(page, resolvedUserId, roles, tenantId);
1216
+ world.vars.currentUserId = resolvedUserId;
1217
+ world.vars.currentTenantId = tenantId;
1218
+ world.vars.currentRoles = roles.join(",");
1219
+ console.log(`[BDD] UI authenticated as ${roles.join(", ")} with id ${resolvedUserId}`);
1220
+ }
1221
+ );
1222
+ Given(
1223
+ "I am authenticated in UI with bearer token {string}",
1224
+ { tags: "@ui" },
1225
+ async ({ page, world }, token) => {
1226
+ const resolvedToken = interpolate(token, world.vars);
1227
+ await setupBearerAuth(page, resolvedToken);
1228
+ console.log("[BDD] UI authenticated with bearer token");
1229
+ }
1230
+ );
1231
+ Given(
1232
+ "I am authenticated in UI with headers:",
1233
+ { tags: "@ui" },
1234
+ async ({ page, world }, dataTable) => {
1235
+ const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
1236
+ header: row[0],
1237
+ value: row[1]
1238
+ })) || [];
1239
+ const headers = {};
1240
+ for (const row of rows) {
1241
+ headers[row.header] = interpolate(row.value, world.vars);
1242
+ }
1243
+ await setupFetchIntercept(
1244
+ page,
1245
+ { userId: headers["x-user-id"] || "", roles: (headers["x-user-roles"] || "").split(","), tenantId: headers["x-tenant-id"] },
1246
+ {
1247
+ urlPattern: "/api/",
1248
+ headers,
1249
+ localStorageKey: "mockAuth"
1250
+ }
1251
+ );
1252
+ console.log(`[BDD] UI authenticated with custom headers: ${Object.keys(headers).join(", ")}`);
1253
+ }
1254
+ );
1255
+ Given(
1256
+ "I switch UI user to {string} with id {string}",
1257
+ { tags: "@ui" },
1258
+ async ({ page, world }, rolesStr, userId) => {
1259
+ const roles = rolesStr.split(",").map((r) => r.trim());
1260
+ const resolvedUserId = interpolate(userId, world.vars);
1261
+ const tenantId = world.vars.currentTenantId || "org-a";
1262
+ await page.reload();
1263
+ await setupBypassAuth(page, resolvedUserId, roles, tenantId);
1264
+ world.vars.currentUserId = resolvedUserId;
1265
+ world.vars.currentRoles = roles.join(",");
1266
+ console.log(`[BDD] UI switched to user ${resolvedUserId} with roles ${roles.join(", ")}`);
1267
+ }
1268
+ );
1269
+ }
1270
+
1271
+ // src/steps/tui.basic.ts
1272
+ import { createBdd as createBdd14 } from "playwright-bdd";
1273
+ function registerTuiBasicSteps(test) {
1274
+ const { Given, When, Then } = createBdd14(test);
1275
+ Given("I start the TUI application", { tags: "@tui" }, async ({ tui }) => {
1276
+ await tui.start();
1277
+ await tui.waitForReady();
1278
+ });
1279
+ Given("the TUI application is running", { tags: "@tui" }, async ({ tui }) => {
1280
+ if (!tui.isRunning()) {
1281
+ await tui.start();
1282
+ await tui.waitForReady();
1283
+ }
1284
+ });
1285
+ When("I restart the TUI application", { tags: "@tui" }, async ({ tui }) => {
1286
+ await tui.restart();
1287
+ await tui.waitForReady();
1288
+ });
1289
+ When("I stop the TUI application", { tags: "@tui" }, async ({ tui }) => {
1290
+ await tui.stop();
1291
+ });
1292
+ When("I type {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1293
+ await tui.typeText(interpolate(text, world.vars));
1294
+ });
1295
+ When("I type {string} slowly", { tags: "@tui" }, async ({ tui, world }, text) => {
1296
+ await tui.typeText(interpolate(text, world.vars), { delay: 50 });
1297
+ });
1298
+ When("I press {string}", { tags: "@tui" }, async ({ tui }, key) => {
1299
+ await tui.pressKey(key);
1300
+ });
1301
+ When("I press {string} with ctrl", { tags: "@tui" }, async ({ tui }, key) => {
1302
+ await tui.pressKey(key, { ctrl: true });
1303
+ });
1304
+ When("I press {string} with alt", { tags: "@tui" }, async ({ tui }, key) => {
1305
+ await tui.pressKey(key, { alt: true });
1306
+ });
1307
+ When("I press {string} with shift", { tags: "@tui" }, async ({ tui }, key) => {
1308
+ await tui.pressKey(key, { shift: true });
1309
+ });
1310
+ When("I press enter", { tags: "@tui" }, async ({ tui }) => {
1311
+ await tui.pressKey("enter");
1312
+ });
1313
+ When("I press tab", { tags: "@tui" }, async ({ tui }) => {
1314
+ await tui.pressKey("tab");
1315
+ });
1316
+ When("I press escape", { tags: "@tui" }, async ({ tui }) => {
1317
+ await tui.pressKey("escape");
1318
+ });
1319
+ When(
1320
+ "I fill the TUI field {string} with {string}",
1321
+ { tags: "@tui" },
1322
+ async ({ tui, world }, field, value) => {
1323
+ await tui.fillField(interpolate(field, world.vars), interpolate(value, world.vars));
1324
+ }
1325
+ );
1326
+ When(
1327
+ "I enter {string} in the {string} field",
1328
+ { tags: "@tui" },
1329
+ async ({ tui, world }, value, field) => {
1330
+ await tui.fillField(interpolate(field, world.vars), interpolate(value, world.vars));
1331
+ }
1332
+ );
1333
+ When("I select {string}", { tags: "@tui" }, async ({ tui, world }, option) => {
1334
+ await tui.selectOption(interpolate(option, world.vars));
1335
+ });
1336
+ When("I select option {string}", { tags: "@tui" }, async ({ tui, world }, option) => {
1337
+ await tui.selectOption(interpolate(option, world.vars));
1338
+ });
1339
+ When("I click at position {int}, {int}", { tags: "@tui" }, async ({ tui }, x, y) => {
1340
+ await tui.click(x, y);
1341
+ });
1342
+ When("I click on {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1343
+ await tui.clickOnText(interpolate(text, world.vars));
1344
+ });
1345
+ Then("I should see {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1346
+ await tui.expectText(interpolate(text, world.vars));
1347
+ });
1348
+ Then("I should see {string} in the terminal", { tags: "@tui" }, async ({ tui, world }, text) => {
1349
+ await tui.expectText(interpolate(text, world.vars));
1350
+ });
1351
+ Then("I should see text {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1352
+ await tui.expectText(interpolate(text, world.vars));
1353
+ });
1354
+ Then("I should not see {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1355
+ await tui.expectNotText(interpolate(text, world.vars));
1356
+ });
1357
+ Then("the screen should contain {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1358
+ await tui.assertScreenContains(interpolate(text, world.vars));
1359
+ });
1360
+ Then("the screen should match pattern {string}", { tags: "@tui" }, async ({ tui }, pattern) => {
1361
+ await tui.assertScreenMatches(new RegExp(pattern));
1362
+ });
1363
+ When("I wait for {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1364
+ await tui.waitForText(interpolate(text, world.vars));
1365
+ });
1366
+ When(
1367
+ "I wait for {string} for {int} seconds",
1368
+ { tags: "@tui" },
1369
+ async ({ tui, world }, text, seconds) => {
1370
+ await tui.waitForText(interpolate(text, world.vars), { timeout: seconds * 1e3 });
1371
+ }
1372
+ );
1373
+ When("I wait {int} seconds", { tags: "@tui" }, async ({ tui }, seconds) => {
1374
+ await tui.waitSeconds(seconds);
1375
+ });
1376
+ Then("the screen should match snapshot {string}", { tags: "@tui" }, async ({ tui }, name) => {
1377
+ const result = await tui.matchSnapshot(name);
1378
+ if (!result.pass) {
1379
+ throw new Error(`Snapshot "${name}" mismatch:
1380
+ ${result.diff || "No diff available"}`);
1381
+ }
1382
+ });
1383
+ Then("I take a snapshot named {string}", { tags: "@tui" }, async ({ tui }, name) => {
1384
+ await tui.takeSnapshot(name);
1385
+ });
1386
+ When("I clear the terminal", { tags: "@tui" }, async ({ tui }) => {
1387
+ await tui.clear();
1388
+ });
1389
+ When("I resize the terminal to {int}x{int}", { tags: "@tui" }, async ({ tui }, cols, rows) => {
1390
+ await tui.resize({ cols, rows });
1391
+ });
1392
+ }
1393
+
1394
+ // src/steps/tui.wizard.ts
1395
+ import { createBdd as createBdd15 } from "playwright-bdd";
1396
+ function registerTuiWizardSteps(test) {
1397
+ const { Given, When, Then } = createBdd15(test);
1398
+ When("I navigate down {int} times", { tags: "@tui" }, async ({ tui }, times) => {
1399
+ for (let i = 0; i < times; i++) {
1400
+ await tui.pressKey("down");
1401
+ }
1402
+ });
1403
+ When("I navigate up {int} times", { tags: "@tui" }, async ({ tui }, times) => {
1404
+ for (let i = 0; i < times; i++) {
1405
+ await tui.pressKey("up");
1406
+ }
1407
+ });
1408
+ When("I navigate to {string} and select", { tags: "@tui" }, async ({ tui, world }, option) => {
1409
+ const target = interpolate(option, world.vars);
1410
+ await tui.waitForText(target);
1411
+ await tui.pressKey("enter");
1412
+ });
1413
+ When("I go back", { tags: "@tui" }, async ({ tui }) => {
1414
+ await tui.pressKey("escape");
1415
+ });
1416
+ When("I fill the form:", { tags: "@tui" }, async ({ tui, world }, dataTable) => {
1417
+ const rows = dataTable.hashes();
1418
+ for (const row of rows) {
1419
+ const field = interpolate(row.field || row.Field, world.vars);
1420
+ const value = interpolate(row.value || row.Value, world.vars);
1421
+ await tui.fillField(field, value);
1422
+ await tui.pressKey("tab");
1423
+ }
1424
+ });
1425
+ When("I submit the form", { tags: "@tui" }, async ({ tui }) => {
1426
+ await tui.pressKey("enter");
1427
+ });
1428
+ When("I submit the form with ctrl+s", { tags: "@tui" }, async ({ tui }) => {
1429
+ await tui.pressKey("s", { ctrl: true });
1430
+ });
1431
+ When("I select menu item {string}", { tags: "@tui" }, async ({ tui, world }, item) => {
1432
+ const target = interpolate(item, world.vars);
1433
+ await tui.waitForText(target);
1434
+ await tui.selectOption(target);
1435
+ });
1436
+ When("I open the menu", { tags: "@tui" }, async ({ tui }) => {
1437
+ await tui.pressKey("m", { alt: true });
1438
+ });
1439
+ When("I select from dropdown {string} value {string}", { tags: "@tui" }, async ({ tui, world }, dropdown, value) => {
1440
+ const dropdownLabel = interpolate(dropdown, world.vars);
1441
+ const selectedValue = interpolate(value, world.vars);
1442
+ await tui.waitForText(dropdownLabel);
1443
+ await tui.pressKey("enter");
1444
+ await tui.waitForText(selectedValue);
1445
+ await tui.pressKey("enter");
1446
+ });
1447
+ When("I press ctrl+{word}", { tags: "@tui" }, async ({ tui }, key) => {
1448
+ await tui.pressKey(key, { ctrl: true });
1449
+ });
1450
+ When("I press alt+{word}", { tags: "@tui" }, async ({ tui }, key) => {
1451
+ await tui.pressKey(key, { alt: true });
1452
+ });
1453
+ When("I press shift+{word}", { tags: "@tui" }, async ({ tui }, key) => {
1454
+ await tui.pressKey(key, { shift: true });
1455
+ });
1456
+ When("I press ctrl+shift+{word}", { tags: "@tui" }, async ({ tui }, key) => {
1457
+ await tui.pressKey(key, { ctrl: true, shift: true });
1458
+ });
1459
+ When("I quit the application", { tags: "@tui" }, async ({ tui }) => {
1460
+ await tui.pressKey("q");
1461
+ });
1462
+ When("I force quit the application", { tags: "@tui" }, async ({ tui }) => {
1463
+ await tui.pressKey("c", { ctrl: true });
1464
+ });
1465
+ Then("line {int} should contain {string}", { tags: "@tui" }, async ({ tui, world }, lineNum, text) => {
1466
+ const lines = await tui.getScreenLines();
1467
+ const expectedText = interpolate(text, world.vars);
1468
+ const lineIndex = lineNum - 1;
1469
+ if (lineIndex < 0 || lineIndex >= lines.length) {
1470
+ throw new Error(`Line ${lineNum} does not exist. Screen has ${lines.length} lines.`);
1471
+ }
1472
+ if (!lines[lineIndex].includes(expectedText)) {
1473
+ throw new Error(`Expected line ${lineNum} to contain "${expectedText}"
1474
+ Actual: "${lines[lineIndex]}"`);
1475
+ }
1476
+ });
1477
+ Then("the first line should contain {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1478
+ const lines = await tui.getScreenLines();
1479
+ const expectedText = interpolate(text, world.vars);
1480
+ if (!lines[0]?.includes(expectedText)) {
1481
+ throw new Error(`Expected first line to contain "${expectedText}"
1482
+ Actual: "${lines[0]}"`);
1483
+ }
1484
+ });
1485
+ Then("the last line should contain {string}", { tags: "@tui" }, async ({ tui, world }, text) => {
1486
+ const lines = await tui.getScreenLines();
1487
+ const expectedText = interpolate(text, world.vars);
1488
+ const lastLine = lines[lines.length - 1] || "";
1489
+ if (!lastLine.includes(expectedText)) {
1490
+ throw new Error(`Expected last line to contain "${expectedText}"
1491
+ Actual: "${lastLine}"`);
1492
+ }
1493
+ });
1494
+ Then("I should see all of:", { tags: "@tui" }, async ({ tui, world }, dataTable) => {
1495
+ const rows = dataTable.raw();
1496
+ for (const row of rows) {
1497
+ const text = interpolate(row[0], world.vars);
1498
+ await tui.expectText(text);
1499
+ }
1500
+ });
1501
+ Then("I should not see any of:", { tags: "@tui" }, async ({ tui, world }, dataTable) => {
1502
+ const rows = dataTable.raw();
1503
+ for (const row of rows) {
1504
+ const text = interpolate(row[0], world.vars);
1505
+ await tui.expectNotText(text);
1506
+ }
1507
+ });
1508
+ When("I capture the screen", { tags: "@tui" }, async ({ tui, world }) => {
1509
+ const capture = await tui.captureScreen();
1510
+ world.vars["lastScreenCapture"] = capture.text;
1511
+ });
1512
+ Then("I print the screen", { tags: "@tui" }, async ({ tui }) => {
1513
+ const text = await tui.getScreenText();
1514
+ console.log("=== Screen Content ===");
1515
+ console.log(text);
1516
+ console.log("======================");
1517
+ });
1518
+ When("I execute command {string}", { tags: "@tui" }, async ({ tui, world }, command) => {
1519
+ const cmd = interpolate(command, world.vars);
1520
+ await tui.typeText(cmd);
1521
+ await tui.pressKey("enter");
1522
+ });
1523
+ When("I run {string}", { tags: "@tui" }, async ({ tui, world }, command) => {
1524
+ const cmd = interpolate(command, world.vars);
1525
+ await tui.typeText(cmd);
1526
+ await tui.pressKey("enter");
1527
+ });
1528
+ When("I confirm the dialog", { tags: "@tui" }, async ({ tui }) => {
1529
+ await tui.typeText("y");
1530
+ await tui.pressKey("enter");
1531
+ });
1532
+ When("I cancel the dialog", { tags: "@tui" }, async ({ tui }) => {
1533
+ await tui.typeText("n");
1534
+ await tui.pressKey("enter");
1535
+ });
1536
+ When("I dismiss the dialog", { tags: "@tui" }, async ({ tui }) => {
1537
+ await tui.pressKey("escape");
1538
+ });
1539
+ }
1540
+
1541
+ // src/steps/index.ts
1542
+ function registerApiSteps(test) {
1543
+ registerApiAuthSteps(test);
1544
+ registerApiHttpSteps(test);
1545
+ registerApiAssertionSteps(test);
1546
+ }
1547
+ function registerUiSteps(test) {
1548
+ registerUiBasicSteps(test);
1549
+ registerWizardSteps(test);
1550
+ registerFormSteps(test);
1551
+ registerDebugSteps(test);
1552
+ registerLayoutSteps(test);
1553
+ registerUiAuthSteps(test);
1554
+ }
1555
+ function registerSharedSteps(test) {
1556
+ registerSharedVarSteps(test);
1557
+ registerSharedCleanupSteps(test);
1558
+ registerFlagSteps(test);
1559
+ }
1560
+ function registerHybridSuite(test) {
1561
+ registerHybridSteps(test);
1562
+ }
1563
+ function registerTuiSteps(test) {
1564
+ registerTuiBasicSteps(test);
1565
+ registerTuiWizardSteps(test);
1566
+ }
1567
+ function registerAllSteps(test) {
1568
+ registerApiSteps(test);
1569
+ registerUiSteps(test);
1570
+ registerTuiSteps(test);
1571
+ registerSharedSteps(test);
1572
+ registerHybridSuite(test);
1573
+ }
1574
+
1575
+ export {
1576
+ interpolate,
1577
+ tryParseJson,
1578
+ selectPath,
1579
+ parseExpected,
1580
+ assertMasked,
1581
+ registerCleanup,
1582
+ defaultBypassConfig,
1583
+ defaultBearerConfig,
1584
+ setupFetchIntercept,
1585
+ clearFetchIntercept,
1586
+ setupBypassAuth,
1587
+ setupBearerAuth,
1588
+ registerApiHttpSteps,
1589
+ registerApiAssertionSteps,
1590
+ registerApiAuthSteps,
1591
+ registerHybridSteps,
1592
+ registerSharedCleanupSteps,
1593
+ registerSharedVarSteps,
1594
+ isFlagEnabled,
1595
+ setFlag,
1596
+ registerFlagSteps,
1597
+ registerUiBasicSteps,
1598
+ registerWizardSteps,
1599
+ registerFormSteps,
1600
+ registerDebugSteps,
1601
+ registerLayoutSteps,
1602
+ registerUiAuthSteps,
1603
+ registerTuiBasicSteps,
1604
+ registerTuiWizardSteps,
1605
+ registerApiSteps,
1606
+ registerUiSteps,
1607
+ registerSharedSteps,
1608
+ registerHybridSuite,
1609
+ registerTuiSteps,
1610
+ registerAllSteps
1611
+ };