@esimplicitylabs/katalyst-xspec 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/cli/init.cjs +48 -21
- package/cli/upgrade.cjs +21 -10
- package/dist/{chunk-UTTKW2SR.js → chunk-VHVH476W.js} +39 -6
- package/dist/index.d.ts +160 -8
- package/dist/index.js +323 -71
- package/dist/steps/index.js +1 -1
- package/package.json +1 -1
- package/skills/katalyst-bdd-architecture/SKILL.md +23 -38
- package/skills/katalyst-bdd-architecture/references/adapters.md +44 -25
- package/skills/katalyst-bdd-architecture/references/ports.md +16 -2
- package/skills/katalyst-bdd-create-test/SKILL.md +15 -5
- package/skills/katalyst-bdd-create-test/references/api-patterns.md +25 -16
- package/skills/katalyst-bdd-create-test/references/hybrid-patterns.md +30 -13
- package/skills/katalyst-bdd-create-test/references/ui-patterns.md +11 -0
- package/skills/katalyst-bdd-quickstart/SKILL.md +21 -17
- package/skills/katalyst-bdd-step-reference/SKILL.md +7 -4
- package/skills/katalyst-bdd-step-reference/references/api-steps.md +14 -6
- package/skills/katalyst-bdd-step-reference/references/shared-steps.md +2 -2
- package/skills/katalyst-bdd-step-reference/references/ui-steps.md +22 -1
- package/skills/katalyst-bdd-troubleshooting/SKILL.md +46 -37
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ npm test
|
|
|
22
22
|
|
|
23
23
|
- **Fixtures**: `createBddTest` wiring world, api/ui/auth/cleanup adapters.
|
|
24
24
|
- **Ports**: `ApiPort`, `UiPort`, `AuthPort`, `CleanupPort`.
|
|
25
|
-
- **Adapters**: Playwright API/UI adapters, default cleanup,
|
|
25
|
+
- **Adapters**: Playwright API/UI adapters, default cleanup, `UniversalAuthAdapter` (role-based API and UI login).
|
|
26
26
|
- **Step registrations**: API (auth/http/assertions), UI (basic + wizard), shared vars/cleanup, hybrid helpers.
|
|
27
27
|
- **Config helpers**: `tagsForProject` / `resolveExtraTags` for optional tag filtering (`@Skip`/`@ignore`, `TEST_TAGS`).
|
|
28
28
|
|
|
@@ -57,6 +57,20 @@ registerApiSteps(test);
|
|
|
57
57
|
|
|
58
58
|
3) Configure Playwright projects with your features/steps globs (each project selects feature files by folder; tags are optional). Keep `@playwright/test` and `playwright-bdd` aligned with peer ranges.
|
|
59
59
|
|
|
60
|
+
## Logging in
|
|
61
|
+
|
|
62
|
+
Set credentials per role in `.env` (`AUTH_ADMIN_USERNAME` / `AUTH_ADMIN_PASSWORD`, `AUTH_PM_USERNAME` / ..., any role name) and use the role in features:
|
|
63
|
+
|
|
64
|
+
```gherkin
|
|
65
|
+
Given I am authenticated as "pm" via API
|
|
66
|
+
Given I am logged in as "pm"
|
|
67
|
+
When I log in as "pm" in UI
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Missing credentials fail the step with a message naming the variables. Login endpoint, body format, token location and form fields are set with `API_AUTH_*` / `UI_*` variables; see the [Authentication guide](https://github.com/esimplicityinc/katalyst-xspec/blob/main/docs/guides/authentication.md).
|
|
71
|
+
|
|
72
|
+
`FRONTEND_URL` sets where UI steps go; `API_BASE_URL` is optional (API calls go to `FRONTEND_URL` without it).
|
|
73
|
+
|
|
60
74
|
## Publishing (npm)
|
|
61
75
|
|
|
62
76
|
Publishing is automated. The `.github/workflows/publish.yml` workflow builds the package and publishes it to the
|
package/cli/init.cjs
CHANGED
|
@@ -301,7 +301,7 @@ function templates(packageName) {
|
|
|
301
301
|
clean: 'rm -rf .features-gen node_modules test-results storage cucumber-report playwright-report'
|
|
302
302
|
},
|
|
303
303
|
devDependencies: {
|
|
304
|
-
'@esimplicitylabs/katalyst-xspec': '^0.
|
|
304
|
+
'@esimplicitylabs/katalyst-xspec': '^0.8.0',
|
|
305
305
|
'@playwright/test': '^1.49.0',
|
|
306
306
|
'playwright-bdd': '^9.1.0',
|
|
307
307
|
dotenv: '^16.1.4',
|
|
@@ -335,7 +335,12 @@ function templates(packageName) {
|
|
|
335
335
|
export const { test } = createBddTest({
|
|
336
336
|
createApi: ({ apiRequest }) => new PlaywrightApiAdapter(apiRequest),
|
|
337
337
|
createUi: ({ page }) => new PlaywrightUiAdapter(page),
|
|
338
|
-
|
|
338
|
+
// Logins read AUTH_<ROLE>_USERNAME / AUTH_<ROLE>_PASSWORD and the API_AUTH_* /
|
|
339
|
+
// UI_LOGIN_* settings in .env. You can also give credentials in code:
|
|
340
|
+
// roles: { pm: { username: 'pm@example.com', password: process.env.PM_PASSWORD } },
|
|
341
|
+
// For SSO or unusual login flows, subclass UniversalAuthAdapter and override
|
|
342
|
+
// apiLogin() / uiLogin() (see the Authentication guide).
|
|
343
|
+
createAuth: ({ api, ui }) => new UniversalAuthAdapter({ api, ui, roles: {} }),
|
|
339
344
|
createCleanup: () => new DefaultCleanupAdapter(),
|
|
340
345
|
// TUI testing (optional - requires tui-tester and tmux installed)
|
|
341
346
|
// Uncomment and configure for your CLI application:
|
|
@@ -370,7 +375,7 @@ export { test };
|
|
|
370
375
|
|
|
371
376
|
const playwrightConfig = `import { defineConfig } from '@playwright/test';
|
|
372
377
|
import { defineBddProject, cucumberReporter } from 'playwright-bdd';
|
|
373
|
-
import { resolveWorkers, tagsForProject, resolveExtraTags } from '@esimplicitylabs/katalyst-xspec';
|
|
378
|
+
import { resolveWorkers, tagsForProject, resolveExtraTags, resolveTargets, logTargets } from '@esimplicitylabs/katalyst-xspec';
|
|
374
379
|
import dotenv from 'dotenv';
|
|
375
380
|
import fs from 'node:fs';
|
|
376
381
|
import path from 'node:path';
|
|
@@ -388,6 +393,10 @@ if (fs.existsSync(localEnvPath)) {
|
|
|
388
393
|
dotenv.config();
|
|
389
394
|
}
|
|
390
395
|
|
|
396
|
+
// UI and API base URLs from FRONTEND_URL / API_BASE_URL (API falls back to the
|
|
397
|
+
// frontend URL). Printed once per run; silence with KATALYST_XSPEC_QUIET=true.
|
|
398
|
+
const targets = logTargets(resolveTargets());
|
|
399
|
+
|
|
391
400
|
// Each project runs the feature files in its folder. Any scenario can use any
|
|
392
401
|
// step (API, UI, shared). Tags are optional: use your own (e.g. @smoke) and
|
|
393
402
|
// filter with TEST_TAGS="@smoke"; scenarios tagged @Skip or @ignore are skipped.
|
|
@@ -425,7 +434,7 @@ export default defineConfig({
|
|
|
425
434
|
// Add tuiBdd to this array when TUI testing is enabled
|
|
426
435
|
projects: [apiBdd, uiBdd /* , tuiBdd */],
|
|
427
436
|
use: {
|
|
428
|
-
baseURL:
|
|
437
|
+
baseURL: targets.frontendUrl,
|
|
429
438
|
headless: process.env.HEADLESS === 'false' ? false : true,
|
|
430
439
|
},
|
|
431
440
|
});
|
|
@@ -480,27 +489,45 @@ storage
|
|
|
480
489
|
.env
|
|
481
490
|
`;
|
|
482
491
|
|
|
483
|
-
const envExample = `#
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
API_AUTH_LOGIN_PATH=/auth/login
|
|
487
|
-
API_BASE_URL=http://localhost:3000
|
|
488
|
-
|
|
489
|
-
# UI defaults
|
|
492
|
+
const envExample = `# ── Where to test ──────────────────────────────────────────────
|
|
493
|
+
# Relative paths in steps ("/login", "/api/users") use these.
|
|
494
|
+
# API_BASE_URL is optional: without it, API calls go to FRONTEND_URL.
|
|
490
495
|
FRONTEND_URL=http://localhost:3000
|
|
496
|
+
# API_BASE_URL=http://localhost:4000
|
|
491
497
|
HEADLESS=true
|
|
492
498
|
|
|
493
|
-
#
|
|
499
|
+
# ── Who logs in ────────────────────────────────────────────────
|
|
500
|
+
# One pair per role. Use any role name: "pm" reads AUTH_PM_USERNAME/PASSWORD.
|
|
501
|
+
# Given I am authenticated as "admin" via API
|
|
502
|
+
# Given I am logged in as "admin"
|
|
503
|
+
AUTH_ADMIN_USERNAME=admin@example.com
|
|
504
|
+
AUTH_ADMIN_PASSWORD=changeme
|
|
505
|
+
# AUTH_USER_USERNAME=user@example.com
|
|
506
|
+
# AUTH_USER_PASSWORD=changeme
|
|
507
|
+
|
|
508
|
+
# ── API login (defaults shown) ─────────────────────────────────
|
|
509
|
+
# API_AUTH_LOGIN_PATH=/auth/login
|
|
510
|
+
# API_AUTH_BODY=form # or json
|
|
511
|
+
# API_AUTH_USERNAME_FIELD=username # e.g. email
|
|
512
|
+
# API_AUTH_PASSWORD_FIELD=password
|
|
513
|
+
# API_AUTH_TOKEN_PATH=access_token # e.g. data.token; default tries common names.
|
|
514
|
+
# # No token but a session cookie also works.
|
|
515
|
+
|
|
516
|
+
# ── UI login (defaults shown) ──────────────────────────────────
|
|
517
|
+
# UI_LOGIN_PATH=/login
|
|
518
|
+
# UI_USERNAME_FIELD=Username # label, placeholder or name of the field
|
|
519
|
+
# UI_PASSWORD_FIELD=Password
|
|
520
|
+
# UI_LOGIN_BUTTON=Login
|
|
521
|
+
# UI_LOGIN_SUCCESS_URL=/dashboard # default: any page other than the login page
|
|
522
|
+
# UI_LOGIN_SUCCESS_TEXT=Welcome # use if the URL doesn't change after login
|
|
523
|
+
# UI_SESSION_REUSE=true # "I am logged in as" reuses the session
|
|
524
|
+
|
|
525
|
+
# ── Other ──────────────────────────────────────────────────────
|
|
494
526
|
# CLEANUP_RULES=[{"varMatch":"user","path":"/api/users/{id}"}]
|
|
495
|
-
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
DEBUG=false
|
|
499
|
-
|
|
500
|
-
# Worker configuration
|
|
501
|
-
# Set to a number for explicit worker count, or "auto" to let Playwright decide
|
|
502
|
-
# In CI, defaults to 1 for stability unless explicitly overridden
|
|
503
|
-
# WORKERS=auto
|
|
527
|
+
# TEST_TAGS=@smoke # run only scenarios with these tags
|
|
528
|
+
# WORKERS=auto # defaults to 1 in CI
|
|
529
|
+
# KATALYST_XSPEC_QUIET=true # don't print the targets line
|
|
530
|
+
# DEBUG=false # TUI tester output
|
|
504
531
|
`;
|
|
505
532
|
|
|
506
533
|
const readme = `# ${packageName}
|
package/cli/upgrade.cjs
CHANGED
|
@@ -553,7 +553,7 @@ function getTemplates() {
|
|
|
553
553
|
clean: 'rm -rf .features-gen node_modules test-results storage cucumber-report playwright-report'
|
|
554
554
|
},
|
|
555
555
|
devDependencies: {
|
|
556
|
-
'@esimplicitylabs/katalyst-xspec': '^0.
|
|
556
|
+
'@esimplicitylabs/katalyst-xspec': '^0.8.0',
|
|
557
557
|
'@playwright/test': '^1.49.0',
|
|
558
558
|
'playwright-bdd': '^9.1.0',
|
|
559
559
|
dotenv: '^16.1.4',
|
|
@@ -576,7 +576,12 @@ function getTemplates() {
|
|
|
576
576
|
export const { test } = createBddTest({
|
|
577
577
|
createApi: ({ apiRequest }) => new PlaywrightApiAdapter(apiRequest),
|
|
578
578
|
createUi: ({ page }) => new PlaywrightUiAdapter(page),
|
|
579
|
-
|
|
579
|
+
// Logins read AUTH_<ROLE>_USERNAME / AUTH_<ROLE>_PASSWORD and the API_AUTH_* /
|
|
580
|
+
// UI_LOGIN_* settings in .env. You can also give credentials in code:
|
|
581
|
+
// roles: { pm: { username: 'pm@example.com', password: process.env.PM_PASSWORD } },
|
|
582
|
+
// For SSO or unusual login flows, subclass UniversalAuthAdapter and override
|
|
583
|
+
// apiLogin() / uiLogin() (see the Authentication guide).
|
|
584
|
+
createAuth: ({ api, ui }) => new UniversalAuthAdapter({ api, ui, roles: {} }),
|
|
580
585
|
createCleanup: () => new DefaultCleanupAdapter(),
|
|
581
586
|
// TUI testing (optional - requires tui-tester and tmux installed)
|
|
582
587
|
// Uncomment and configure for your CLI application:
|
|
@@ -634,6 +639,7 @@ async function migrate(cwd, options) {
|
|
|
634
639
|
|
|
635
640
|
log('Starting migration...');
|
|
636
641
|
log(`Backup directory: ${backupDir}`);
|
|
642
|
+
const templates = getTemplates();
|
|
637
643
|
console.log('');
|
|
638
644
|
|
|
639
645
|
// =========================================================================
|
|
@@ -714,6 +720,7 @@ async function migrate(cwd, options) {
|
|
|
714
720
|
}
|
|
715
721
|
if (existingFixturesTs) {
|
|
716
722
|
fs.writeFileSync(path.join(backupDir, 'steps', 'fixtures.ts.original'), existingFixturesTs);
|
|
723
|
+
fs.writeFileSync(path.join(backupDir, 'steps', 'fixtures.ts.template'), templates['features/steps/fixtures.ts']);
|
|
717
724
|
}
|
|
718
725
|
}
|
|
719
726
|
|
|
@@ -728,7 +735,6 @@ async function migrate(cwd, options) {
|
|
|
728
735
|
// =========================================================================
|
|
729
736
|
log('Phase 3: Merging configurations...');
|
|
730
737
|
|
|
731
|
-
const templates = getTemplates();
|
|
732
738
|
const filesToUpdate = {};
|
|
733
739
|
|
|
734
740
|
// Merge package.json
|
|
@@ -745,13 +751,18 @@ async function migrate(cwd, options) {
|
|
|
745
751
|
);
|
|
746
752
|
console.log(` steps.ts: merged (${customImports.length} custom imports preserved)`);
|
|
747
753
|
|
|
748
|
-
//
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
754
|
+
// fixtures.ts is your adapter wiring (custom auth, roles, cleanup rules): keep
|
|
755
|
+
// it, and leave the current template in the backup folder for comparison.
|
|
756
|
+
if (existingFixturesTs) {
|
|
757
|
+
console.log(` fixtures.ts: kept (template saved as ${path.join(backupDir, 'steps', 'fixtures.ts.template')})`);
|
|
758
|
+
} else {
|
|
759
|
+
filesToUpdate['features/steps/fixtures.ts'] = mergeFixturesTs(
|
|
760
|
+
existingFixturesTs,
|
|
761
|
+
templates['features/steps/fixtures.ts'],
|
|
762
|
+
cleanupRules
|
|
763
|
+
);
|
|
764
|
+
console.log(' fixtures.ts: created');
|
|
765
|
+
}
|
|
755
766
|
console.log('');
|
|
756
767
|
|
|
757
768
|
// =========================================================================
|
|
@@ -145,13 +145,34 @@ function registerApiAssertionSteps(test) {
|
|
|
145
145
|
|
|
146
146
|
// src/steps/api.auth.ts
|
|
147
147
|
import { createBdd as createBdd3 } from "playwright-bdd";
|
|
148
|
+
|
|
149
|
+
// src/auth/login-steps.ts
|
|
150
|
+
async function apiLoginAsRole(auth, world, role) {
|
|
151
|
+
const name = interpolate(role, world.vars);
|
|
152
|
+
if (auth.apiLoginAs) return auth.apiLoginAs(world, name);
|
|
153
|
+
if (name === "admin") return auth.apiLoginAsAdmin(world);
|
|
154
|
+
if (name === "user") return auth.apiLoginAsUser(world);
|
|
155
|
+
throw new Error(`Your auth adapter doesn't implement apiLoginAs(world, role), so it can't log in as "${name}".`);
|
|
156
|
+
}
|
|
157
|
+
async function uiLoginAsRole(auth, world, role, options) {
|
|
158
|
+
const name = interpolate(role, world.vars);
|
|
159
|
+
if (auth.uiLoginAs) return auth.uiLoginAs(world, name, options);
|
|
160
|
+
if (name === "admin") return auth.uiLoginAsAdmin(world);
|
|
161
|
+
if (name === "user") return auth.uiLoginAsUser(world);
|
|
162
|
+
throw new Error(`Your auth adapter doesn't implement uiLoginAs(world, role), so it can't log in as "${name}".`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/steps/api.auth.ts
|
|
148
166
|
function registerApiAuthSteps(test) {
|
|
149
167
|
const { Given } = createBdd3(test);
|
|
168
|
+
Given("I am authenticated as {string} via API", async ({ auth, world }, role) => {
|
|
169
|
+
await apiLoginAsRole(auth, world, role);
|
|
170
|
+
});
|
|
150
171
|
Given("I am authenticated as an admin via API", async ({ auth, world }) => {
|
|
151
|
-
await auth
|
|
172
|
+
await apiLoginAsRole(auth, world, "admin");
|
|
152
173
|
});
|
|
153
174
|
Given("I am authenticated as a user via API", async ({ auth, world }) => {
|
|
154
|
-
await auth
|
|
175
|
+
await apiLoginAsRole(auth, world, "user");
|
|
155
176
|
});
|
|
156
177
|
Given("I set bearer token from variable {string}", async ({ auth, world }, varName) => {
|
|
157
178
|
const token = world.vars[varName];
|
|
@@ -353,11 +374,21 @@ function registerUiBasicSteps(test) {
|
|
|
353
374
|
When("I fill in {string} with {string}", async ({ ui, world }, label, value) => {
|
|
354
375
|
await ui.fillLabel(interpolate(label, world.vars), interpolate(value, world.vars));
|
|
355
376
|
});
|
|
356
|
-
|
|
357
|
-
|
|
377
|
+
Given("I am logged in as {string}", async ({ auth, ui, world }, role) => {
|
|
378
|
+
void ui;
|
|
379
|
+
await uiLoginAsRole(auth, world, role, { reuseSession: true });
|
|
380
|
+
});
|
|
381
|
+
When("I log in as {string} in UI", async ({ auth, ui, world }, role) => {
|
|
382
|
+
void ui;
|
|
383
|
+
await uiLoginAsRole(auth, world, role);
|
|
384
|
+
});
|
|
385
|
+
When("I log in as admin in UI", async ({ auth, ui, world }) => {
|
|
386
|
+
void ui;
|
|
387
|
+
await uiLoginAsRole(auth, world, "admin");
|
|
358
388
|
});
|
|
359
|
-
When("I log in as user in UI", async ({ auth, world }) => {
|
|
360
|
-
|
|
389
|
+
When("I log in as user in UI", async ({ auth, ui, world }) => {
|
|
390
|
+
void ui;
|
|
391
|
+
await uiLoginAsRole(auth, world, "user");
|
|
361
392
|
});
|
|
362
393
|
When("I click the element {string}", async ({ page, world }, selector) => {
|
|
363
394
|
await page.locator(interpolate(selector, world.vars)).click();
|
|
@@ -1528,6 +1559,8 @@ export {
|
|
|
1528
1559
|
setupBearerAuth,
|
|
1529
1560
|
registerApiHttpSteps,
|
|
1530
1561
|
registerApiAssertionSteps,
|
|
1562
|
+
apiLoginAsRole,
|
|
1563
|
+
uiLoginAsRole,
|
|
1531
1564
|
registerApiAuthSteps,
|
|
1532
1565
|
registerHybridSteps,
|
|
1533
1566
|
registerSharedCleanupSteps,
|
package/dist/index.d.ts
CHANGED
|
@@ -68,14 +68,51 @@ interface UiPort {
|
|
|
68
68
|
expectElementWithTextVisible(elementType: string, text: string, shouldBeVisible: boolean): Promise<void>;
|
|
69
69
|
expectElementState(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState): Promise<void>;
|
|
70
70
|
expectElementStateWithin(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState, seconds: number): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Fill a field found by (in order) exact label, exact placeholder, label,
|
|
73
|
+
* placeholder, or name attribute. Resolves false if none appears in time.
|
|
74
|
+
*/
|
|
75
|
+
fillField?(name: string, value: string, options?: {
|
|
76
|
+
timeoutMs?: number;
|
|
77
|
+
}): Promise<boolean>;
|
|
78
|
+
/** Wait until the URL satisfies `predicate`; resolves false on timeout. */
|
|
79
|
+
waitForUrl?(predicate: (url: string) => boolean, timeoutMs: number): Promise<boolean>;
|
|
80
|
+
/** Wait until `text` is visible; resolves false on timeout. */
|
|
81
|
+
waitForText?(text: string, timeoutMs: number): Promise<boolean>;
|
|
82
|
+
/** Snapshot cookies + localStorage (Playwright storageState). */
|
|
83
|
+
saveSession?(): Promise<UiSessionState>;
|
|
84
|
+
/** Apply a snapshot from saveSession() to the current browser context. */
|
|
85
|
+
restoreSession?(state: UiSessionState): Promise<void>;
|
|
71
86
|
}
|
|
87
|
+
/** Opaque browser session snapshot (Playwright `storageState()` shape). */
|
|
88
|
+
type UiSessionState = {
|
|
89
|
+
cookies: Array<Record<string, unknown>>;
|
|
90
|
+
origins: Array<{
|
|
91
|
+
origin: string;
|
|
92
|
+
localStorage: Array<{
|
|
93
|
+
name: string;
|
|
94
|
+
value: string;
|
|
95
|
+
}>;
|
|
96
|
+
}>;
|
|
97
|
+
};
|
|
72
98
|
|
|
99
|
+
type UiLoginOptions = {
|
|
100
|
+
/** Log in through the form once per role (per worker), then restore the saved session. */
|
|
101
|
+
reuseSession?: boolean;
|
|
102
|
+
};
|
|
73
103
|
interface AuthPort {
|
|
74
104
|
apiLoginAsAdmin(world: World): Promise<void>;
|
|
75
105
|
apiLoginAsUser(world: World): Promise<void>;
|
|
76
106
|
apiSetBearer(world: World, token: string): void;
|
|
77
107
|
uiLoginAsAdmin(world: World): Promise<void>;
|
|
78
108
|
uiLoginAsUser(world: World): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Log in to the API as any named role. Optional so pre-0.8 custom adapters
|
|
111
|
+
* keep compiling; the built-in steps fall back to the admin/user methods.
|
|
112
|
+
*/
|
|
113
|
+
apiLoginAs?(world: World, role: string): Promise<void>;
|
|
114
|
+
/** Log in through the UI as any named role. */
|
|
115
|
+
uiLoginAs?(world: World, role: string, options?: UiLoginOptions): Promise<void>;
|
|
79
116
|
}
|
|
80
117
|
|
|
81
118
|
interface CleanupPort {
|
|
@@ -363,6 +400,10 @@ type TuiFactory = () => TuiPort | undefined;
|
|
|
363
400
|
type CreateBddTestOptions = {
|
|
364
401
|
createApi?: (ctx: CreateContext) => ApiPort;
|
|
365
402
|
createUi?: (ctx: CreateContext) => UiPort;
|
|
403
|
+
/**
|
|
404
|
+
* Build the auth adapter. `ui` is available to UI login methods once a step
|
|
405
|
+
* requests the `ui` fixture; API login never starts a browser.
|
|
406
|
+
*/
|
|
366
407
|
createAuth?: (ctx: CreateContext & {
|
|
367
408
|
api: ApiPort;
|
|
368
409
|
ui: UiPort;
|
|
@@ -407,6 +448,9 @@ declare function createBddTest(options?: CreateBddTestOptions): {
|
|
|
407
448
|
cleanup: CleanupPort;
|
|
408
449
|
tui: TuiPort | undefined;
|
|
409
450
|
apiRequest: APIRequestContext;
|
|
451
|
+
uiBinding: {
|
|
452
|
+
current?: UiPort;
|
|
453
|
+
};
|
|
410
454
|
}, PlaywrightWorkerArgs & _playwright_test.PlaywrightWorkerOptions & playwright_bdd.BddWorkerFixtures>;
|
|
411
455
|
expect: _playwright_test.Expect<{}>;
|
|
412
456
|
};
|
|
@@ -458,24 +502,82 @@ declare class PlaywrightUiAdapter implements UiPort {
|
|
|
458
502
|
expectElementStateWithin(ordinal: string, text: string, method: UiLocatorMethod, state: UiElementState, seconds: number): Promise<void>;
|
|
459
503
|
private parseOrdinal;
|
|
460
504
|
private locatorBy;
|
|
505
|
+
fillField(name: string, value: string, options?: {
|
|
506
|
+
timeoutMs?: number;
|
|
507
|
+
}): Promise<boolean>;
|
|
508
|
+
waitForUrl(predicate: (url: string) => boolean, timeoutMs: number): Promise<boolean>;
|
|
509
|
+
waitForText(text: string, timeoutMs: number): Promise<boolean>;
|
|
510
|
+
saveSession(): Promise<UiSessionState>;
|
|
511
|
+
restoreSession(state: UiSessionState): Promise<void>;
|
|
461
512
|
private performClick;
|
|
462
513
|
private expectState;
|
|
463
514
|
private assertUrlAgainst;
|
|
464
515
|
}
|
|
465
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Login credentials by role.
|
|
519
|
+
*
|
|
520
|
+
* Any role name works: "pm" reads AUTH_PM_USERNAME / AUTH_PM_PASSWORD,
|
|
521
|
+
* "project manager" reads AUTH_PROJECT_MANAGER_USERNAME / ..._PASSWORD.
|
|
522
|
+
* Roles passed in code (UniversalAuthAdapter `roles` option) take precedence.
|
|
523
|
+
* "admin" and "user" also accept the pre-0.8 DEFAULT_* / NON_ADMIN_* names.
|
|
524
|
+
*/
|
|
525
|
+
type Env$3 = Record<string, string | undefined>;
|
|
526
|
+
type Credentials = {
|
|
527
|
+
username: string;
|
|
528
|
+
password: string;
|
|
529
|
+
};
|
|
530
|
+
type RoleCredentials = Record<string, Partial<Credentials>>;
|
|
531
|
+
declare class MissingCredentialsError extends Error {
|
|
532
|
+
constructor(message: string);
|
|
533
|
+
}
|
|
534
|
+
declare function roleEnvKeys(role: string): {
|
|
535
|
+
username: string;
|
|
536
|
+
password: string;
|
|
537
|
+
};
|
|
538
|
+
declare function resolveCredentials(role: string, { env, roles }?: {
|
|
539
|
+
env?: Env$3;
|
|
540
|
+
roles?: RoleCredentials;
|
|
541
|
+
}): Credentials;
|
|
542
|
+
|
|
543
|
+
type Env$2 = Record<string, string | undefined>;
|
|
544
|
+
type UniversalAuthOptions = {
|
|
545
|
+
api: ApiPort;
|
|
546
|
+
ui: UiPort;
|
|
547
|
+
/** Credentials per role, e.g. { pm: { username, password } }. Overrides AUTH_<ROLE>_* env vars. */
|
|
548
|
+
roles?: RoleCredentials;
|
|
549
|
+
/** Settings source; defaults to process.env. */
|
|
550
|
+
env?: Env$2;
|
|
551
|
+
};
|
|
552
|
+
/** Forget saved UI sessions (e.g. after changing a password mid-run). */
|
|
553
|
+
declare function clearUiSessions(): void;
|
|
554
|
+
/**
|
|
555
|
+
* Logs in by role through the API or the UI, configured by environment
|
|
556
|
+
* variables (see docs/guides/authentication.md).
|
|
557
|
+
*
|
|
558
|
+
* Extend it by subclassing and overriding `apiLogin` / `uiLogin` (e.g. for SSO
|
|
559
|
+
* or a multi-step form); credentials, steps and session reuse keep working.
|
|
560
|
+
*/
|
|
466
561
|
declare class UniversalAuthAdapter implements AuthPort {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
562
|
+
protected readonly api: ApiPort;
|
|
563
|
+
protected readonly ui: UiPort;
|
|
564
|
+
protected readonly roles?: RoleCredentials;
|
|
565
|
+
protected readonly env: Env$2;
|
|
566
|
+
constructor(options: UniversalAuthOptions);
|
|
567
|
+
/** Credentials for a role; throws MissingCredentialsError with the variables to set. */
|
|
568
|
+
credentialsFor(role: string): Credentials;
|
|
472
569
|
apiSetBearer(world: World, token: string): void;
|
|
570
|
+
apiLoginAs(world: World, role: string): Promise<void>;
|
|
571
|
+
uiLoginAs(world: World, role: string, options?: UiLoginOptions): Promise<void>;
|
|
473
572
|
apiLoginAsAdmin(world: World): Promise<void>;
|
|
474
573
|
apiLoginAsUser(world: World): Promise<void>;
|
|
475
|
-
private apiLogin;
|
|
476
574
|
uiLoginAsAdmin(world: World): Promise<void>;
|
|
477
575
|
uiLoginAsUser(world: World): Promise<void>;
|
|
478
|
-
|
|
576
|
+
/** POST credentials to the login endpoint; keep the bearer token or session cookie. */
|
|
577
|
+
protected apiLogin(world: World, role: string, creds: Credentials): Promise<void>;
|
|
578
|
+
/** Fill and submit the login form, then confirm the login worked. */
|
|
579
|
+
protected uiLogin(_world: World, role: string, creds: Credentials): Promise<void>;
|
|
580
|
+
private sessionReuseEnabled;
|
|
479
581
|
}
|
|
480
582
|
|
|
481
583
|
type CleanupRule = {
|
|
@@ -881,4 +983,54 @@ type ApiRequestTarget = {
|
|
|
881
983
|
};
|
|
882
984
|
declare function resolveApiRequestTarget(baseURL: string, env?: Record<string, string | undefined>): ApiRequestTarget;
|
|
883
985
|
|
|
884
|
-
|
|
986
|
+
/**
|
|
987
|
+
* Where tests point: the frontend (UI) and the API.
|
|
988
|
+
*
|
|
989
|
+
* Canonical variables are FRONTEND_URL and API_BASE_URL. BASE_URL and
|
|
990
|
+
* TARGET_BASE_URL are accepted as older aliases. When no API URL is set, API
|
|
991
|
+
* requests go to the frontend URL, so same-origin apps (`/api/...`) and
|
|
992
|
+
* scenarios that mix API and UI steps work with a single setting.
|
|
993
|
+
*/
|
|
994
|
+
type Env$1 = Record<string, string | undefined>;
|
|
995
|
+
type Targets = {
|
|
996
|
+
frontendUrl: string;
|
|
997
|
+
frontendSource: 'FRONTEND_URL' | 'BASE_URL' | 'default';
|
|
998
|
+
apiBaseUrl: string;
|
|
999
|
+
apiSource: 'API_BASE_URL' | 'TARGET_BASE_URL' | 'TARGET_PORT' | 'FRONTEND_URL' | 'default';
|
|
1000
|
+
};
|
|
1001
|
+
/** Resolve the frontend and API targets from environment variables. */
|
|
1002
|
+
declare function resolveTargets(env?: Env$1): Targets;
|
|
1003
|
+
/**
|
|
1004
|
+
* Base URL for the API request context in a given Playwright project.
|
|
1005
|
+
* Order: API_BASE_URL > TARGET_BASE_URL > baseURL of a project named like
|
|
1006
|
+
* "api" > TARGET_PORT > the project's baseURL (any project) > localhost:3000.
|
|
1007
|
+
*/
|
|
1008
|
+
declare function resolveApiBaseUrl({ env, projectName, projectBaseURL, }: {
|
|
1009
|
+
env?: Env$1;
|
|
1010
|
+
projectName?: string;
|
|
1011
|
+
projectBaseURL?: string;
|
|
1012
|
+
}): string;
|
|
1013
|
+
declare function formatTargets(t: Targets): string;
|
|
1014
|
+
/** Only the main `playwright test` process logs (not workers, not bddgen). */
|
|
1015
|
+
declare function shouldLogTargets(env?: Env$1, argv?: string[]): boolean;
|
|
1016
|
+
/**
|
|
1017
|
+
* Print the resolved targets once per run. Call from playwright.config.ts.
|
|
1018
|
+
* Silence with KATALYST_XSPEC_QUIET=true.
|
|
1019
|
+
*/
|
|
1020
|
+
declare function logTargets(targets?: Targets): Targets;
|
|
1021
|
+
|
|
1022
|
+
type Env = Record<string, string | undefined>;
|
|
1023
|
+
type ApiLoginRequest = {
|
|
1024
|
+
path: string;
|
|
1025
|
+
body: 'form' | 'json';
|
|
1026
|
+
fields: Record<string, string>;
|
|
1027
|
+
};
|
|
1028
|
+
declare function buildApiLoginRequest(creds: Credentials, env?: Env): ApiLoginRequest;
|
|
1029
|
+
declare function extractToken(json: unknown, env?: Env): string | undefined;
|
|
1030
|
+
|
|
1031
|
+
/** Log in to the API as a role, supporting pre-0.8 adapters that only know admin/user. */
|
|
1032
|
+
declare function apiLoginAsRole(auth: AuthPort, world: World, role: string): Promise<void>;
|
|
1033
|
+
/** Log in through the UI as a role, supporting pre-0.8 adapters that only know admin/user. */
|
|
1034
|
+
declare function uiLoginAsRole(auth: AuthPort, world: World, role: string, options?: UiLoginOptions): Promise<void>;
|
|
1035
|
+
|
|
1036
|
+
export { type ApiLoginRequest, type ApiMethod, type ApiPort, type ApiRequestTarget, type ApiResult, type AuthPort, type CleanupAuthProvider, type CleanupItem, type CleanupPort, type CleanupRule, type CreateBddTestOptions, type Credentials, DefaultCleanupAdapter, type FetchInterceptAuthData, type FetchInterceptConfig, MissingCredentialsError, type OidcCleanupAuthConfig, PlaywrightApiAdapter, PlaywrightUiAdapter, type ResolveFeaturesOptions, type ResolveStepsOptions, type ResolveWorkersOptions, type RoleCredentials, type Targets, 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 UiLoginOptions, type UiPort, type UiSessionState, type UiUrlAssertMode, UniversalAuthAdapter, type UniversalAuthOptions, type World, apiLoginAsRole, assertMasked, buildApiLoginRequest, clearFetchIntercept, clearUiSessions, createBddTest, createOidcCleanupAuth, defaultBearerConfig, defaultBypassConfig, extractToken, formatTargets, getCpuCount, initWorld, interpolate, logTargets, parseExpected, registerCleanup, resetOidcCleanupAuth, resolveApiBaseUrl, resolveApiRequestTarget, resolveBddPaths, resolveCredentials, resolveExtraTags, resolveFeatures, resolveSteps, resolveTargets, resolveWorkers, roleEnvKeys, selectPath, setupBearerAuth, setupBypassAuth, setupFetchIntercept, shouldLogTargets, tagsForProject, tryParseJson, uiLoginAsRole };
|