@zitadel/testing 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/app-env-D3W0GYhA.d.mts +122 -0
- package/dist/app-env-D3W0GYhA.d.mts.map +1 -0
- package/dist/app-runner.cjs +36 -0
- package/dist/app-runner.cjs.map +1 -0
- package/dist/app-runner.d.mts +1 -0
- package/dist/app-runner.mjs +37 -0
- package/dist/app-runner.mjs.map +1 -0
- package/dist/handshake-BPtWruO8.mjs +69 -0
- package/dist/handshake-BPtWruO8.mjs.map +1 -0
- package/dist/handshake-CggSIoxF.cjs +98 -0
- package/dist/handshake-CggSIoxF.cjs.map +1 -0
- package/dist/index.cjs +13 -0
- package/dist/index.d.mts +93 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/orchestration-C640_1Lg.mjs +38 -0
- package/dist/orchestration-C640_1Lg.mjs.map +1 -0
- package/dist/orchestration-D7QBgQ0l.cjs +73 -0
- package/dist/orchestration-D7QBgQ0l.cjs.map +1 -0
- package/dist/playwright.cjs +156 -0
- package/dist/playwright.cjs.map +1 -0
- package/dist/playwright.d.mts +124 -0
- package/dist/playwright.d.mts.map +1 -0
- package/dist/playwright.mjs +146 -0
- package/dist/playwright.mjs.map +1 -0
- package/dist/src-BIL0PdSd.cjs +2356 -0
- package/dist/src-BIL0PdSd.cjs.map +1 -0
- package/dist/src-DkG0V4A6.mjs +515 -0
- package/dist/src-DkG0V4A6.mjs.map +1 -0
- package/dist/supervisor.cjs +76 -0
- package/dist/supervisor.cjs.map +1 -0
- package/dist/supervisor.d.mts +1 -0
- package/dist/supervisor.mjs +77 -0
- package/dist/supervisor.mjs.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { a as Identity, d as SeedUserInput, f as SeedUsersTemplate, i as ConnectedZitadel, l as MintedSession, n as applyAppEnvTemplate, o as InstanceHandle, p as SeededUser, r as nextAppEnv, t as AppEnvTemplate, u as SeedSessionInput } from "./app-env-D3W0GYhA.mjs";
|
|
2
|
+
import { SetupPreset, SetupUseCase } from "@zitadel/config/defaults";
|
|
3
|
+
import * as _$_playwright_test0 from "@playwright/test";
|
|
4
|
+
import { Page, PlaywrightTestConfig, expect } from "@playwright/test";
|
|
5
|
+
|
|
6
|
+
//#region src/orchestration.d.ts
|
|
7
|
+
interface SupervisorConfig {
|
|
8
|
+
/** Fixed port: the config process must know the health URL up front. */
|
|
9
|
+
port: number;
|
|
10
|
+
appOrigins?: string[];
|
|
11
|
+
serverBinary?: string;
|
|
12
|
+
/** Appended to the missing-binary error, e.g. how to build it. */
|
|
13
|
+
serverBinaryHint?: string;
|
|
14
|
+
dir?: string;
|
|
15
|
+
keep?: boolean;
|
|
16
|
+
projectName?: string;
|
|
17
|
+
preset?: SetupPreset;
|
|
18
|
+
useCase?: SetupUseCase;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/playwright-config.d.ts
|
|
23
|
+
type WebServerEntry = Extract<NonNullable<PlaywrightTestConfig["webServer"]>, readonly unknown[]>[number];
|
|
24
|
+
interface WithZitadelOptions {
|
|
25
|
+
/**
|
|
26
|
+
* The Playwright config's directory (`import.meta.dirname`). Anchors the
|
|
27
|
+
* default handshake location and the working directory of the generated
|
|
28
|
+
* webServer entries.
|
|
29
|
+
*/
|
|
30
|
+
configDir: string;
|
|
31
|
+
/**
|
|
32
|
+
* Fixed TCP port for the instance. Required (unlike `startLocalZitadel`,
|
|
33
|
+
* which defaults to a free port) because Playwright's readiness URL must be
|
|
34
|
+
* known while the config is evaluated, before anything boots.
|
|
35
|
+
*/
|
|
36
|
+
port: number;
|
|
37
|
+
/**
|
|
38
|
+
* Origin the browser will use for the app under test. Registered as the
|
|
39
|
+
* project's preview origin (the backend's origin check rejects forwarded
|
|
40
|
+
* requests from unregistered origins) and the base of `app.readyPath`.
|
|
41
|
+
*/
|
|
42
|
+
appOrigin: string;
|
|
43
|
+
/** Boot/bootstrap options forwarded to the instance supervisor. */
|
|
44
|
+
zitadel?: {
|
|
45
|
+
/** Absolute path to the server binary. */serverBinary?: string; /** Appended to the missing-binary error, e.g. "run `moon run server:build` first." */
|
|
46
|
+
serverBinaryHint?: string;
|
|
47
|
+
projectName?: string;
|
|
48
|
+
preset?: SupervisorConfig["preset"];
|
|
49
|
+
useCase?: SupervisorConfig["useCase"]; /** Absolute state directory; defaults to a fresh temp dir removed on stop. */
|
|
50
|
+
dir?: string; /** Keep the owned temp dir after stop (debugging). */
|
|
51
|
+
keep?: boolean; /** webServer readiness timeout for the boot; cold boot dominates it. */
|
|
52
|
+
bootTimeoutMs?: number;
|
|
53
|
+
};
|
|
54
|
+
/** The app dev server to run against the instance. */
|
|
55
|
+
app: {
|
|
56
|
+
/** Spawn argv (no shell), e.g. ["corepack", "pnpm", "--filter", "my-app", "dev"]. */command: string[]; /** Working directory for the app command. */
|
|
57
|
+
cwd: string; /** Path on `appOrigin` Playwright polls for readiness, e.g. "/login". */
|
|
58
|
+
readyPath: string;
|
|
59
|
+
/**
|
|
60
|
+
* Env vars the app needs, as a template mapping env names to
|
|
61
|
+
* InstanceHandle fields — see `nextAppEnv` for the `@zitadel/sdk-next`
|
|
62
|
+
* shape. A template (not a callback) because it crosses into the app
|
|
63
|
+
* runner process.
|
|
64
|
+
*/
|
|
65
|
+
env: AppEnvTemplate;
|
|
66
|
+
readyTimeoutMs?: number; /** How long SIGTERM gets before the app is killed on teardown. */
|
|
67
|
+
gracefulShutdownMs?: number;
|
|
68
|
+
};
|
|
69
|
+
/** Absolute path; defaults to `<configDir>/.zitadel-testing/handshake.json`. */
|
|
70
|
+
handshakePath?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Generate the Playwright `webServer` entries that boot an ephemeral seeded
|
|
74
|
+
* Zitadel and run the app against it, replacing the per-suite wrapper
|
|
75
|
+
* scripts. Spread the result into `defineConfig`:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* export default defineConfig({
|
|
79
|
+
* ...withZitadel({ configDir: import.meta.dirname, port: 8092, ... }),
|
|
80
|
+
* testDir: "./src-real",
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the
|
|
85
|
+
* `@zitadel/testing/playwright` fixtures resolve the instance — Playwright
|
|
86
|
+
* workers re-evaluate the config, which re-applies this for every process
|
|
87
|
+
* that needs it. The returned value is plain data; append your own entries
|
|
88
|
+
* to `webServer` if the suite needs additional servers.
|
|
89
|
+
*/
|
|
90
|
+
declare function withZitadel(options: WithZitadelOptions, /** Test seam: alternative executable resolution. */resolveEntry?: (name: "supervisor" | "app-runner") => string): {
|
|
91
|
+
webServer: WebServerEntry[];
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/playwright.d.ts
|
|
95
|
+
interface AuthenticatedPage {
|
|
96
|
+
/** A page in its own context, already carrying the session cookie. */
|
|
97
|
+
page: Page;
|
|
98
|
+
user: SeededUser;
|
|
99
|
+
session: MintedSession;
|
|
100
|
+
}
|
|
101
|
+
interface ZitadelTestFixtures {
|
|
102
|
+
/** Per-test seeding; each call mints unique data on the shared instance. */
|
|
103
|
+
seed: {
|
|
104
|
+
user(input?: SeedUserInput): Promise<SeededUser>;
|
|
105
|
+
users(count: number, template?: SeedUsersTemplate): Promise<SeededUser[]>; /** Unused email+password for registration flows — creates nothing. */
|
|
106
|
+
identity(): Identity; /** Seeded user + headless real-flow login; password flows only. */
|
|
107
|
+
session(input?: SeedSessionInput): Promise<MintedSession>;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Start the test authenticated: a fresh user, a real session minted through
|
|
111
|
+
* the flow API, and the cookie injected into a dedicated browser context —
|
|
112
|
+
* the default `page` stays signed out for login-flow tests. Requires
|
|
113
|
+
* `use.baseURL` (every withZitadel consumer sets it).
|
|
114
|
+
*/
|
|
115
|
+
authenticatedPage: AuthenticatedPage;
|
|
116
|
+
}
|
|
117
|
+
interface ZitadelWorkerFixtures {
|
|
118
|
+
/** Connection to the suite's instance, resolved once per worker. */
|
|
119
|
+
zitadel: ConnectedZitadel;
|
|
120
|
+
}
|
|
121
|
+
declare const test: _$_playwright_test0.TestType<_$_playwright_test0.PlaywrightTestArgs & _$_playwright_test0.PlaywrightTestOptions & ZitadelTestFixtures, _$_playwright_test0.PlaywrightWorkerArgs & _$_playwright_test0.PlaywrightWorkerOptions & ZitadelWorkerFixtures>;
|
|
122
|
+
//#endregion
|
|
123
|
+
export { type AppEnvTemplate, AuthenticatedPage, type ConnectedZitadel, type Identity, type InstanceHandle, type MintedSession, type SeedSessionInput, type SeedUserInput, type SeedUsersTemplate, type SeededUser, type WithZitadelOptions, ZitadelTestFixtures, ZitadelWorkerFixtures, applyAppEnvTemplate, expect, nextAppEnv, test, withZitadel };
|
|
124
|
+
//# sourceMappingURL=playwright.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright.d.mts","names":[],"sources":["../src/orchestration.ts","../src/playwright-config.ts","../src/playwright.ts"],"mappings":";;;;;;UAeiB,gBAAA;EAMf;EAJA,IAAA;EACA,UAAA;EACA,YAAA;EAMA;EAJA,gBAAA;EACA,GAAA;EACA,IAAA;EACA,WAAA;EACA,MAAA,GAAS,WAAA;EACT,OAAA,GAAU,YAAA;EACV,SAAA;AAAA;;;KCZG,cAAA,GAAiB,OAAA,CACpB,WAAA,CAAY,oBAAA;AAAA,UAIG,kBAAA;;ADLjB;;;;ECWE,SAAA;EDRA;;;;;ECcA,IAAA;EDPA;;;;;ECaA,SAAA;EDXS;ECaT,OAAA;8CAEE,YAAA,WA3BC;IA6BD,gBAAA;IACA,WAAA;IACA,MAAA,GAAS,gBAAA;IACT,OAAA,GAAU,gBAAA,aAhCQ;IAkClB,GAAA,WAlCyB;IAoCzB,IAAA,YAnCF;IAqCE,aAAA;EAAA;EArC8B;EAwChC,GAAA;IApCiC,qFAsC/B,OAAA,YAZS;IAcT,GAAA,UASK;IAPL,SAAA;IAOmB;;;;;;IAAnB,GAAA,EAAK,cAAA;IACL,cAAA,WAxBA;IA0BA,kBAAA;EAAA;EAzBU;EA4BZ,aAAA;AAAA;;;;;;;;;;;;;AAqBF;;;;;;iBAAgB,WAAA,CACd,OAAA,EAAS,kBAAA,sDAET,YAAA,IAAe,IAAA;EACZ,SAAA,EAAW,cAAA;AAAA;;;UCtFC,iBAAA;;EAEf,IAAA,EAAM,IAAA;EACN,IAAA,EAAM,UAAA;EACN,OAAA,EAAS,aAAA;AAAA;AAAA,UAGM,mBAAA;EFKO;EEHtB,IAAA;IACE,IAAA,CAAK,KAAA,GAAQ,aAAA,GAAgB,OAAA,CAAQ,UAAA;IACrC,KAAA,CAAM,KAAA,UAAe,QAAA,GAAW,iBAAA,GAAoB,OAAA,CAAQ,UAAA,KFJ9D;IEME,QAAA,IAAY,QAAA,EFJd;IEME,OAAA,CAAQ,KAAA,GAAQ,gBAAA,GAAmB,OAAA,CAAQ,aAAA;EAAA;EFJpC;;;;;;EEYT,iBAAA,EAAmB,iBAAA;AAAA;AAAA,UAGJ,qBAAA;EDzBZ;EC2BH,OAAA,EAAS,gBAAA;AAAA;AAAA,cAGE,IAAA,EAAI,mBAAA,CAAA,QAAA,CA0Cf,mBAAA,CA1Ce,kBAAA,GAAA,mBAAA,CAAA,qBAAA,GAAA,mBAAA,EAAA,mBAAA,CAAA,oBAAA,GAAA,mBAAA,CAAA,uBAAA,GAAA,qBAAA"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { a as nextAppEnv, i as applyAppEnvTemplate, t as readHandshakeSync } from "./handshake-BPtWruO8.mjs";
|
|
2
|
+
import { t as connectZitadel } from "./src-DkG0V4A6.mjs";
|
|
3
|
+
import { n as HANDSHAKE_ENV, r as SUPERVISOR_CONFIG_ENV, t as APP_RUNNER_CONFIG_ENV } from "./orchestration-C640_1Lg.mjs";
|
|
4
|
+
import { extname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { expect, test as test$1 } from "@playwright/test";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
//#region src/playwright-config.ts
|
|
9
|
+
/**
|
|
10
|
+
* Generate the Playwright `webServer` entries that boot an ephemeral seeded
|
|
11
|
+
* Zitadel and run the app against it, replacing the per-suite wrapper
|
|
12
|
+
* scripts. Spread the result into `defineConfig`:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* export default defineConfig({
|
|
16
|
+
* ...withZitadel({ configDir: import.meta.dirname, port: 8092, ... }),
|
|
17
|
+
* testDir: "./src-real",
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the
|
|
22
|
+
* `@zitadel/testing/playwright` fixtures resolve the instance — Playwright
|
|
23
|
+
* workers re-evaluate the config, which re-applies this for every process
|
|
24
|
+
* that needs it. The returned value is plain data; append your own entries
|
|
25
|
+
* to `webServer` if the suite needs additional servers.
|
|
26
|
+
*/
|
|
27
|
+
function withZitadel(options, resolveEntry = entryPoint) {
|
|
28
|
+
const { configDir, port, appOrigin, app } = options;
|
|
29
|
+
if (!isAbsolute(configDir)) throw new Error(`withZitadel: configDir must be absolute, got "${configDir}"`);
|
|
30
|
+
if (!Number.isInteger(port) || port <= 0) throw new Error(`withZitadel: port must be a positive integer, got ${port}`);
|
|
31
|
+
const origin = URL.canParse(appOrigin) ? new URL(appOrigin) : void 0;
|
|
32
|
+
if (!origin || origin.protocol !== "http:" && origin.protocol !== "https:" || origin.pathname !== "/" || origin.search !== "" || origin.hash !== "") throw new Error(`withZitadel: appOrigin must be an origin like "http://localhost:3002", got "${appOrigin}"`);
|
|
33
|
+
if (!app.readyPath.startsWith("/")) throw new Error(`withZitadel: app.readyPath must start with "/", got "${app.readyPath}"`);
|
|
34
|
+
if (app.command.length === 0) throw new Error("withZitadel: app.command must not be empty");
|
|
35
|
+
if (!isAbsolute(app.cwd)) throw new Error(`withZitadel: app.cwd must be absolute, got "${app.cwd}"`);
|
|
36
|
+
for (const [label, value] of [
|
|
37
|
+
["zitadel.serverBinary", options.zitadel?.serverBinary],
|
|
38
|
+
["zitadel.dir", options.zitadel?.dir],
|
|
39
|
+
["handshakePath", options.handshakePath]
|
|
40
|
+
]) if (value !== void 0 && !isAbsolute(value)) throw new Error(`withZitadel: ${label} must be an absolute path, got "${value}"`);
|
|
41
|
+
const handshakePath = options.handshakePath ?? join(configDir, ".zitadel-testing", "handshake.json");
|
|
42
|
+
process.env[HANDSHAKE_ENV] = handshakePath;
|
|
43
|
+
const supervisorConfig = {
|
|
44
|
+
port,
|
|
45
|
+
appOrigins: [appOrigin],
|
|
46
|
+
serverBinary: options.zitadel?.serverBinary,
|
|
47
|
+
serverBinaryHint: options.zitadel?.serverBinaryHint,
|
|
48
|
+
dir: options.zitadel?.dir,
|
|
49
|
+
keep: options.zitadel?.keep,
|
|
50
|
+
projectName: options.zitadel?.projectName,
|
|
51
|
+
preset: options.zitadel?.preset,
|
|
52
|
+
useCase: options.zitadel?.useCase
|
|
53
|
+
};
|
|
54
|
+
const appRunnerConfig = {
|
|
55
|
+
command: app.command,
|
|
56
|
+
cwd: app.cwd,
|
|
57
|
+
env: app.env,
|
|
58
|
+
handshakeTimeoutMs: app.readyTimeoutMs ?? 18e4
|
|
59
|
+
};
|
|
60
|
+
return { webServer: [{
|
|
61
|
+
command: `node ${JSON.stringify(resolveEntry("supervisor"))}`,
|
|
62
|
+
url: `http://localhost:${port}/healthz`,
|
|
63
|
+
reuseExistingServer: false,
|
|
64
|
+
cwd: configDir,
|
|
65
|
+
stdout: "pipe",
|
|
66
|
+
stderr: "pipe",
|
|
67
|
+
timeout: options.zitadel?.bootTimeoutMs ?? 12e4,
|
|
68
|
+
env: {
|
|
69
|
+
[HANDSHAKE_ENV]: handshakePath,
|
|
70
|
+
[SUPERVISOR_CONFIG_ENV]: JSON.stringify(supervisorConfig)
|
|
71
|
+
},
|
|
72
|
+
gracefulShutdown: {
|
|
73
|
+
signal: "SIGTERM",
|
|
74
|
+
timeout: 3e4
|
|
75
|
+
}
|
|
76
|
+
}, {
|
|
77
|
+
command: `node ${JSON.stringify(resolveEntry("app-runner"))}`,
|
|
78
|
+
url: new URL(app.readyPath, appOrigin).toString(),
|
|
79
|
+
reuseExistingServer: false,
|
|
80
|
+
cwd: configDir,
|
|
81
|
+
stdout: "pipe",
|
|
82
|
+
stderr: "pipe",
|
|
83
|
+
timeout: app.readyTimeoutMs ?? 18e4,
|
|
84
|
+
env: {
|
|
85
|
+
[HANDSHAKE_ENV]: handshakePath,
|
|
86
|
+
[APP_RUNNER_CONFIG_ENV]: JSON.stringify(appRunnerConfig)
|
|
87
|
+
},
|
|
88
|
+
gracefulShutdown: {
|
|
89
|
+
signal: "SIGTERM",
|
|
90
|
+
timeout: app.gracefulShutdownMs ?? 15e3
|
|
91
|
+
}
|
|
92
|
+
}] };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve a sibling dist entry in the same module format this file was loaded
|
|
96
|
+
* as (dist/supervisor.mjs next to dist/playwright.mjs, .cjs next to .cjs), so
|
|
97
|
+
* the spawned process needs no package-manager bin plumbing.
|
|
98
|
+
*/
|
|
99
|
+
function entryPoint(name) {
|
|
100
|
+
const self = fileURLToPath(import.meta.url);
|
|
101
|
+
const ext = extname(self);
|
|
102
|
+
if (ext !== ".mjs" && ext !== ".cjs") throw new Error(`withZitadel: expected to run from the built package (got ${self}); build @zitadel/testing first (in-repo: \`moon run testing:build\`).`);
|
|
103
|
+
const path = fileURLToPath(new URL(`./${name}${ext}`, import.meta.url));
|
|
104
|
+
if (!existsSync(path)) throw new Error(`withZitadel: missing ${path}; rebuild @zitadel/testing (in-repo: \`moon run testing:build\`).`);
|
|
105
|
+
return path;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/playwright.ts
|
|
109
|
+
const test = test$1.extend({
|
|
110
|
+
zitadel: [async ({}, use) => {
|
|
111
|
+
const handshakePath = process.env.ZITADEL_TESTING_HANDSHAKE;
|
|
112
|
+
if (!handshakePath) throw new Error("ZITADEL_TESTING_HANDSHAKE is not set. Point it at the handshake file written by the script that boots the instance (see @zitadel/testing docs).");
|
|
113
|
+
await use(connectZitadel(readHandshakeSync(handshakePath)));
|
|
114
|
+
}, { scope: "worker" }],
|
|
115
|
+
seed: async ({ zitadel, baseURL }, use) => {
|
|
116
|
+
await use({
|
|
117
|
+
user: (input) => zitadel.seedUser(input),
|
|
118
|
+
users: (count, template) => zitadel.seedUsers(count, template),
|
|
119
|
+
identity: () => zitadel.identity(),
|
|
120
|
+
session: (input) => zitadel.seedSession({
|
|
121
|
+
origin: baseURL,
|
|
122
|
+
...input
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
authenticatedPage: async ({ browser, zitadel, baseURL }, use) => {
|
|
127
|
+
if (!baseURL) throw new Error("authenticatedPage requires `use.baseURL` so the session cookie can be scoped to the app under test.");
|
|
128
|
+
const session = await zitadel.seedSession({ origin: baseURL });
|
|
129
|
+
const context = await browser.newContext({ baseURL });
|
|
130
|
+
const { path: _path, ...cookie } = session.cookie;
|
|
131
|
+
await context.addCookies([{
|
|
132
|
+
...cookie,
|
|
133
|
+
url: baseURL
|
|
134
|
+
}]);
|
|
135
|
+
await use({
|
|
136
|
+
page: await context.newPage(),
|
|
137
|
+
user: session.user,
|
|
138
|
+
session
|
|
139
|
+
});
|
|
140
|
+
await context.close();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
//#endregion
|
|
144
|
+
export { applyAppEnvTemplate, expect, nextAppEnv, test, withZitadel };
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=playwright.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright.mjs","names":["base"],"sources":["../src/playwright-config.ts","../src/playwright.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { extname, isAbsolute, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { PlaywrightTestConfig } from \"@playwright/test\";\n\nimport type { AppEnvTemplate } from \"./app-env\";\nimport {\n APP_RUNNER_CONFIG_ENV,\n HANDSHAKE_ENV,\n SUPERVISOR_CONFIG_ENV,\n type AppRunnerConfig,\n type SupervisorConfig,\n} from \"./orchestration\";\n\ntype WebServerEntry = Extract<\n NonNullable<PlaywrightTestConfig[\"webServer\"]>,\n readonly unknown[]\n>[number];\n\nexport interface WithZitadelOptions {\n /**\n * The Playwright config's directory (`import.meta.dirname`). Anchors the\n * default handshake location and the working directory of the generated\n * webServer entries.\n */\n configDir: string;\n /**\n * Fixed TCP port for the instance. Required (unlike `startLocalZitadel`,\n * which defaults to a free port) because Playwright's readiness URL must be\n * known while the config is evaluated, before anything boots.\n */\n port: number;\n /**\n * Origin the browser will use for the app under test. Registered as the\n * project's preview origin (the backend's origin check rejects forwarded\n * requests from unregistered origins) and the base of `app.readyPath`.\n */\n appOrigin: string;\n /** Boot/bootstrap options forwarded to the instance supervisor. */\n zitadel?: {\n /** Absolute path to the server binary. */\n serverBinary?: string;\n /** Appended to the missing-binary error, e.g. \"run `moon run server:build` first.\" */\n serverBinaryHint?: string;\n projectName?: string;\n preset?: SupervisorConfig[\"preset\"];\n useCase?: SupervisorConfig[\"useCase\"];\n /** Absolute state directory; defaults to a fresh temp dir removed on stop. */\n dir?: string;\n /** Keep the owned temp dir after stop (debugging). */\n keep?: boolean;\n /** webServer readiness timeout for the boot; cold boot dominates it. */\n bootTimeoutMs?: number;\n };\n /** The app dev server to run against the instance. */\n app: {\n /** Spawn argv (no shell), e.g. [\"corepack\", \"pnpm\", \"--filter\", \"my-app\", \"dev\"]. */\n command: string[];\n /** Working directory for the app command. */\n cwd: string;\n /** Path on `appOrigin` Playwright polls for readiness, e.g. \"/login\". */\n readyPath: string;\n /**\n * Env vars the app needs, as a template mapping env names to\n * InstanceHandle fields — see `nextAppEnv` for the `@zitadel/sdk-next`\n * shape. A template (not a callback) because it crosses into the app\n * runner process.\n */\n env: AppEnvTemplate;\n readyTimeoutMs?: number;\n /** How long SIGTERM gets before the app is killed on teardown. */\n gracefulShutdownMs?: number;\n };\n /** Absolute path; defaults to `<configDir>/.zitadel-testing/handshake.json`. */\n handshakePath?: string;\n}\n\n/**\n * Generate the Playwright `webServer` entries that boot an ephemeral seeded\n * Zitadel and run the app against it, replacing the per-suite wrapper\n * scripts. Spread the result into `defineConfig`:\n *\n * ```ts\n * export default defineConfig({\n * ...withZitadel({ configDir: import.meta.dirname, port: 8092, ... }),\n * testDir: \"./src-real\",\n * });\n * ```\n *\n * Also points ZITADEL_TESTING_HANDSHAKE at the handshake file so the\n * `@zitadel/testing/playwright` fixtures resolve the instance — Playwright\n * workers re-evaluate the config, which re-applies this for every process\n * that needs it. The returned value is plain data; append your own entries\n * to `webServer` if the suite needs additional servers.\n */\nexport function withZitadel(\n options: WithZitadelOptions,\n /** Test seam: alternative executable resolution. */\n resolveEntry: (name: \"supervisor\" | \"app-runner\") => string = entryPoint,\n): { webServer: WebServerEntry[] } {\n const { configDir, port, appOrigin, app } = options;\n if (!isAbsolute(configDir)) {\n throw new Error(`withZitadel: configDir must be absolute, got \"${configDir}\"`);\n }\n if (!Number.isInteger(port) || port <= 0) {\n throw new Error(`withZitadel: port must be a positive integer, got ${port}`);\n }\n const origin = URL.canParse(appOrigin) ? new URL(appOrigin) : undefined;\n if (\n !origin ||\n (origin.protocol !== \"http:\" && origin.protocol !== \"https:\") ||\n origin.pathname !== \"/\" ||\n origin.search !== \"\" ||\n origin.hash !== \"\"\n ) {\n throw new Error(\n `withZitadel: appOrigin must be an origin like \"http://localhost:3002\", got \"${appOrigin}\"`,\n );\n }\n if (!app.readyPath.startsWith(\"/\")) {\n throw new Error(`withZitadel: app.readyPath must start with \"/\", got \"${app.readyPath}\"`);\n }\n if (app.command.length === 0) {\n throw new Error(\"withZitadel: app.command must not be empty\");\n }\n if (!isAbsolute(app.cwd)) {\n throw new Error(`withZitadel: app.cwd must be absolute, got \"${app.cwd}\"`);\n }\n // Path options are consumed by the executables, whose cwd is configDir —\n // a relative path would silently resolve against that, not the project.\n for (const [label, value] of [\n [\"zitadel.serverBinary\", options.zitadel?.serverBinary],\n [\"zitadel.dir\", options.zitadel?.dir],\n [\"handshakePath\", options.handshakePath],\n ] as const) {\n if (value !== undefined && !isAbsolute(value)) {\n throw new Error(`withZitadel: ${label} must be an absolute path, got \"${value}\"`);\n }\n }\n\n const handshakePath =\n options.handshakePath ?? join(configDir, \".zitadel-testing\", \"handshake.json\");\n // Workers inherit the runner's env; the fixtures resolve the instance from it.\n process.env[HANDSHAKE_ENV] = handshakePath;\n\n const supervisorConfig: SupervisorConfig = {\n port,\n appOrigins: [appOrigin],\n serverBinary: options.zitadel?.serverBinary,\n serverBinaryHint: options.zitadel?.serverBinaryHint,\n dir: options.zitadel?.dir,\n keep: options.zitadel?.keep,\n projectName: options.zitadel?.projectName,\n preset: options.zitadel?.preset,\n useCase: options.zitadel?.useCase,\n };\n const appRunnerConfig: AppRunnerConfig = {\n command: app.command,\n cwd: app.cwd,\n env: app.env,\n handshakeTimeoutMs: app.readyTimeoutMs ?? 180_000,\n };\n\n return {\n webServer: [\n {\n command: `node ${JSON.stringify(resolveEntry(\"supervisor\"))}`,\n url: `http://localhost:${port}/healthz`,\n reuseExistingServer: false,\n cwd: configDir,\n stdout: \"pipe\",\n stderr: \"pipe\",\n // Cold boot (fresh data dir: migrations + health wait) dominates.\n timeout: options.zitadel?.bootTimeoutMs ?? 120_000,\n env: {\n [HANDSHAKE_ENV]: handshakePath,\n [SUPERVISOR_CONFIG_ENV]: JSON.stringify(supervisorConfig),\n },\n // SIGTERM first so the supervisor can stop the instance; the default\n // hard kill would orphan the server process group.\n gracefulShutdown: { signal: \"SIGTERM\", timeout: 30_000 },\n },\n {\n command: `node ${JSON.stringify(resolveEntry(\"app-runner\"))}`,\n url: new URL(app.readyPath, appOrigin).toString(),\n reuseExistingServer: false,\n cwd: configDir,\n stdout: \"pipe\",\n stderr: \"pipe\",\n timeout: app.readyTimeoutMs ?? 180_000,\n env: {\n [HANDSHAKE_ENV]: handshakePath,\n [APP_RUNNER_CONFIG_ENV]: JSON.stringify(appRunnerConfig),\n },\n gracefulShutdown: {\n signal: \"SIGTERM\",\n timeout: app.gracefulShutdownMs ?? 15_000,\n },\n },\n ],\n };\n}\n\n/**\n * Resolve a sibling dist entry in the same module format this file was loaded\n * as (dist/supervisor.mjs next to dist/playwright.mjs, .cjs next to .cjs), so\n * the spawned process needs no package-manager bin plumbing.\n */\nfunction entryPoint(name: \"supervisor\" | \"app-runner\"): string {\n const self = fileURLToPath(import.meta.url);\n const ext = extname(self);\n if (ext !== \".mjs\" && ext !== \".cjs\") {\n throw new Error(\n `withZitadel: expected to run from the built package (got ${self}); ` +\n \"build @zitadel/testing first (in-repo: `moon run testing:build`).\",\n );\n }\n const path = fileURLToPath(new URL(`./${name}${ext}`, import.meta.url));\n if (!existsSync(path)) {\n throw new Error(\n `withZitadel: missing ${path}; rebuild @zitadel/testing (in-repo: \\`moon run testing:build\\`).`,\n );\n }\n return path;\n}\n","import { test as base, type Page } from \"@playwright/test\";\n\nimport { readHandshakeSync } from \"./handshake\";\nimport { connectZitadel } from \"./index\";\nimport type {\n ConnectedZitadel,\n Identity,\n MintedSession,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n} from \"./types\";\n\nexport interface AuthenticatedPage {\n /** A page in its own context, already carrying the session cookie. */\n page: Page;\n user: SeededUser;\n session: MintedSession;\n}\n\nexport interface ZitadelTestFixtures {\n /** Per-test seeding; each call mints unique data on the shared instance. */\n seed: {\n user(input?: SeedUserInput): Promise<SeededUser>;\n users(count: number, template?: SeedUsersTemplate): Promise<SeededUser[]>;\n /** Unused email+password for registration flows — creates nothing. */\n identity(): Identity;\n /** Seeded user + headless real-flow login; password flows only. */\n session(input?: SeedSessionInput): Promise<MintedSession>;\n };\n /**\n * Start the test authenticated: a fresh user, a real session minted through\n * the flow API, and the cookie injected into a dedicated browser context —\n * the default `page` stays signed out for login-flow tests. Requires\n * `use.baseURL` (every withZitadel consumer sets it).\n */\n authenticatedPage: AuthenticatedPage;\n}\n\nexport interface ZitadelWorkerFixtures {\n /** Connection to the suite's instance, resolved once per worker. */\n zitadel: ConnectedZitadel;\n}\n\nexport const test = base.extend<ZitadelTestFixtures, ZitadelWorkerFixtures>({\n zitadel: [\n // Playwright derives fixture dependencies from the destructuring pattern,\n // so the empty pattern is required here.\n // oxlint-disable-next-line no-empty-pattern\n async ({}, use) => {\n const handshakePath = process.env.ZITADEL_TESTING_HANDSHAKE;\n if (!handshakePath) {\n throw new Error(\n \"ZITADEL_TESTING_HANDSHAKE is not set. Point it at the handshake file \" +\n \"written by the script that boots the instance (see @zitadel/testing docs).\",\n );\n }\n await use(connectZitadel(readHandshakeSync(handshakePath)));\n },\n { scope: \"worker\" },\n ],\n seed: async ({ zitadel, baseURL }, use) => {\n await use({\n user: (input) => zitadel.seedUser(input),\n users: (count, template) => zitadel.seedUsers(count, template),\n identity: () => zitadel.identity(),\n // The suite's baseURL is the app origin the project allowlists.\n session: (input) => zitadel.seedSession({ origin: baseURL, ...input }),\n });\n },\n authenticatedPage: async ({ browser, zitadel, baseURL }, use) => {\n if (!baseURL) {\n throw new Error(\n \"authenticatedPage requires `use.baseURL` so the session cookie can be \" +\n \"scoped to the app under test.\",\n );\n }\n const session = await zitadel.seedSession({ origin: baseURL });\n const context = await browser.newContext({ baseURL });\n // `addCookies` takes either url or domain/path; url derives the rest.\n const { path: _path, ...cookie } = session.cookie;\n await context.addCookies([{ ...cookie, url: baseURL }]);\n const page = await context.newPage();\n await use({ page, user: session.user, session });\n await context.close();\n },\n});\n\nexport { expect } from \"@playwright/test\";\nexport { applyAppEnvTemplate, nextAppEnv } from \"./app-env\";\nexport type { AppEnvTemplate } from \"./app-env\";\nexport { withZitadel } from \"./playwright-config\";\nexport type { WithZitadelOptions } from \"./playwright-config\";\nexport type {\n ConnectedZitadel,\n Identity,\n InstanceHandle,\n MintedSession,\n SeededUser,\n SeedSessionInput,\n SeedUserInput,\n SeedUsersTemplate,\n} from \"./types\";\n"],"mappings":"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"}
|