@esimplicity/stack-tests 0.1.3 → 0.1.6
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/README.md +5 -5
- package/dist/{chunk-73J5FNGG.js → chunk-VWBEMXYC.js} +717 -9
- package/dist/index.d.ts +60 -2
- package/dist/index.js +29 -1
- package/dist/steps/index.d.ts +95 -2
- package/dist/steps/index.js +19 -3
- package/package.json +1 -1
- package/scripts/postinstall.cjs +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @esimplicity/stack-tests
|
|
2
2
|
|
|
3
3
|
Reusable Playwright-BDD fixtures, ports, adapters, and step registrations for API, UI, and hybrid testing. Designed to be consumed as a dev dependency across repos.
|
|
4
4
|
|
|
@@ -9,13 +9,13 @@ GitHub Packages (recommended for release builds):
|
|
|
9
9
|
```bash
|
|
10
10
|
npm config set @kata:registry https://npm.pkg.github.com
|
|
11
11
|
npm set //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
12
|
-
npm install -D @
|
|
12
|
+
npm install -D @esimplicity/stack-tests @playwright/test playwright-bdd
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Workspace/local development (from this monorepo):
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
bun add -d @
|
|
18
|
+
bun add -d @esimplicity/stack-tests@"file:../packages/stack-tests" @playwright/test playwright-bdd
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## What’s included
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
PlaywrightUiAdapter,
|
|
38
38
|
UniversalAuthAdapter,
|
|
39
39
|
DefaultCleanupAdapter,
|
|
40
|
-
} from '@
|
|
40
|
+
} from '@esimplicity/stack-tests';
|
|
41
41
|
|
|
42
42
|
export const test = createBddTest({
|
|
43
43
|
createApi: ({ apiRequest }) => new PlaywrightApiAdapter(apiRequest),
|
|
@@ -51,7 +51,7 @@ export const test = createBddTest({
|
|
|
51
51
|
```ts
|
|
52
52
|
// features/steps/steps_api/index.ts
|
|
53
53
|
import { test } from '../fixtures';
|
|
54
|
-
import { registerApiSteps } from '@
|
|
54
|
+
import { registerApiSteps } from '@esimplicity/stack-tests/steps';
|
|
55
55
|
registerApiSteps(test);
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -206,10 +206,106 @@ function registerSharedVarSteps(test) {
|
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// src/steps/
|
|
209
|
+
// src/steps/shared.flags.ts
|
|
210
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";
|
|
211
307
|
function registerUiBasicSteps(test) {
|
|
212
|
-
const { Given, When, Then } =
|
|
308
|
+
const { Given, When, Then } = createBdd8(test);
|
|
213
309
|
Given("I navigate to {string}", { tags: "@ui or @hybrid" }, async ({ ui, world }, path) => {
|
|
214
310
|
await ui.goto(interpolate(path, world.vars));
|
|
215
311
|
});
|
|
@@ -240,7 +336,7 @@ function registerUiBasicSteps(test) {
|
|
|
240
336
|
}
|
|
241
337
|
|
|
242
338
|
// src/steps/ui.wizard.ts
|
|
243
|
-
import { createBdd as
|
|
339
|
+
import { createBdd as createBdd9 } from "playwright-bdd";
|
|
244
340
|
function resolveValue(input, world) {
|
|
245
341
|
const interpolated = interpolate(input, world.vars);
|
|
246
342
|
if (interpolated === input && world.vars[input] !== void 0) {
|
|
@@ -338,7 +434,7 @@ function parseRegex(input) {
|
|
|
338
434
|
return new RegExp(raw);
|
|
339
435
|
}
|
|
340
436
|
function registerWizardSteps(test) {
|
|
341
|
-
const { Given, When, Then } =
|
|
437
|
+
const { Given, When, Then } = createBdd9(test);
|
|
342
438
|
Given("I open {string} page", { tags: "@ui or @hybrid" }, async ({ ui, world }, urlOrVar) => {
|
|
343
439
|
await ui.goto(resolveValue(urlOrVar, world));
|
|
344
440
|
});
|
|
@@ -510,10 +606,596 @@ function registerWizardSteps(test) {
|
|
|
510
606
|
);
|
|
511
607
|
}
|
|
512
608
|
|
|
609
|
+
// src/steps/ui.form.ts
|
|
610
|
+
import { createBdd as createBdd10 } from "playwright-bdd";
|
|
611
|
+
function registerFormSteps(test) {
|
|
612
|
+
const { Given, When } = createBdd10(test);
|
|
613
|
+
When(
|
|
614
|
+
"I fill the form:",
|
|
615
|
+
{ tags: "@ui or @hybrid" },
|
|
616
|
+
async ({ ui, world }, dataTable) => {
|
|
617
|
+
const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
|
|
618
|
+
field: row[0],
|
|
619
|
+
value: row[1]
|
|
620
|
+
})) || [];
|
|
621
|
+
for (const row of rows) {
|
|
622
|
+
const field = interpolate(row.field, world.vars);
|
|
623
|
+
const value = interpolate(row.value, world.vars);
|
|
624
|
+
await ui.fillLabel(field, value);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
);
|
|
628
|
+
When(
|
|
629
|
+
"I fill the form with {string} locators:",
|
|
630
|
+
{ tags: "@ui or @hybrid" },
|
|
631
|
+
async ({ ui, world }, locatorMethod, dataTable) => {
|
|
632
|
+
const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
|
|
633
|
+
field: row[0],
|
|
634
|
+
value: row[1]
|
|
635
|
+
})) || [];
|
|
636
|
+
for (const row of rows) {
|
|
637
|
+
const field = interpolate(row.field, world.vars);
|
|
638
|
+
const value = interpolate(row.value, world.vars);
|
|
639
|
+
await ui.inputInElement("fill", value, "1", field, locatorMethod);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
When(
|
|
644
|
+
"I fill and submit the form:",
|
|
645
|
+
{ tags: "@ui or @hybrid" },
|
|
646
|
+
async ({ ui, world }, dataTable) => {
|
|
647
|
+
const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
|
|
648
|
+
field: row[0],
|
|
649
|
+
value: row[1]
|
|
650
|
+
})) || [];
|
|
651
|
+
for (const row of rows) {
|
|
652
|
+
const field = interpolate(row.field, world.vars);
|
|
653
|
+
const value = interpolate(row.value, world.vars);
|
|
654
|
+
await ui.fillLabel(field, value);
|
|
655
|
+
}
|
|
656
|
+
await ui.pressKey("Enter");
|
|
657
|
+
}
|
|
658
|
+
);
|
|
659
|
+
Given(
|
|
660
|
+
"I clear and fill the form:",
|
|
661
|
+
{ tags: "@ui or @hybrid" },
|
|
662
|
+
async ({ page, world }, dataTable) => {
|
|
663
|
+
const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
|
|
664
|
+
field: row[0],
|
|
665
|
+
value: row[1]
|
|
666
|
+
})) || [];
|
|
667
|
+
for (const row of rows) {
|
|
668
|
+
const field = interpolate(row.field, world.vars);
|
|
669
|
+
const value = interpolate(row.value, world.vars);
|
|
670
|
+
const locator = page.getByLabel(field);
|
|
671
|
+
await locator.clear();
|
|
672
|
+
await locator.fill(value);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/steps/ui.debug.ts
|
|
679
|
+
import { createBdd as createBdd11 } from "playwright-bdd";
|
|
680
|
+
function registerDebugSteps(test) {
|
|
681
|
+
const { When, Then } = createBdd11(test);
|
|
682
|
+
When(
|
|
683
|
+
"I capture the page HTML as {string}",
|
|
684
|
+
{ tags: "@ui or @hybrid" },
|
|
685
|
+
async ({ page, world }, varName) => {
|
|
686
|
+
const content = await page.content();
|
|
687
|
+
world.vars[varName] = content;
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
Then(
|
|
691
|
+
"I log the current URL",
|
|
692
|
+
{ tags: "@ui or @hybrid" },
|
|
693
|
+
async ({ page }) => {
|
|
694
|
+
console.log(`[DEBUG] Current URL: ${page.url()}`);
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
Then(
|
|
698
|
+
"I log the page title",
|
|
699
|
+
{ tags: "@ui or @hybrid" },
|
|
700
|
+
async ({ page }) => {
|
|
701
|
+
const title = await page.title();
|
|
702
|
+
console.log(`[DEBUG] Page title: ${title}`);
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
When(
|
|
706
|
+
"I save a screenshot as {string}",
|
|
707
|
+
{ tags: "@ui or @hybrid" },
|
|
708
|
+
async ({ page, world }, filename) => {
|
|
709
|
+
const resolvedFilename = interpolate(filename, world.vars);
|
|
710
|
+
await page.screenshot({ path: resolvedFilename });
|
|
711
|
+
console.log(`[DEBUG] Screenshot saved: ${resolvedFilename}`);
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
When(
|
|
715
|
+
"I save a full page screenshot as {string}",
|
|
716
|
+
{ tags: "@ui or @hybrid" },
|
|
717
|
+
async ({ page, world }, filename) => {
|
|
718
|
+
const resolvedFilename = interpolate(filename, world.vars);
|
|
719
|
+
await page.screenshot({ path: resolvedFilename, fullPage: true });
|
|
720
|
+
console.log(`[DEBUG] Full page screenshot saved: ${resolvedFilename}`);
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
When(
|
|
724
|
+
"I pause for debugging",
|
|
725
|
+
{ tags: "@ui or @hybrid" },
|
|
726
|
+
async ({ page }) => {
|
|
727
|
+
console.log('[DEBUG] Pausing for debugging. Press "Resume" in Playwright Inspector to continue.');
|
|
728
|
+
await page.pause();
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
Then(
|
|
732
|
+
"I print visible text",
|
|
733
|
+
{ tags: "@ui or @hybrid" },
|
|
734
|
+
async ({ page }) => {
|
|
735
|
+
const text = await page.innerText("body");
|
|
736
|
+
const truncated = text.length > 2e3 ? `${text.substring(0, 2e3)}...(truncated)` : text;
|
|
737
|
+
console.log(`[DEBUG] Visible text:
|
|
738
|
+
${truncated}`);
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
Then(
|
|
742
|
+
"I count elements matching {string}",
|
|
743
|
+
{ tags: "@ui or @hybrid" },
|
|
744
|
+
async ({ page, world }, selector) => {
|
|
745
|
+
const resolvedSelector = interpolate(selector, world.vars);
|
|
746
|
+
const count = await page.locator(resolvedSelector).count();
|
|
747
|
+
console.log(`[DEBUG] Found ${count} elements matching "${resolvedSelector}"`);
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
Then(
|
|
751
|
+
"I print browser console messages",
|
|
752
|
+
{ tags: "@ui or @hybrid" },
|
|
753
|
+
async ({ page }) => {
|
|
754
|
+
console.log("[DEBUG] Browser console messages will be captured from this point.");
|
|
755
|
+
page.on("console", (msg) => {
|
|
756
|
+
console.log(`[BROWSER ${msg.type().toUpperCase()}] ${msg.text()}`);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
);
|
|
760
|
+
When(
|
|
761
|
+
"I capture viewport size",
|
|
762
|
+
{ tags: "@ui or @hybrid" },
|
|
763
|
+
async ({ page, world }) => {
|
|
764
|
+
const viewport = page.viewportSize();
|
|
765
|
+
if (viewport) {
|
|
766
|
+
world.vars.viewportWidth = String(viewport.width);
|
|
767
|
+
world.vars.viewportHeight = String(viewport.height);
|
|
768
|
+
console.log(`[DEBUG] Viewport size: ${viewport.width}x${viewport.height}`);
|
|
769
|
+
} else {
|
|
770
|
+
console.log("[DEBUG] Viewport size not available");
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
Then(
|
|
775
|
+
"I log all cookies",
|
|
776
|
+
{ tags: "@ui or @hybrid" },
|
|
777
|
+
async ({ page }) => {
|
|
778
|
+
const context = page.context();
|
|
779
|
+
const cookies = await context.cookies();
|
|
780
|
+
console.log(`[DEBUG] Cookies (${cookies.length}):`);
|
|
781
|
+
for (const cookie of cookies) {
|
|
782
|
+
console.log(` - ${cookie.name}: ${cookie.value.substring(0, 50)}${cookie.value.length > 50 ? "..." : ""}`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
Then(
|
|
787
|
+
"I log localStorage",
|
|
788
|
+
{ tags: "@ui or @hybrid" },
|
|
789
|
+
async ({ page }) => {
|
|
790
|
+
const storage = await page.evaluate(() => {
|
|
791
|
+
const items = {};
|
|
792
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
793
|
+
const key = localStorage.key(i);
|
|
794
|
+
if (key) {
|
|
795
|
+
items[key] = localStorage.getItem(key) || "";
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return items;
|
|
799
|
+
});
|
|
800
|
+
console.log("[DEBUG] localStorage:");
|
|
801
|
+
for (const [key, value] of Object.entries(storage)) {
|
|
802
|
+
const truncated = value.length > 100 ? `${value.substring(0, 100)}...` : value;
|
|
803
|
+
console.log(` - ${key}: ${truncated}`);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
);
|
|
807
|
+
When(
|
|
808
|
+
"I highlight element {string}",
|
|
809
|
+
{ tags: "@ui or @hybrid" },
|
|
810
|
+
async ({ page, world }, selector) => {
|
|
811
|
+
const resolvedSelector = interpolate(selector, world.vars);
|
|
812
|
+
await page.evaluate((sel) => {
|
|
813
|
+
const elements = document.querySelectorAll(sel);
|
|
814
|
+
elements.forEach((el) => {
|
|
815
|
+
el.style.outline = "3px solid red";
|
|
816
|
+
el.style.outlineOffset = "2px";
|
|
817
|
+
});
|
|
818
|
+
}, resolvedSelector);
|
|
819
|
+
console.log(`[DEBUG] Highlighted ${await page.locator(resolvedSelector).count()} element(s) matching "${resolvedSelector}"`);
|
|
820
|
+
}
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/steps/ui.layout.ts
|
|
825
|
+
import { expect as expect3 } from "@playwright/test";
|
|
826
|
+
import { createBdd as createBdd12 } from "playwright-bdd";
|
|
827
|
+
function registerLayoutSteps(test) {
|
|
828
|
+
const { Given, When, Then } = createBdd12(test);
|
|
829
|
+
Then(
|
|
830
|
+
"I should see the {string} panel",
|
|
831
|
+
{ tags: "@ui or @hybrid" },
|
|
832
|
+
async ({ page, world }, panelName) => {
|
|
833
|
+
const resolvedName = interpolate(panelName, world.vars);
|
|
834
|
+
const panel = page.getByTestId(`${resolvedName}-panel`);
|
|
835
|
+
await expect3(panel).toBeVisible();
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
Then(
|
|
839
|
+
"I should not see the {string} panel",
|
|
840
|
+
{ tags: "@ui or @hybrid" },
|
|
841
|
+
async ({ page, world }, panelName) => {
|
|
842
|
+
const resolvedName = interpolate(panelName, world.vars);
|
|
843
|
+
const panel = page.getByTestId(`${resolvedName}-panel`);
|
|
844
|
+
await expect3(panel).toBeHidden();
|
|
845
|
+
}
|
|
846
|
+
);
|
|
847
|
+
Then(
|
|
848
|
+
"the {string} panel should be {string}",
|
|
849
|
+
{ tags: "@ui or @hybrid" },
|
|
850
|
+
async ({ page, world }, panelName, state) => {
|
|
851
|
+
const resolvedName = interpolate(panelName, world.vars);
|
|
852
|
+
const panel = page.getByTestId(`${resolvedName}-panel`);
|
|
853
|
+
if (state === "visible") {
|
|
854
|
+
await expect3(panel).toBeVisible();
|
|
855
|
+
} else if (state === "hidden") {
|
|
856
|
+
await expect3(panel).toBeHidden();
|
|
857
|
+
} else {
|
|
858
|
+
throw new Error(`Unknown panel state: ${state}. Expected 'visible' or 'hidden'.`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
);
|
|
862
|
+
Then(
|
|
863
|
+
"the {string} panel should be full width",
|
|
864
|
+
{ tags: "@ui or @hybrid" },
|
|
865
|
+
async ({ page, world }, panelName) => {
|
|
866
|
+
const resolvedName = interpolate(panelName, world.vars);
|
|
867
|
+
const panel = page.getByTestId(`${resolvedName}-panel`);
|
|
868
|
+
const isFullWidth = await panel.evaluate((el) => {
|
|
869
|
+
return !el.classList.contains("split") && !el.classList.contains("narrow") && !el.classList.contains("split-view-list");
|
|
870
|
+
});
|
|
871
|
+
expect3(isFullWidth, `Expected "${resolvedName}" panel to be full width`).toBe(true);
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
Then(
|
|
875
|
+
"the {string} panel should be narrow",
|
|
876
|
+
{ tags: "@ui or @hybrid" },
|
|
877
|
+
async ({ page, world }, panelName) => {
|
|
878
|
+
const resolvedName = interpolate(panelName, world.vars);
|
|
879
|
+
const panel = page.getByTestId(`${resolvedName}-panel`);
|
|
880
|
+
const isNarrow = await panel.evaluate((el) => {
|
|
881
|
+
return el.classList.contains("split") || el.classList.contains("narrow") || el.classList.contains("split-view-list");
|
|
882
|
+
});
|
|
883
|
+
expect3(isNarrow, `Expected "${resolvedName}" panel to be narrow`).toBe(true);
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
Then(
|
|
887
|
+
"I should see a split view layout",
|
|
888
|
+
{ tags: "@ui or @hybrid" },
|
|
889
|
+
async ({ page }) => {
|
|
890
|
+
const splitContainer = page.locator("[data-testid='split-view'], .split-view, [data-split-view]");
|
|
891
|
+
await expect3(splitContainer.first()).toBeVisible();
|
|
892
|
+
}
|
|
893
|
+
);
|
|
894
|
+
Then(
|
|
895
|
+
"I should not see a split view layout",
|
|
896
|
+
{ tags: "@ui or @hybrid" },
|
|
897
|
+
async ({ page }) => {
|
|
898
|
+
const splitContainer = page.locator("[data-testid='split-view'], .split-view, [data-split-view]");
|
|
899
|
+
const count = await splitContainer.count();
|
|
900
|
+
expect3(count === 0 || !await splitContainer.first().isVisible()).toBe(true);
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
Then(
|
|
904
|
+
"the sidebar should be visible",
|
|
905
|
+
{ tags: "@ui or @hybrid" },
|
|
906
|
+
async ({ page }) => {
|
|
907
|
+
const sidebar = page.locator("[data-testid='sidebar'], aside, nav.sidebar, .sidebar");
|
|
908
|
+
await expect3(sidebar.first()).toBeVisible();
|
|
909
|
+
}
|
|
910
|
+
);
|
|
911
|
+
Then(
|
|
912
|
+
"the sidebar should be hidden",
|
|
913
|
+
{ tags: "@ui or @hybrid" },
|
|
914
|
+
async ({ page }) => {
|
|
915
|
+
const sidebar = page.locator("[data-testid='sidebar'], aside.sidebar, nav.sidebar");
|
|
916
|
+
const count = await sidebar.count();
|
|
917
|
+
if (count > 0) {
|
|
918
|
+
await expect3(sidebar.first()).toBeHidden();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
Then(
|
|
923
|
+
"the sidebar should be collapsed",
|
|
924
|
+
{ tags: "@ui or @hybrid" },
|
|
925
|
+
async ({ page }) => {
|
|
926
|
+
const sidebar = page.locator("[data-testid='sidebar'], aside, nav.sidebar, .sidebar").first();
|
|
927
|
+
const isCollapsed = await sidebar.evaluate((el) => {
|
|
928
|
+
return el.classList.contains("collapsed") || el.classList.contains("minimized") || el.getAttribute("data-collapsed") === "true";
|
|
929
|
+
});
|
|
930
|
+
expect3(isCollapsed, "Expected sidebar to be collapsed").toBe(true);
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
Then(
|
|
934
|
+
"I should see a modal dialog",
|
|
935
|
+
{ tags: "@ui or @hybrid" },
|
|
936
|
+
async ({ page }) => {
|
|
937
|
+
const modal = page.locator("[role='dialog'], [data-testid='modal'], .modal, [aria-modal='true']");
|
|
938
|
+
await expect3(modal.first()).toBeVisible();
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
Then(
|
|
942
|
+
"I should not see a modal dialog",
|
|
943
|
+
{ tags: "@ui or @hybrid" },
|
|
944
|
+
async ({ page }) => {
|
|
945
|
+
const modal = page.locator("[role='dialog'], [data-testid='modal'], .modal, [aria-modal='true']");
|
|
946
|
+
const count = await modal.count();
|
|
947
|
+
if (count > 0) {
|
|
948
|
+
await expect3(modal.first()).toBeHidden();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
);
|
|
952
|
+
Then(
|
|
953
|
+
"I should see the {string} modal",
|
|
954
|
+
{ tags: "@ui or @hybrid" },
|
|
955
|
+
async ({ page, world }, modalName) => {
|
|
956
|
+
const resolvedName = interpolate(modalName, world.vars);
|
|
957
|
+
const modal = page.getByTestId(`${resolvedName}-modal`);
|
|
958
|
+
await expect3(modal).toBeVisible();
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
Given(
|
|
962
|
+
"the viewport is {string} size",
|
|
963
|
+
{ tags: "@ui or @hybrid" },
|
|
964
|
+
async ({ page }, sizeName) => {
|
|
965
|
+
const sizes = {
|
|
966
|
+
mobile: { width: 375, height: 667 },
|
|
967
|
+
tablet: { width: 768, height: 1024 },
|
|
968
|
+
desktop: { width: 1280, height: 800 },
|
|
969
|
+
"large-desktop": { width: 1920, height: 1080 }
|
|
970
|
+
};
|
|
971
|
+
const size = sizes[sizeName.toLowerCase()];
|
|
972
|
+
if (!size) {
|
|
973
|
+
throw new Error(`Unknown viewport size: ${sizeName}. Available: ${Object.keys(sizes).join(", ")}`);
|
|
974
|
+
}
|
|
975
|
+
await page.setViewportSize(size);
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
Given(
|
|
979
|
+
"the viewport is {int}x{int}",
|
|
980
|
+
{ tags: "@ui or @hybrid" },
|
|
981
|
+
async ({ page }, width, height) => {
|
|
982
|
+
await page.setViewportSize({ width, height });
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
Then(
|
|
986
|
+
"the layout should be responsive",
|
|
987
|
+
{ tags: "@ui or @hybrid" },
|
|
988
|
+
async ({ page }) => {
|
|
989
|
+
const original = page.viewportSize();
|
|
990
|
+
await page.setViewportSize({ width: 375, height: 667 });
|
|
991
|
+
await page.waitForTimeout(100);
|
|
992
|
+
await page.setViewportSize({ width: 1280, height: 800 });
|
|
993
|
+
await page.waitForTimeout(100);
|
|
994
|
+
if (original) {
|
|
995
|
+
await page.setViewportSize(original);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
);
|
|
999
|
+
Then(
|
|
1000
|
+
"the {string} tab should be active",
|
|
1001
|
+
{ tags: "@ui or @hybrid" },
|
|
1002
|
+
async ({ page, world }, tabName) => {
|
|
1003
|
+
const resolvedName = interpolate(tabName, world.vars);
|
|
1004
|
+
const tab = page.getByRole("tab", { name: resolvedName });
|
|
1005
|
+
await expect3(tab).toHaveAttribute("aria-selected", "true");
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
Then(
|
|
1009
|
+
"the {string} tab should not be active",
|
|
1010
|
+
{ tags: "@ui or @hybrid" },
|
|
1011
|
+
async ({ page, world }, tabName) => {
|
|
1012
|
+
const resolvedName = interpolate(tabName, world.vars);
|
|
1013
|
+
const tab = page.getByRole("tab", { name: resolvedName });
|
|
1014
|
+
await expect3(tab).toHaveAttribute("aria-selected", "false");
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
When(
|
|
1018
|
+
"I click the {string} tab",
|
|
1019
|
+
{ tags: "@ui or @hybrid" },
|
|
1020
|
+
async ({ page, world }, tabName) => {
|
|
1021
|
+
const resolvedName = interpolate(tabName, world.vars);
|
|
1022
|
+
const tab = page.getByRole("tab", { name: resolvedName });
|
|
1023
|
+
await tab.click();
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/steps/ui.auth.ts
|
|
1029
|
+
import { createBdd as createBdd13 } from "playwright-bdd";
|
|
1030
|
+
|
|
1031
|
+
// src/adapters/ui/fetch-intercept-auth.adapter.ts
|
|
1032
|
+
var defaultBypassConfig = {
|
|
1033
|
+
urlPattern: "/api/",
|
|
1034
|
+
headers: {
|
|
1035
|
+
"x-user-id": (data) => data.userId,
|
|
1036
|
+
"x-tenant-id": (data) => data.tenantId || "",
|
|
1037
|
+
"x-user-roles": (data) => data.roles.join(",")
|
|
1038
|
+
},
|
|
1039
|
+
localStorageKey: "mockAuth"
|
|
1040
|
+
};
|
|
1041
|
+
var defaultBearerConfig = {
|
|
1042
|
+
urlPattern: "/api/",
|
|
1043
|
+
headers: {
|
|
1044
|
+
Authorization: (data) => `Bearer ${data.token || ""}`
|
|
1045
|
+
}
|
|
1046
|
+
};
|
|
1047
|
+
async function setupFetchIntercept(page, authData, config = defaultBypassConfig) {
|
|
1048
|
+
const staticHeaders = {};
|
|
1049
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
1050
|
+
staticHeaders[key] = typeof value === "function" ? value(authData) : value;
|
|
1051
|
+
}
|
|
1052
|
+
const headers = {};
|
|
1053
|
+
for (const [key, value] of Object.entries(staticHeaders)) {
|
|
1054
|
+
if (value) {
|
|
1055
|
+
headers[key] = value;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const urlPattern = config.urlPattern instanceof RegExp ? config.urlPattern.source : config.urlPattern || "";
|
|
1059
|
+
await page.addInitScript(
|
|
1060
|
+
(params) => {
|
|
1061
|
+
if (params.localStorageKey) {
|
|
1062
|
+
localStorage.setItem(params.localStorageKey, JSON.stringify(params.authData));
|
|
1063
|
+
}
|
|
1064
|
+
const originalFetch = window.fetch;
|
|
1065
|
+
window.fetch = function(input, init) {
|
|
1066
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
1067
|
+
const shouldIntercept = !params.urlPattern || url.includes(params.urlPattern);
|
|
1068
|
+
if (shouldIntercept) {
|
|
1069
|
+
const modifiedInit = init || {};
|
|
1070
|
+
modifiedInit.headers = {
|
|
1071
|
+
...modifiedInit.headers || {},
|
|
1072
|
+
...params.headers
|
|
1073
|
+
};
|
|
1074
|
+
return originalFetch.call(this, input, modifiedInit);
|
|
1075
|
+
}
|
|
1076
|
+
return originalFetch.call(this, input, init);
|
|
1077
|
+
};
|
|
1078
|
+
},
|
|
1079
|
+
{ authData, headers, urlPattern, localStorageKey: config.localStorageKey }
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
async function clearFetchIntercept(page) {
|
|
1083
|
+
await page.reload();
|
|
1084
|
+
}
|
|
1085
|
+
async function setupBypassAuth(page, userId, roles, tenantId) {
|
|
1086
|
+
await setupFetchIntercept(page, { userId, roles, tenantId }, defaultBypassConfig);
|
|
1087
|
+
}
|
|
1088
|
+
async function setupBearerAuth(page, token) {
|
|
1089
|
+
await setupFetchIntercept(
|
|
1090
|
+
page,
|
|
1091
|
+
{ userId: "", roles: [], token },
|
|
1092
|
+
defaultBearerConfig
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/steps/ui.auth.ts
|
|
1097
|
+
function registerUiAuthSteps(test) {
|
|
1098
|
+
const { Given } = createBdd13(test);
|
|
1099
|
+
Given(
|
|
1100
|
+
"I am authenticated in UI as {string}",
|
|
1101
|
+
{ tags: "@ui" },
|
|
1102
|
+
async ({ page, world }, rolesStr) => {
|
|
1103
|
+
const roles = rolesStr.split(",").map((r) => r.trim());
|
|
1104
|
+
const testRunId = world.testRunId || world.vars.test_run_id || Date.now();
|
|
1105
|
+
const userId = `test-user-${testRunId}`;
|
|
1106
|
+
const isAdmin = roles.includes("admin");
|
|
1107
|
+
const tenantId = isAdmin ? void 0 : "org-a";
|
|
1108
|
+
await setupBypassAuth(page, userId, roles, tenantId);
|
|
1109
|
+
world.vars.currentUserId = userId;
|
|
1110
|
+
world.vars.currentRoles = roles.join(",");
|
|
1111
|
+
if (tenantId) {
|
|
1112
|
+
world.vars.currentTenantId = tenantId;
|
|
1113
|
+
}
|
|
1114
|
+
console.log(`[BDD] UI authenticated as ${roles.join(", ")} (user: ${userId})`);
|
|
1115
|
+
}
|
|
1116
|
+
);
|
|
1117
|
+
Given(
|
|
1118
|
+
"I am authenticated in UI as {string} for tenant {string}",
|
|
1119
|
+
{ tags: "@ui" },
|
|
1120
|
+
async ({ page, world }, rolesStr, tenantId) => {
|
|
1121
|
+
const roles = rolesStr.split(",").map((r) => r.trim());
|
|
1122
|
+
const testRunId = world.testRunId || world.vars.test_run_id || Date.now();
|
|
1123
|
+
const userId = `test-user-${testRunId}`;
|
|
1124
|
+
const resolvedTenantId = interpolate(tenantId, world.vars);
|
|
1125
|
+
await setupBypassAuth(page, userId, roles, resolvedTenantId);
|
|
1126
|
+
world.vars.currentUserId = userId;
|
|
1127
|
+
world.vars.currentTenantId = resolvedTenantId;
|
|
1128
|
+
world.vars.currentRoles = roles.join(",");
|
|
1129
|
+
console.log(`[BDD] UI authenticated as ${roles.join(", ")} for tenant ${resolvedTenantId}`);
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
Given(
|
|
1133
|
+
"I am authenticated in UI as {string} with id {string}",
|
|
1134
|
+
{ tags: "@ui" },
|
|
1135
|
+
async ({ page, world }, rolesStr, userId) => {
|
|
1136
|
+
const roles = rolesStr.split(",").map((r) => r.trim());
|
|
1137
|
+
const resolvedUserId = interpolate(userId, world.vars);
|
|
1138
|
+
const tenantId = "org-a";
|
|
1139
|
+
await setupBypassAuth(page, resolvedUserId, roles, tenantId);
|
|
1140
|
+
world.vars.currentUserId = resolvedUserId;
|
|
1141
|
+
world.vars.currentTenantId = tenantId;
|
|
1142
|
+
world.vars.currentRoles = roles.join(",");
|
|
1143
|
+
console.log(`[BDD] UI authenticated as ${roles.join(", ")} with id ${resolvedUserId}`);
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
Given(
|
|
1147
|
+
"I am authenticated in UI with bearer token {string}",
|
|
1148
|
+
{ tags: "@ui" },
|
|
1149
|
+
async ({ page, world }, token) => {
|
|
1150
|
+
const resolvedToken = interpolate(token, world.vars);
|
|
1151
|
+
await setupBearerAuth(page, resolvedToken);
|
|
1152
|
+
console.log("[BDD] UI authenticated with bearer token");
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
Given(
|
|
1156
|
+
"I am authenticated in UI with headers:",
|
|
1157
|
+
{ tags: "@ui" },
|
|
1158
|
+
async ({ page, world }, dataTable) => {
|
|
1159
|
+
const rows = dataTable.hashes ? dataTable.hashes() : dataTable.rawTable?.slice(1).map((row) => ({
|
|
1160
|
+
header: row[0],
|
|
1161
|
+
value: row[1]
|
|
1162
|
+
})) || [];
|
|
1163
|
+
const headers = {};
|
|
1164
|
+
for (const row of rows) {
|
|
1165
|
+
headers[row.header] = interpolate(row.value, world.vars);
|
|
1166
|
+
}
|
|
1167
|
+
await setupFetchIntercept(
|
|
1168
|
+
page,
|
|
1169
|
+
{ userId: headers["x-user-id"] || "", roles: (headers["x-user-roles"] || "").split(","), tenantId: headers["x-tenant-id"] },
|
|
1170
|
+
{
|
|
1171
|
+
urlPattern: "/api/",
|
|
1172
|
+
headers,
|
|
1173
|
+
localStorageKey: "mockAuth"
|
|
1174
|
+
}
|
|
1175
|
+
);
|
|
1176
|
+
console.log(`[BDD] UI authenticated with custom headers: ${Object.keys(headers).join(", ")}`);
|
|
1177
|
+
}
|
|
1178
|
+
);
|
|
1179
|
+
Given(
|
|
1180
|
+
"I switch UI user to {string} with id {string}",
|
|
1181
|
+
{ tags: "@ui" },
|
|
1182
|
+
async ({ page, world }, rolesStr, userId) => {
|
|
1183
|
+
const roles = rolesStr.split(",").map((r) => r.trim());
|
|
1184
|
+
const resolvedUserId = interpolate(userId, world.vars);
|
|
1185
|
+
const tenantId = world.vars.currentTenantId || "org-a";
|
|
1186
|
+
await page.reload();
|
|
1187
|
+
await setupBypassAuth(page, resolvedUserId, roles, tenantId);
|
|
1188
|
+
world.vars.currentUserId = resolvedUserId;
|
|
1189
|
+
world.vars.currentRoles = roles.join(",");
|
|
1190
|
+
console.log(`[BDD] UI switched to user ${resolvedUserId} with roles ${roles.join(", ")}`);
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
513
1195
|
// src/steps/tui.basic.ts
|
|
514
|
-
import { createBdd as
|
|
1196
|
+
import { createBdd as createBdd14 } from "playwright-bdd";
|
|
515
1197
|
function registerTuiBasicSteps(test) {
|
|
516
|
-
const { Given, When, Then } =
|
|
1198
|
+
const { Given, When, Then } = createBdd14(test);
|
|
517
1199
|
Given("I start the TUI application", { tags: "@tui" }, async ({ tui }) => {
|
|
518
1200
|
await tui.start();
|
|
519
1201
|
await tui.waitForReady();
|
|
@@ -634,9 +1316,9 @@ ${result.diff || "No diff available"}`);
|
|
|
634
1316
|
}
|
|
635
1317
|
|
|
636
1318
|
// src/steps/tui.wizard.ts
|
|
637
|
-
import { createBdd as
|
|
1319
|
+
import { createBdd as createBdd15 } from "playwright-bdd";
|
|
638
1320
|
function registerTuiWizardSteps(test) {
|
|
639
|
-
const { Given, When, Then } =
|
|
1321
|
+
const { Given, When, Then } = createBdd15(test);
|
|
640
1322
|
When("I navigate down {int} times", { tags: "@tui" }, async ({ tui }, times) => {
|
|
641
1323
|
for (let i = 0; i < times; i++) {
|
|
642
1324
|
await tui.pressKey("down");
|
|
@@ -789,10 +1471,15 @@ function registerApiSteps(test) {
|
|
|
789
1471
|
function registerUiSteps(test) {
|
|
790
1472
|
registerUiBasicSteps(test);
|
|
791
1473
|
registerWizardSteps(test);
|
|
1474
|
+
registerFormSteps(test);
|
|
1475
|
+
registerDebugSteps(test);
|
|
1476
|
+
registerLayoutSteps(test);
|
|
1477
|
+
registerUiAuthSteps(test);
|
|
792
1478
|
}
|
|
793
1479
|
function registerSharedSteps(test) {
|
|
794
1480
|
registerSharedVarSteps(test);
|
|
795
1481
|
registerSharedCleanupSteps(test);
|
|
1482
|
+
registerFlagSteps(test);
|
|
796
1483
|
}
|
|
797
1484
|
function registerHybridSuite(test) {
|
|
798
1485
|
registerHybridSteps(test);
|
|
@@ -801,6 +1488,13 @@ function registerTuiSteps(test) {
|
|
|
801
1488
|
registerTuiBasicSteps(test);
|
|
802
1489
|
registerTuiWizardSteps(test);
|
|
803
1490
|
}
|
|
1491
|
+
function registerAllSteps(test) {
|
|
1492
|
+
registerApiSteps(test);
|
|
1493
|
+
registerUiSteps(test);
|
|
1494
|
+
registerTuiSteps(test);
|
|
1495
|
+
registerSharedSteps(test);
|
|
1496
|
+
registerHybridSuite(test);
|
|
1497
|
+
}
|
|
804
1498
|
|
|
805
1499
|
export {
|
|
806
1500
|
interpolate,
|
|
@@ -809,19 +1503,33 @@ export {
|
|
|
809
1503
|
parseExpected,
|
|
810
1504
|
assertMasked,
|
|
811
1505
|
registerCleanup,
|
|
1506
|
+
defaultBypassConfig,
|
|
1507
|
+
defaultBearerConfig,
|
|
1508
|
+
setupFetchIntercept,
|
|
1509
|
+
clearFetchIntercept,
|
|
1510
|
+
setupBypassAuth,
|
|
1511
|
+
setupBearerAuth,
|
|
812
1512
|
registerApiHttpSteps,
|
|
813
1513
|
registerApiAssertionSteps,
|
|
814
1514
|
registerApiAuthSteps,
|
|
815
1515
|
registerHybridSteps,
|
|
816
1516
|
registerSharedCleanupSteps,
|
|
817
1517
|
registerSharedVarSteps,
|
|
1518
|
+
isFlagEnabled,
|
|
1519
|
+
setFlag,
|
|
1520
|
+
registerFlagSteps,
|
|
818
1521
|
registerUiBasicSteps,
|
|
819
1522
|
registerWizardSteps,
|
|
1523
|
+
registerFormSteps,
|
|
1524
|
+
registerDebugSteps,
|
|
1525
|
+
registerLayoutSteps,
|
|
1526
|
+
registerUiAuthSteps,
|
|
820
1527
|
registerTuiBasicSteps,
|
|
821
1528
|
registerTuiWizardSteps,
|
|
822
1529
|
registerApiSteps,
|
|
823
1530
|
registerUiSteps,
|
|
824
1531
|
registerSharedSteps,
|
|
825
1532
|
registerHybridSuite,
|
|
826
|
-
registerTuiSteps
|
|
1533
|
+
registerTuiSteps,
|
|
1534
|
+
registerAllSteps
|
|
827
1535
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as playwright_bdd from 'playwright-bdd';
|
|
|
2
2
|
export { test as baseTest } from 'playwright-bdd';
|
|
3
3
|
import * as _playwright_test from '@playwright/test';
|
|
4
4
|
import { APIResponse, PlaywrightTestArgs, PlaywrightWorkerArgs, APIRequestContext, Page } from '@playwright/test';
|
|
5
|
-
export { registerApiAssertionSteps, registerApiAuthSteps, registerApiHttpSteps, registerApiSteps, registerHybridSteps, registerHybridSuite, registerSharedCleanupSteps, registerSharedSteps, registerSharedVarSteps, registerTuiBasicSteps, registerTuiSteps, registerTuiWizardSteps, registerUiBasicSteps, registerUiSteps, registerWizardSteps } from './steps/index.js';
|
|
5
|
+
export { isFlagEnabled, registerAllSteps, registerApiAssertionSteps, registerApiAuthSteps, registerApiHttpSteps, registerApiSteps, registerDebugSteps, registerFlagSteps, registerFormSteps, registerHybridSteps, registerHybridSuite, registerLayoutSteps, registerSharedCleanupSteps, registerSharedSteps, registerSharedVarSteps, registerTuiBasicSteps, registerTuiSteps, registerTuiWizardSteps, registerUiAuthSteps, registerUiBasicSteps, registerUiSteps, registerWizardSteps, setFlag } from './steps/index.js';
|
|
6
6
|
|
|
7
7
|
type CleanupItem = {
|
|
8
8
|
method: 'DELETE' | 'POST' | 'PATCH' | 'PUT';
|
|
@@ -529,6 +529,64 @@ declare class TuiTesterAdapter implements TuiPort {
|
|
|
529
529
|
getConfig(): TuiConfig;
|
|
530
530
|
}
|
|
531
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Fetch Intercept Auth Adapter
|
|
534
|
+
*
|
|
535
|
+
* Provides UI authentication by intercepting fetch requests and injecting headers.
|
|
536
|
+
* Useful for testing UIs that communicate with APIs using fetch.
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
interface FetchInterceptAuthData {
|
|
540
|
+
userId: string;
|
|
541
|
+
tenantId?: string;
|
|
542
|
+
roles: string[];
|
|
543
|
+
token?: string;
|
|
544
|
+
}
|
|
545
|
+
interface FetchInterceptConfig {
|
|
546
|
+
/**
|
|
547
|
+
* URL pattern to match for header injection.
|
|
548
|
+
* If not provided, headers are added to all fetch requests.
|
|
549
|
+
*/
|
|
550
|
+
urlPattern?: string | RegExp;
|
|
551
|
+
/**
|
|
552
|
+
* Headers to inject. Can be static values or functions that receive auth data.
|
|
553
|
+
*/
|
|
554
|
+
headers: Record<string, string | ((data: FetchInterceptAuthData) => string)>;
|
|
555
|
+
/**
|
|
556
|
+
* Key in localStorage to store auth data for UI components to read.
|
|
557
|
+
*/
|
|
558
|
+
localStorageKey?: string;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Default configuration for bypass auth mode.
|
|
562
|
+
*/
|
|
563
|
+
declare const defaultBypassConfig: FetchInterceptConfig;
|
|
564
|
+
/**
|
|
565
|
+
* Default configuration for bearer token auth.
|
|
566
|
+
*/
|
|
567
|
+
declare const defaultBearerConfig: FetchInterceptConfig;
|
|
568
|
+
/**
|
|
569
|
+
* Set up fetch interception on a page.
|
|
570
|
+
*
|
|
571
|
+
* @param page - Playwright page
|
|
572
|
+
* @param authData - Authentication data to inject
|
|
573
|
+
* @param config - Configuration for interception
|
|
574
|
+
*/
|
|
575
|
+
declare function setupFetchIntercept(page: Page, authData: FetchInterceptAuthData, config?: FetchInterceptConfig): Promise<void>;
|
|
576
|
+
/**
|
|
577
|
+
* Clear fetch interception setup (by reloading the page).
|
|
578
|
+
* Note: There's no clean way to remove addInitScript, so we reload.
|
|
579
|
+
*/
|
|
580
|
+
declare function clearFetchIntercept(page: Page): Promise<void>;
|
|
581
|
+
/**
|
|
582
|
+
* Helper to set up bypass auth for a page.
|
|
583
|
+
*/
|
|
584
|
+
declare function setupBypassAuth(page: Page, userId: string, roles: string[], tenantId?: string): Promise<void>;
|
|
585
|
+
/**
|
|
586
|
+
* Helper to set up bearer token auth for a page.
|
|
587
|
+
*/
|
|
588
|
+
declare function setupBearerAuth(page: Page, token: string): Promise<void>;
|
|
589
|
+
|
|
532
590
|
type TagsForProjectInput = {
|
|
533
591
|
projectTag: string;
|
|
534
592
|
extraTags?: string;
|
|
@@ -537,4 +595,4 @@ type TagsForProjectInput = {
|
|
|
537
595
|
declare function tagsForProject({ projectTag, extraTags, defaultExcludes }: TagsForProjectInput): string;
|
|
538
596
|
declare function resolveExtraTags(raw?: string | null): string | undefined;
|
|
539
597
|
|
|
540
|
-
export { type ApiMethod, type ApiPort, type ApiResult, type AuthPort, type CleanupItem, type CleanupPort, type CleanupRule, type CreateBddTestOptions, DefaultCleanupAdapter, PlaywrightApiAdapter, PlaywrightUiAdapter, type TuiConfig, type TuiFactory, type TuiKeyModifiers, type TuiMouseButton, type TuiMouseEvent, type TuiMouseEventType, type TuiPort, type TuiScreenCapture, type TuiSnapshotResult, TuiTesterAdapter, type TuiWaitOptions, type UiClickMode, type UiElementState, type UiInputMode, type UiLocatorMethod, type UiPort, type UiUrlAssertMode, UniversalAuthAdapter, type World, assertMasked, createBddTest, initWorld, interpolate, parseExpected, registerCleanup, resolveExtraTags, selectPath, tagsForProject, tryParseJson };
|
|
598
|
+
export { type ApiMethod, type ApiPort, type ApiResult, type AuthPort, type CleanupItem, type CleanupPort, type CleanupRule, type CreateBddTestOptions, DefaultCleanupAdapter, type FetchInterceptAuthData, type FetchInterceptConfig, PlaywrightApiAdapter, PlaywrightUiAdapter, type TuiConfig, type TuiFactory, type TuiKeyModifiers, type TuiMouseButton, type TuiMouseEvent, type TuiMouseEventType, type TuiPort, type TuiScreenCapture, type TuiSnapshotResult, TuiTesterAdapter, type TuiWaitOptions, type UiClickMode, type UiElementState, type UiInputMode, type UiLocatorMethod, type UiPort, type UiUrlAssertMode, UniversalAuthAdapter, type World, assertMasked, clearFetchIntercept, createBddTest, defaultBearerConfig, defaultBypassConfig, initWorld, interpolate, parseExpected, registerCleanup, resolveExtraTags, selectPath, setupBearerAuth, setupBypassAuth, setupFetchIntercept, tagsForProject, tryParseJson };
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertMasked,
|
|
3
|
+
clearFetchIntercept,
|
|
4
|
+
defaultBearerConfig,
|
|
5
|
+
defaultBypassConfig,
|
|
3
6
|
interpolate,
|
|
7
|
+
isFlagEnabled,
|
|
4
8
|
parseExpected,
|
|
9
|
+
registerAllSteps,
|
|
5
10
|
registerApiAssertionSteps,
|
|
6
11
|
registerApiAuthSteps,
|
|
7
12
|
registerApiHttpSteps,
|
|
8
13
|
registerApiSteps,
|
|
9
14
|
registerCleanup,
|
|
15
|
+
registerDebugSteps,
|
|
16
|
+
registerFlagSteps,
|
|
17
|
+
registerFormSteps,
|
|
10
18
|
registerHybridSteps,
|
|
11
19
|
registerHybridSuite,
|
|
20
|
+
registerLayoutSteps,
|
|
12
21
|
registerSharedCleanupSteps,
|
|
13
22
|
registerSharedSteps,
|
|
14
23
|
registerSharedVarSteps,
|
|
15
24
|
registerTuiBasicSteps,
|
|
16
25
|
registerTuiSteps,
|
|
17
26
|
registerTuiWizardSteps,
|
|
27
|
+
registerUiAuthSteps,
|
|
18
28
|
registerUiBasicSteps,
|
|
19
29
|
registerUiSteps,
|
|
20
30
|
registerWizardSteps,
|
|
21
31
|
selectPath,
|
|
32
|
+
setFlag,
|
|
33
|
+
setupBearerAuth,
|
|
34
|
+
setupBypassAuth,
|
|
35
|
+
setupFetchIntercept,
|
|
22
36
|
tryParseJson
|
|
23
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VWBEMXYC.js";
|
|
24
38
|
|
|
25
39
|
// src/fixtures.ts
|
|
26
40
|
import { test as base } from "playwright-bdd";
|
|
@@ -854,28 +868,42 @@ export {
|
|
|
854
868
|
UniversalAuthAdapter,
|
|
855
869
|
assertMasked,
|
|
856
870
|
base as baseTest,
|
|
871
|
+
clearFetchIntercept,
|
|
857
872
|
createBddTest,
|
|
873
|
+
defaultBearerConfig,
|
|
874
|
+
defaultBypassConfig,
|
|
858
875
|
initWorld,
|
|
859
876
|
interpolate,
|
|
877
|
+
isFlagEnabled,
|
|
860
878
|
parseExpected,
|
|
879
|
+
registerAllSteps,
|
|
861
880
|
registerApiAssertionSteps,
|
|
862
881
|
registerApiAuthSteps,
|
|
863
882
|
registerApiHttpSteps,
|
|
864
883
|
registerApiSteps,
|
|
865
884
|
registerCleanup,
|
|
885
|
+
registerDebugSteps,
|
|
886
|
+
registerFlagSteps,
|
|
887
|
+
registerFormSteps,
|
|
866
888
|
registerHybridSteps,
|
|
867
889
|
registerHybridSuite,
|
|
890
|
+
registerLayoutSteps,
|
|
868
891
|
registerSharedCleanupSteps,
|
|
869
892
|
registerSharedSteps,
|
|
870
893
|
registerSharedVarSteps,
|
|
871
894
|
registerTuiBasicSteps,
|
|
872
895
|
registerTuiSteps,
|
|
873
896
|
registerTuiWizardSteps,
|
|
897
|
+
registerUiAuthSteps,
|
|
874
898
|
registerUiBasicSteps,
|
|
875
899
|
registerUiSteps,
|
|
876
900
|
registerWizardSteps,
|
|
877
901
|
resolveExtraTags,
|
|
878
902
|
selectPath,
|
|
903
|
+
setFlag,
|
|
904
|
+
setupBearerAuth,
|
|
905
|
+
setupBypassAuth,
|
|
906
|
+
setupFetchIntercept,
|
|
879
907
|
tagsForProject,
|
|
880
908
|
tryParseJson
|
|
881
909
|
};
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -10,10 +10,60 @@ declare function registerSharedCleanupSteps(test: any): void;
|
|
|
10
10
|
|
|
11
11
|
declare function registerSharedVarSteps(test: any): void;
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Feature Flag Step Definitions
|
|
15
|
+
*
|
|
16
|
+
* Steps for managing feature flags in tests.
|
|
17
|
+
* These steps store flag state in the world object for use in other steps.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Check if a feature flag is enabled.
|
|
21
|
+
* Can be used by other steps for conditional logic.
|
|
22
|
+
*/
|
|
23
|
+
declare function isFlagEnabled(world: any, flagName: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Set a feature flag value.
|
|
26
|
+
* Can be used programmatically by other steps or adapters.
|
|
27
|
+
*/
|
|
28
|
+
declare function setFlag(world: any, flagName: string, enabled: boolean): void;
|
|
29
|
+
declare function registerFlagSteps(test: any): void;
|
|
30
|
+
|
|
13
31
|
declare function registerUiBasicSteps(test: any): void;
|
|
14
32
|
|
|
15
33
|
declare function registerWizardSteps(test: any): void;
|
|
16
34
|
|
|
35
|
+
/**
|
|
36
|
+
* UI Form Step Definitions
|
|
37
|
+
*
|
|
38
|
+
* Steps for bulk form filling using Gherkin data tables.
|
|
39
|
+
* Tagged with @ui or @hybrid for selective execution.
|
|
40
|
+
*/
|
|
41
|
+
declare function registerFormSteps(test: any): void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* UI Debug Step Definitions
|
|
45
|
+
*
|
|
46
|
+
* Steps for debugging and troubleshooting UI tests.
|
|
47
|
+
* Tagged with @ui or @hybrid for selective execution.
|
|
48
|
+
*/
|
|
49
|
+
declare function registerDebugSteps(test: any): void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* UI Layout Assertion Step Definitions
|
|
53
|
+
*
|
|
54
|
+
* Steps for asserting layout states like panels, split views, and responsive behavior.
|
|
55
|
+
* Tagged with @ui or @hybrid for selective execution.
|
|
56
|
+
*/
|
|
57
|
+
declare function registerLayoutSteps(test: any): void;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* UI Auth Step Definitions
|
|
61
|
+
*
|
|
62
|
+
* Steps for authenticating in UI tests using fetch interception.
|
|
63
|
+
* Tagged with @ui for selective execution.
|
|
64
|
+
*/
|
|
65
|
+
declare function registerUiAuthSteps(test: any): void;
|
|
66
|
+
|
|
17
67
|
/**
|
|
18
68
|
* TUI Basic Step Definitions
|
|
19
69
|
*
|
|
@@ -32,9 +82,37 @@ declare function registerTuiBasicSteps(test: any): void;
|
|
|
32
82
|
*/
|
|
33
83
|
declare function registerTuiWizardSteps(test: any): void;
|
|
34
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Register all API step definitions.
|
|
87
|
+
* Steps are tagged with @api or @hybrid for selective execution.
|
|
88
|
+
*/
|
|
35
89
|
declare function registerApiSteps(test: any): void;
|
|
90
|
+
/**
|
|
91
|
+
* Register all UI step definitions.
|
|
92
|
+
* Steps are tagged with @ui or @hybrid for selective execution.
|
|
93
|
+
*
|
|
94
|
+
* Includes:
|
|
95
|
+
* - Basic UI steps (navigation, clicks, fills)
|
|
96
|
+
* - Wizard steps (advanced interactions, locators, assertions)
|
|
97
|
+
* - Form steps (bulk form filling)
|
|
98
|
+
* - Debug steps (debugging utilities)
|
|
99
|
+
* - Layout steps (panel, split view, responsive assertions)
|
|
100
|
+
* - Auth steps (fetch interception auth)
|
|
101
|
+
*/
|
|
36
102
|
declare function registerUiSteps(test: any): void;
|
|
103
|
+
/**
|
|
104
|
+
* Register shared step definitions (used by all test types).
|
|
105
|
+
*
|
|
106
|
+
* Includes:
|
|
107
|
+
* - Variable management steps
|
|
108
|
+
* - Cleanup steps
|
|
109
|
+
* - Feature flag steps
|
|
110
|
+
*/
|
|
37
111
|
declare function registerSharedSteps(test: any): void;
|
|
112
|
+
/**
|
|
113
|
+
* Register hybrid step definitions.
|
|
114
|
+
* Steps that work with both API and UI in the same scenario.
|
|
115
|
+
*/
|
|
38
116
|
declare function registerHybridSuite(test: any): void;
|
|
39
117
|
/**
|
|
40
118
|
* Register all TUI (Terminal User Interface) step definitions.
|
|
@@ -42,7 +120,7 @@ declare function registerHybridSuite(test: any): void;
|
|
|
42
120
|
*
|
|
43
121
|
* @example
|
|
44
122
|
* ```typescript
|
|
45
|
-
* import { createBddTest, registerTuiSteps, TuiTesterAdapter } from '@
|
|
123
|
+
* import { createBddTest, registerTuiSteps, TuiTesterAdapter } from '@esimplicity/stack-tests';
|
|
46
124
|
*
|
|
47
125
|
* const test = createBddTest({
|
|
48
126
|
* createTui: () => new TuiTesterAdapter({
|
|
@@ -54,5 +132,20 @@ declare function registerHybridSuite(test: any): void;
|
|
|
54
132
|
* ```
|
|
55
133
|
*/
|
|
56
134
|
declare function registerTuiSteps(test: any): void;
|
|
135
|
+
/**
|
|
136
|
+
* Register all step definitions for a full-featured test suite.
|
|
137
|
+
* This is a convenience function that registers API, UI, TUI, Shared, and Hybrid steps.
|
|
138
|
+
*
|
|
139
|
+
* Note: TUI steps require the optional `tui-tester` peer dependency.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { createBddTest, registerAllSteps } from '@esimplicity/stack-tests';
|
|
144
|
+
*
|
|
145
|
+
* const test = createBddTest({ ... });
|
|
146
|
+
* registerAllSteps(test);
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
declare function registerAllSteps(test: any): void;
|
|
57
150
|
|
|
58
|
-
export { registerApiAssertionSteps, registerApiAuthSteps, registerApiHttpSteps, registerApiSteps, registerHybridSteps, registerHybridSuite, registerSharedCleanupSteps, registerSharedSteps, registerSharedVarSteps, registerTuiBasicSteps, registerTuiSteps, registerTuiWizardSteps, registerUiBasicSteps, registerUiSteps, registerWizardSteps };
|
|
151
|
+
export { isFlagEnabled, registerAllSteps, registerApiAssertionSteps, registerApiAuthSteps, registerApiHttpSteps, registerApiSteps, registerDebugSteps, registerFlagSteps, registerFormSteps, registerHybridSteps, registerHybridSuite, registerLayoutSteps, registerSharedCleanupSteps, registerSharedSteps, registerSharedVarSteps, registerTuiBasicSteps, registerTuiSteps, registerTuiWizardSteps, registerUiAuthSteps, registerUiBasicSteps, registerUiSteps, registerWizardSteps, setFlag };
|
package/dist/steps/index.js
CHANGED
|
@@ -1,34 +1,50 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isFlagEnabled,
|
|
3
|
+
registerAllSteps,
|
|
2
4
|
registerApiAssertionSteps,
|
|
3
5
|
registerApiAuthSteps,
|
|
4
6
|
registerApiHttpSteps,
|
|
5
7
|
registerApiSteps,
|
|
8
|
+
registerDebugSteps,
|
|
9
|
+
registerFlagSteps,
|
|
10
|
+
registerFormSteps,
|
|
6
11
|
registerHybridSteps,
|
|
7
12
|
registerHybridSuite,
|
|
13
|
+
registerLayoutSteps,
|
|
8
14
|
registerSharedCleanupSteps,
|
|
9
15
|
registerSharedSteps,
|
|
10
16
|
registerSharedVarSteps,
|
|
11
17
|
registerTuiBasicSteps,
|
|
12
18
|
registerTuiSteps,
|
|
13
19
|
registerTuiWizardSteps,
|
|
20
|
+
registerUiAuthSteps,
|
|
14
21
|
registerUiBasicSteps,
|
|
15
22
|
registerUiSteps,
|
|
16
|
-
registerWizardSteps
|
|
17
|
-
|
|
23
|
+
registerWizardSteps,
|
|
24
|
+
setFlag
|
|
25
|
+
} from "../chunk-VWBEMXYC.js";
|
|
18
26
|
export {
|
|
27
|
+
isFlagEnabled,
|
|
28
|
+
registerAllSteps,
|
|
19
29
|
registerApiAssertionSteps,
|
|
20
30
|
registerApiAuthSteps,
|
|
21
31
|
registerApiHttpSteps,
|
|
22
32
|
registerApiSteps,
|
|
33
|
+
registerDebugSteps,
|
|
34
|
+
registerFlagSteps,
|
|
35
|
+
registerFormSteps,
|
|
23
36
|
registerHybridSteps,
|
|
24
37
|
registerHybridSuite,
|
|
38
|
+
registerLayoutSteps,
|
|
25
39
|
registerSharedCleanupSteps,
|
|
26
40
|
registerSharedSteps,
|
|
27
41
|
registerSharedVarSteps,
|
|
28
42
|
registerTuiBasicSteps,
|
|
29
43
|
registerTuiSteps,
|
|
30
44
|
registerTuiWizardSteps,
|
|
45
|
+
registerUiAuthSteps,
|
|
31
46
|
registerUiBasicSteps,
|
|
32
47
|
registerUiSteps,
|
|
33
|
-
registerWizardSteps
|
|
48
|
+
registerWizardSteps,
|
|
49
|
+
setFlag
|
|
34
50
|
};
|
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -78,7 +78,7 @@ function main() {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// Always show success message
|
|
81
|
-
console.log(`${GREEN}${BOLD}@
|
|
81
|
+
console.log(`${GREEN}${BOLD}@esimplicity/stack-tests${RESET} installed successfully!`);
|
|
82
82
|
console.log('');
|
|
83
83
|
}
|
|
84
84
|
|