@emesoft_company/playwright-core 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +62 -0
  2. package/dist/api/index.cjs +248 -0
  3. package/dist/api/index.cjs.map +1 -0
  4. package/dist/api/index.d.cts +12 -0
  5. package/dist/api/index.d.ts +12 -0
  6. package/dist/api/index.js +245 -0
  7. package/dist/api/index.js.map +1 -0
  8. package/dist/api-client-C_StjpPX.d.ts +58 -0
  9. package/dist/api-client-CpXo_NOU.d.cts +58 -0
  10. package/dist/auth/index.cjs +310 -0
  11. package/dist/auth/index.cjs.map +1 -0
  12. package/dist/auth/index.d.cts +98 -0
  13. package/dist/auth/index.d.ts +98 -0
  14. package/dist/auth/index.js +304 -0
  15. package/dist/auth/index.js.map +1 -0
  16. package/dist/config/index.cjs +268 -0
  17. package/dist/config/index.cjs.map +1 -0
  18. package/dist/config/index.d.cts +105 -0
  19. package/dist/config/index.d.ts +105 -0
  20. package/dist/config/index.js +254 -0
  21. package/dist/config/index.js.map +1 -0
  22. package/dist/data/index.cjs +59 -0
  23. package/dist/data/index.cjs.map +1 -0
  24. package/dist/data/index.d.cts +25 -0
  25. package/dist/data/index.d.ts +25 -0
  26. package/dist/data/index.js +50 -0
  27. package/dist/data/index.js.map +1 -0
  28. package/dist/di/index.cjs +47 -0
  29. package/dist/di/index.cjs.map +1 -0
  30. package/dist/di/index.d.cts +19 -0
  31. package/dist/di/index.d.ts +19 -0
  32. package/dist/di/index.js +45 -0
  33. package/dist/di/index.js.map +1 -0
  34. package/dist/errors/index.cjs +90 -0
  35. package/dist/errors/index.cjs.map +1 -0
  36. package/dist/errors/index.d.cts +38 -0
  37. package/dist/errors/index.d.ts +38 -0
  38. package/dist/errors/index.js +83 -0
  39. package/dist/errors/index.js.map +1 -0
  40. package/dist/expect/index.cjs +36 -0
  41. package/dist/expect/index.cjs.map +1 -0
  42. package/dist/expect/index.d.cts +30 -0
  43. package/dist/expect/index.d.ts +30 -0
  44. package/dist/expect/index.js +34 -0
  45. package/dist/expect/index.js.map +1 -0
  46. package/dist/fixtures/index.cjs +660 -0
  47. package/dist/fixtures/index.cjs.map +1 -0
  48. package/dist/fixtures/index.d.cts +68 -0
  49. package/dist/fixtures/index.d.ts +68 -0
  50. package/dist/fixtures/index.js +651 -0
  51. package/dist/fixtures/index.js.map +1 -0
  52. package/dist/index.cjs +1322 -0
  53. package/dist/index.cjs.map +1 -0
  54. package/dist/index.d.cts +21 -0
  55. package/dist/index.d.ts +21 -0
  56. package/dist/index.js +1248 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/logging/index.cjs +32 -0
  59. package/dist/logging/index.cjs.map +1 -0
  60. package/dist/logging/index.d.cts +18 -0
  61. package/dist/logging/index.d.ts +18 -0
  62. package/dist/logging/index.js +25 -0
  63. package/dist/logging/index.js.map +1 -0
  64. package/dist/reporting/index.cjs +59 -0
  65. package/dist/reporting/index.cjs.map +1 -0
  66. package/dist/reporting/index.d.cts +63 -0
  67. package/dist/reporting/index.d.ts +63 -0
  68. package/dist/reporting/index.js +52 -0
  69. package/dist/reporting/index.js.map +1 -0
  70. package/dist/retry-XqnSC8YL.d.cts +27 -0
  71. package/dist/retry-XqnSC8YL.d.ts +27 -0
  72. package/dist/schema-y5w2hbT-.d.cts +292 -0
  73. package/dist/schema-y5w2hbT-.d.ts +292 -0
  74. package/dist/types-CbWnSYEK.d.cts +5 -0
  75. package/dist/types-CbWnSYEK.d.ts +5 -0
  76. package/dist/types-CopwwpSW.d.cts +8 -0
  77. package/dist/types-CopwwpSW.d.ts +8 -0
  78. package/dist/types-hInbkPXf.d.cts +59 -0
  79. package/dist/types-hInbkPXf.d.ts +59 -0
  80. package/dist/ui/index.cjs +366 -0
  81. package/dist/ui/index.cjs.map +1 -0
  82. package/dist/ui/index.d.cts +190 -0
  83. package/dist/ui/index.d.ts +190 -0
  84. package/dist/ui/index.js +348 -0
  85. package/dist/ui/index.js.map +1 -0
  86. package/dist/utils/index.cjs +72 -0
  87. package/dist/utils/index.cjs.map +1 -0
  88. package/dist/utils/index.d.cts +27 -0
  89. package/dist/utils/index.d.ts +27 -0
  90. package/dist/utils/index.js +66 -0
  91. package/dist/utils/index.js.map +1 -0
  92. package/package.json +133 -0
@@ -0,0 +1,254 @@
1
+ import { z } from 'zod';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { parse } from 'dotenv';
4
+ import { devices } from '@playwright/test';
5
+
6
+ // src/errors/app-error.ts
7
+ var AppError = class extends Error {
8
+ code;
9
+ context;
10
+ constructor(message, options) {
11
+ super(message, { cause: options.cause });
12
+ this.name = new.target.name;
13
+ this.code = options.code;
14
+ this.context = options.context;
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ };
18
+ var ConfigValidationError = class extends AppError {
19
+ constructor(message, issues, context) {
20
+ super(message, { code: "CONFIG_VALIDATION_ERROR", context: { issues, ...context } });
21
+ }
22
+ };
23
+ var ConfigMissingError = class extends AppError {
24
+ constructor(name) {
25
+ super(`Required environment variable "${name}" is not set`, {
26
+ code: "ENV_VAR_MISSING",
27
+ context: { name }
28
+ });
29
+ }
30
+ };
31
+
32
+ // src/utils/deep-merge.ts
33
+ function isPlainObject(value) {
34
+ return typeof value === "object" && value !== null && !Array.isArray(value);
35
+ }
36
+ function deepMerge(base, override) {
37
+ if (override === void 0) return base;
38
+ if (!isPlainObject(base) || !isPlainObject(override)) {
39
+ return override;
40
+ }
41
+ const result = { ...base };
42
+ for (const [key, value] of Object.entries(override)) {
43
+ if (value === void 0) continue;
44
+ const baseValue = base[key];
45
+ result[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;
46
+ }
47
+ return result;
48
+ }
49
+ function pruneUndefined(value) {
50
+ if (Array.isArray(value)) return value.map(pruneUndefined);
51
+ if (isPlainObject(value)) {
52
+ const result = {};
53
+ for (const [key, val] of Object.entries(value)) {
54
+ if (val === void 0) continue;
55
+ const pruned = pruneUndefined(val);
56
+ if (isPlainObject(pruned) && Object.keys(pruned).length === 0) continue;
57
+ result[key] = pruned;
58
+ }
59
+ return result;
60
+ }
61
+ return value;
62
+ }
63
+ var KNOWN_ENVIRONMENTS = ["local", "dev", "qa", "uat", "staging", "prod"];
64
+ var EnvironmentSchema = z.string().min(1);
65
+ var TimeoutsSchema = z.object({
66
+ actionMs: z.number().int().positive().default(15e3),
67
+ navigationMs: z.number().int().positive().default(3e4),
68
+ testMs: z.number().int().positive().default(6e4),
69
+ /** Default budget for `waitForCondition` and other non-locator polling. */
70
+ waitMs: z.number().int().positive().default(3e4)
71
+ });
72
+ var AuthConfigSchema = z.object({
73
+ tokenUrl: z.string().url().optional(),
74
+ clientId: z.string().optional(),
75
+ clientSecret: z.string().optional(),
76
+ username: z.string().optional(),
77
+ password: z.string().optional(),
78
+ /** Single-session storage state file (see `buildGlobalSetup`). */
79
+ storageStatePath: z.string().optional(),
80
+ /** Directory used for per-role storage state files in multi-role setups. */
81
+ storageStateDir: z.string().default(".auth")
82
+ }).default({});
83
+ var ReportingConfigSchema = z.object({
84
+ allureEnabled: z.boolean().default(false),
85
+ resultsDir: z.string().default("allure-results"),
86
+ /** `{id}` is replaced with a test-case id — see `meta()` in `@emesoft_company/playwright-core/reporting`. */
87
+ testCaseUrlTemplate: z.string().optional()
88
+ }).default({});
89
+ var RetryConfigSchema = z.object({
90
+ retries: z.number().int().min(0).default(2),
91
+ minDelayMs: z.number().int().positive().default(200),
92
+ maxDelayMs: z.number().int().positive().default(5e3)
93
+ }).default({});
94
+ var DiagnosticsConfigSchema = z.object({
95
+ /** Collect browser console errors + failed requests and attach them to the report. */
96
+ enabled: z.boolean().default(true),
97
+ /** Fail the test if the page logged a console error. Off by default — many apps are noisy. */
98
+ failOnConsoleError: z.boolean().default(false),
99
+ /** Fail the test on an uncaught page exception. */
100
+ failOnPageError: z.boolean().default(false),
101
+ /** Console messages matching any of these substrings are ignored entirely. */
102
+ ignoreConsolePatterns: z.array(z.string()).default([])
103
+ }).default({});
104
+ var AppConfigSchema = z.object({
105
+ environment: EnvironmentSchema,
106
+ baseUrl: z.string().url(),
107
+ apiBaseUrl: z.string().url().optional(),
108
+ tenant: z.string().optional(),
109
+ timeouts: TimeoutsSchema.default({}),
110
+ auth: AuthConfigSchema,
111
+ reporting: ReportingConfigSchema,
112
+ retry: RetryConfigSchema,
113
+ diagnostics: DiagnosticsConfigSchema,
114
+ logLevel: z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]).default("info"),
115
+ custom: z.record(z.string(), z.unknown()).default({})
116
+ });
117
+
118
+ // src/config/loader.ts
119
+ function readEnvConfig(env) {
120
+ return {
121
+ environment: env.TEST_ENV ?? "dev",
122
+ baseUrl: env.BASE_URL,
123
+ apiBaseUrl: env.API_BASE_URL,
124
+ tenant: env.TENANT,
125
+ logLevel: env.LOG_LEVEL,
126
+ timeouts: {
127
+ actionMs: env.ACTION_TIMEOUT_MS ? Number(env.ACTION_TIMEOUT_MS) : void 0,
128
+ navigationMs: env.NAVIGATION_TIMEOUT_MS ? Number(env.NAVIGATION_TIMEOUT_MS) : void 0,
129
+ testMs: env.TEST_TIMEOUT_MS ? Number(env.TEST_TIMEOUT_MS) : void 0,
130
+ waitMs: env.WAIT_TIMEOUT_MS ? Number(env.WAIT_TIMEOUT_MS) : void 0
131
+ },
132
+ auth: {
133
+ tokenUrl: env.AUTH_TOKEN_URL,
134
+ clientId: env.AUTH_CLIENT_ID,
135
+ clientSecret: env.AUTH_CLIENT_SECRET,
136
+ username: env.AUTH_USERNAME,
137
+ password: env.AUTH_PASSWORD,
138
+ storageStatePath: env.AUTH_STORAGE_STATE_PATH,
139
+ storageStateDir: env.AUTH_STORAGE_STATE_DIR
140
+ },
141
+ reporting: {
142
+ allureEnabled: env.REPORT_ALLURE === "true",
143
+ resultsDir: env.ALLURE_RESULTS_DIR,
144
+ testCaseUrlTemplate: env.TEST_CASE_URL_TEMPLATE
145
+ },
146
+ retry: {
147
+ retries: env.RETRY_COUNT ? Number(env.RETRY_COUNT) : void 0,
148
+ minDelayMs: env.RETRY_MIN_DELAY_MS ? Number(env.RETRY_MIN_DELAY_MS) : void 0,
149
+ maxDelayMs: env.RETRY_MAX_DELAY_MS ? Number(env.RETRY_MAX_DELAY_MS) : void 0
150
+ },
151
+ diagnostics: {
152
+ enabled: env.DIAGNOSTICS === void 0 ? void 0 : env.DIAGNOSTICS !== "false",
153
+ failOnConsoleError: env.FAIL_ON_CONSOLE_ERROR === void 0 ? void 0 : env.FAIL_ON_CONSOLE_ERROR === "true",
154
+ failOnPageError: env.FAIL_ON_PAGE_ERROR === void 0 ? void 0 : env.FAIL_ON_PAGE_ERROR === "true"
155
+ }
156
+ };
157
+ }
158
+ function requireEnv(name, env = process.env) {
159
+ const value = env[name];
160
+ if (value === void 0 || value === "") {
161
+ throw new ConfigMissingError(name);
162
+ }
163
+ return value;
164
+ }
165
+ function loadConfig(options = {}) {
166
+ const { env = process.env, overrides } = options;
167
+ const fromEnv = pruneUndefined(readEnvConfig(env));
168
+ const merged = deepMerge(fromEnv, overrides);
169
+ const result = AppConfigSchema.safeParse(merged);
170
+ if (!result.success) {
171
+ throw new ConfigValidationError(
172
+ `Invalid application config: ${result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ")}`,
173
+ result.error.issues
174
+ );
175
+ }
176
+ return result.data;
177
+ }
178
+ function loadEnvFiles(files, env = process.env) {
179
+ const merged = {};
180
+ const loaded = [];
181
+ for (const file of files) {
182
+ if (!existsSync(file)) continue;
183
+ Object.assign(merged, parse(readFileSync(file)));
184
+ loaded.push(file);
185
+ }
186
+ const applied = [];
187
+ for (const [key, value] of Object.entries(merged)) {
188
+ if (env[key] === void 0) {
189
+ env[key] = value;
190
+ applied.push(key);
191
+ }
192
+ }
193
+ return { loaded, applied };
194
+ }
195
+ function defaultEnvFiles(environment) {
196
+ return [".env", ".env.local", `.env.${environment}`, `.env.${environment}.local`];
197
+ }
198
+
199
+ // src/reporting/allure-setup.ts
200
+ function buildReporterConfig(config) {
201
+ const reporters = [["html", { open: "never" }]];
202
+ if (config.reporting.allureEnabled) {
203
+ reporters.push(["allure-playwright", { resultsDir: config.reporting.resultsDir, detail: true }]);
204
+ }
205
+ return reporters;
206
+ }
207
+
208
+ // src/config/define-config.ts
209
+ function resolveEnvFiles(options, environment) {
210
+ if (options.envFile === false) return [];
211
+ if (typeof options.envFile === "string") return [options.envFile];
212
+ if (Array.isArray(options.envFile)) return options.envFile;
213
+ return defaultEnvFiles(environment);
214
+ }
215
+ function defineE2EConfig(options = {}) {
216
+ const environment = process.env.TEST_ENV ?? options.defaultEnvironment ?? "dev";
217
+ loadEnvFiles(resolveEnvFiles(options, environment));
218
+ const overrides = typeof options.configOverrides === "function" ? options.configOverrides() : options.configOverrides;
219
+ const appConfig = loadConfig({ overrides: deepMerge({ environment }, overrides) });
220
+ const browsers = options.browsers ?? ["chromium"];
221
+ const deviceByBrowser = {
222
+ chromium: "Desktop Chrome",
223
+ firefox: "Desktop Firefox",
224
+ webkit: "Desktop Safari"
225
+ };
226
+ const generated = {
227
+ testDir: options.testDir ?? "./tests",
228
+ fullyParallel: true,
229
+ retries: appConfig.retry.retries,
230
+ timeout: appConfig.timeouts.testMs,
231
+ reporter: buildReporterConfig(appConfig),
232
+ // `globalSetup` is only wired when a path is given AND a storage state path is configured —
233
+ // otherwise a project that hasn't set up login yet would fail on every run.
234
+ ...options.globalSetup && appConfig.auth.storageStatePath ? { globalSetup: options.globalSetup } : {},
235
+ ...options.globalTeardown ? { globalTeardown: options.globalTeardown } : {},
236
+ use: {
237
+ baseURL: appConfig.baseUrl,
238
+ actionTimeout: appConfig.timeouts.actionMs,
239
+ navigationTimeout: appConfig.timeouts.navigationMs,
240
+ trace: "on-first-retry",
241
+ screenshot: "only-on-failure",
242
+ ...options.globalSetup && appConfig.auth.storageStatePath ? { storageState: appConfig.auth.storageStatePath } : {}
243
+ },
244
+ projects: browsers.map((browser) => ({
245
+ name: browser,
246
+ use: { ...devices[deviceByBrowser[browser]] }
247
+ }))
248
+ };
249
+ return deepMerge(generated, options.playwright);
250
+ }
251
+
252
+ export { AppConfigSchema, AuthConfigSchema, DiagnosticsConfigSchema, EnvironmentSchema, KNOWN_ENVIRONMENTS, ReportingConfigSchema, RetryConfigSchema, TimeoutsSchema, defaultEnvFiles, defineE2EConfig, loadConfig, loadEnvFiles, requireEnv };
253
+ //# sourceMappingURL=index.js.map
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors/app-error.ts","../../src/utils/deep-merge.ts","../../src/config/schema.ts","../../src/config/loader.ts","../../src/config/env.ts","../../src/reporting/allure-setup.ts","../../src/config/define-config.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface AppErrorOptions {\n code: string;\n cause?: unknown;\n context?: Record<string, unknown>;\n}\n\n/**\n * Base error class for the framework. Every error thrown by core carries a\n * machine-readable `code` and optional `context`, so callers can branch on\n * `error.code` instead of parsing messages.\n */\nexport class AppError extends Error {\n readonly code: string;\n readonly context?: Record<string, unknown>;\n\n constructor(message: string, options: AppErrorOptions) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code;\n this.context = options.context;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ConfigValidationError extends AppError {\n constructor(message: string, issues: unknown, context?: Record<string, unknown>) {\n super(message, { code: \"CONFIG_VALIDATION_ERROR\", context: { issues, ...context } });\n }\n}\n\nexport class ApiError extends AppError {\n readonly status?: number;\n readonly method?: string;\n readonly url?: string;\n\n constructor(\n message: string,\n options: AppErrorOptions & { status?: number; method?: string; url?: string },\n ) {\n super(message, options);\n this.status = options.status;\n this.method = options.method;\n this.url = options.url;\n }\n}\n\nexport class AuthError extends AppError {\n constructor(message: string, options: Omit<AppErrorOptions, \"code\"> = {}) {\n super(message, { code: \"AUTH_ERROR\", ...options });\n }\n}\n\nexport class ConfigMissingError extends AppError {\n constructor(name: string) {\n super(`Required environment variable \"${name}\" is not set`, {\n code: \"ENV_VAR_MISSING\",\n context: { name },\n });\n }\n}\n","export function isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Recursively merges `override` onto `base`. Arrays and primitives replace wholesale;\n * only plain objects are merged key by key. `undefined` values in `override` are ignored,\n * so a partial object never accidentally erases a configured value.\n */\nexport function deepMerge<T>(base: T, override: unknown): T {\n if (override === undefined) return base;\n if (!isPlainObject(base) || !isPlainObject(override)) {\n return override as T;\n }\n const result: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(override)) {\n if (value === undefined) continue;\n const baseValue = (base as Record<string, unknown>)[key];\n result[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;\n }\n return result as T;\n}\n\n/** Strips `undefined` values and empty nested objects, so zod defaults apply instead. */\nexport function pruneUndefined(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(pruneUndefined);\n if (isPlainObject(value)) {\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value)) {\n if (val === undefined) continue;\n const pruned = pruneUndefined(val);\n if (isPlainObject(pruned) && Object.keys(pruned).length === 0) continue;\n result[key] = pruned;\n }\n return result;\n }\n return value;\n}\n","import { z } from \"zod\";\n\n/** Environments seen most often across projects — autocompleted, but any string is accepted. */\nexport const KNOWN_ENVIRONMENTS = [\"local\", \"dev\", \"qa\", \"uat\", \"staging\", \"prod\"] as const;\n\n/**\n * Free-form on purpose: teams run `uat`, `qa`, `sit`, per-tenant envs and so on. The union\n * below keeps editor autocomplete for the common ones without rejecting anything else\n * (`(string & {})` is the TS idiom that preserves suggestions on an open string type).\n */\nexport type Environment = (typeof KNOWN_ENVIRONMENTS)[number] | (string & {});\n\nexport const EnvironmentSchema = z.string().min(1);\n\nexport const TimeoutsSchema = z.object({\n actionMs: z.number().int().positive().default(15_000),\n navigationMs: z.number().int().positive().default(30_000),\n testMs: z.number().int().positive().default(60_000),\n /** Default budget for `waitForCondition` and other non-locator polling. */\n waitMs: z.number().int().positive().default(30_000),\n});\n\nexport const AuthConfigSchema = z\n .object({\n tokenUrl: z.string().url().optional(),\n clientId: z.string().optional(),\n clientSecret: z.string().optional(),\n username: z.string().optional(),\n password: z.string().optional(),\n /** Single-session storage state file (see `buildGlobalSetup`). */\n storageStatePath: z.string().optional(),\n /** Directory used for per-role storage state files in multi-role setups. */\n storageStateDir: z.string().default(\".auth\"),\n })\n .default({});\n\nexport const ReportingConfigSchema = z\n .object({\n allureEnabled: z.boolean().default(false),\n resultsDir: z.string().default(\"allure-results\"),\n /** `{id}` is replaced with a test-case id — see `meta()` in `@emesoft_company/playwright-core/reporting`. */\n testCaseUrlTemplate: z.string().optional(),\n })\n .default({});\n\nexport const RetryConfigSchema = z\n .object({\n retries: z.number().int().min(0).default(2),\n minDelayMs: z.number().int().positive().default(200),\n maxDelayMs: z.number().int().positive().default(5_000),\n })\n .default({});\n\nexport const DiagnosticsConfigSchema = z\n .object({\n /** Collect browser console errors + failed requests and attach them to the report. */\n enabled: z.boolean().default(true),\n /** Fail the test if the page logged a console error. Off by default — many apps are noisy. */\n failOnConsoleError: z.boolean().default(false),\n /** Fail the test on an uncaught page exception. */\n failOnPageError: z.boolean().default(false),\n /** Console messages matching any of these substrings are ignored entirely. */\n ignoreConsolePatterns: z.array(z.string()).default([]),\n })\n .default({});\n\n/**\n * Base config schema shared by every project. Projects extend this via\n * `AppConfigSchema.extend({ ... })` or by nesting project-specific fields\n * under `custom` — core never hardcodes project business fields here.\n */\nexport const AppConfigSchema = z.object({\n environment: EnvironmentSchema,\n baseUrl: z.string().url(),\n apiBaseUrl: z.string().url().optional(),\n tenant: z.string().optional(),\n timeouts: TimeoutsSchema.default({}),\n auth: AuthConfigSchema,\n reporting: ReportingConfigSchema,\n retry: RetryConfigSchema,\n diagnostics: DiagnosticsConfigSchema,\n logLevel: z.enum([\"fatal\", \"error\", \"warn\", \"info\", \"debug\", \"trace\", \"silent\"]).default(\"info\"),\n custom: z.record(z.string(), z.unknown()).default({}),\n});\n\nexport type AppConfig = Omit<z.infer<typeof AppConfigSchema>, \"environment\"> & {\n environment: Environment;\n};\nexport type TimeoutsConfig = z.infer<typeof TimeoutsSchema>;\nexport type AuthConfig = z.infer<typeof AuthConfigSchema>;\nexport type ReportingConfig = z.infer<typeof ReportingConfigSchema>;\nexport type RetryConfig = z.infer<typeof RetryConfigSchema>;\nexport type DiagnosticsConfig = z.infer<typeof DiagnosticsConfigSchema>;\n","import { ConfigMissingError, ConfigValidationError } from \"../errors/app-error.js\";\nimport { deepMerge, pruneUndefined } from \"../utils/deep-merge.js\";\nimport { AppConfigSchema, type AppConfig } from \"./schema.js\";\nimport type { DeepPartial } from \"./types.js\";\n\nfunction readEnvConfig(env: NodeJS.ProcessEnv): Record<string, unknown> {\n return {\n environment: env.TEST_ENV ?? \"dev\",\n baseUrl: env.BASE_URL,\n apiBaseUrl: env.API_BASE_URL,\n tenant: env.TENANT,\n logLevel: env.LOG_LEVEL,\n timeouts: {\n actionMs: env.ACTION_TIMEOUT_MS ? Number(env.ACTION_TIMEOUT_MS) : undefined,\n navigationMs: env.NAVIGATION_TIMEOUT_MS ? Number(env.NAVIGATION_TIMEOUT_MS) : undefined,\n testMs: env.TEST_TIMEOUT_MS ? Number(env.TEST_TIMEOUT_MS) : undefined,\n waitMs: env.WAIT_TIMEOUT_MS ? Number(env.WAIT_TIMEOUT_MS) : undefined,\n },\n auth: {\n tokenUrl: env.AUTH_TOKEN_URL,\n clientId: env.AUTH_CLIENT_ID,\n clientSecret: env.AUTH_CLIENT_SECRET,\n username: env.AUTH_USERNAME,\n password: env.AUTH_PASSWORD,\n storageStatePath: env.AUTH_STORAGE_STATE_PATH,\n storageStateDir: env.AUTH_STORAGE_STATE_DIR,\n },\n reporting: {\n allureEnabled: env.REPORT_ALLURE === \"true\",\n resultsDir: env.ALLURE_RESULTS_DIR,\n testCaseUrlTemplate: env.TEST_CASE_URL_TEMPLATE,\n },\n retry: {\n retries: env.RETRY_COUNT ? Number(env.RETRY_COUNT) : undefined,\n minDelayMs: env.RETRY_MIN_DELAY_MS ? Number(env.RETRY_MIN_DELAY_MS) : undefined,\n maxDelayMs: env.RETRY_MAX_DELAY_MS ? Number(env.RETRY_MAX_DELAY_MS) : undefined,\n },\n diagnostics: {\n enabled: env.DIAGNOSTICS === undefined ? undefined : env.DIAGNOSTICS !== \"false\",\n failOnConsoleError: env.FAIL_ON_CONSOLE_ERROR === undefined ? undefined : env.FAIL_ON_CONSOLE_ERROR === \"true\",\n failOnPageError: env.FAIL_ON_PAGE_ERROR === undefined ? undefined : env.FAIL_ON_PAGE_ERROR === \"true\",\n },\n };\n}\n\n/**\n * Reads a required environment variable, failing immediately with a clear message instead of\n * letting an `undefined` surface as a confusing failure deep inside a test.\n */\nexport function requireEnv(name: string, env: NodeJS.ProcessEnv = process.env): string {\n const value = env[name];\n if (value === undefined || value === \"\") {\n throw new ConfigMissingError(name);\n }\n return value;\n}\n\nexport interface LoadConfigOptions {\n /** Defaults to `process.env`. */\n env?: NodeJS.ProcessEnv;\n /** Project-specific overrides (e.g. from a project's `config.override.ts`), merged on top of env-derived values. */\n overrides?: DeepPartial<AppConfig>;\n}\n\n/**\n * Loads and validates the application config for the current environment.\n * Resolution order: env-derived defaults -> project `overrides` -> zod validation.\n * Fails fast with `ConfigValidationError` when required fields (e.g. `baseUrl`) are missing/invalid,\n * instead of letting tests fail later with a confusing `undefined` somewhere downstream.\n */\nexport function loadConfig(options: LoadConfigOptions = {}): AppConfig {\n const { env = process.env, overrides } = options;\n\n const fromEnv = pruneUndefined(readEnvConfig(env)) as DeepPartial<AppConfig>;\n const merged = deepMerge(fromEnv as AppConfig, overrides);\n\n const result = AppConfigSchema.safeParse(merged);\n if (!result.success) {\n throw new ConfigValidationError(\n `Invalid application config: ${result.error.issues\n .map((issue) => `${issue.path.join(\".\") || \"(root)\"}: ${issue.message}`)\n .join(\"; \")}`,\n result.error.issues,\n );\n }\n return result.data;\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { parse } from \"dotenv\";\n\nexport interface LoadEnvFilesResult {\n /** Files that existed and were read, in the order they were applied. */\n loaded: string[];\n /** Variable names actually written to the environment. */\n applied: string[];\n}\n\n/**\n * Loads several env files with predictable precedence:\n *\n * 1. **Later files beat earlier ones** — so `.env.staging.local` overrides `.env.staging`.\n * 2. **Real environment variables beat every file** — a `BASE_URL` exported by CI or the shell\n * is never clobbered by a committed `.env`, which is what makes the same config work locally\n * and in a pipeline.\n *\n * Missing files are skipped silently: the `.local` files are gitignored and simply won't exist\n * on most machines.\n *\n * The two rules together are why this can't just be repeated `dotenv.config()` calls — dotenv\n * either never overrides (so later files lose) or always overrides (so the shell loses). Files\n * are merged first, then applied only where the environment has nothing already.\n */\nexport function loadEnvFiles(files: string[], env: NodeJS.ProcessEnv = process.env): LoadEnvFilesResult {\n const merged: Record<string, string> = {};\n const loaded: string[] = [];\n\n for (const file of files) {\n if (!existsSync(file)) continue;\n Object.assign(merged, parse(readFileSync(file)));\n loaded.push(file);\n }\n\n const applied: string[] = [];\n for (const [key, value] of Object.entries(merged)) {\n if (env[key] === undefined) {\n env[key] = value;\n applied.push(key);\n }\n }\n\n return { loaded, applied };\n}\n\n/**\n * The standard cascade for an environment, following the convention used by Vite/CRA/Next:\n * a committed base, a gitignored base override, then the same pair for the specific environment.\n * Keep secrets in the `.local` files — they are gitignored; `.env` / `.env.<environment>` are not.\n */\nexport function defaultEnvFiles(environment: string): string[] {\n return [\".env\", \".env.local\", `.env.${environment}`, `.env.${environment}.local`];\n}\n","import type { AppConfig } from \"../config/schema.js\";\n\nexport type PlaywrightReporterEntry = [string] | [string, Record<string, unknown>];\n\n/**\n * Builds the `reporter` array for a project's `playwright.config.ts`. Always includes\n * the built-in HTML reporter; adds Allure when `reporting.allureEnabled` is true.\n *\n * Allure is optional and NOT bundled transitively: because Playwright resolves reporter\n * packages relative to the project that runs the tests, a project enabling Allure must add\n * `allure-playwright` (and `allure-js-commons` if using manual annotations) to its own\n * `devDependencies`. See `docs/EXTENDING.md`.\n */\nexport function buildReporterConfig(config: Pick<AppConfig, \"reporting\">): PlaywrightReporterEntry[] {\n const reporters: PlaywrightReporterEntry[] = [[\"html\", { open: \"never\" }]];\n if (config.reporting.allureEnabled) {\n reporters.push([\"allure-playwright\", { resultsDir: config.reporting.resultsDir, detail: true }]);\n }\n return reporters;\n}\n","import { devices, type PlaywrightTestConfig } from \"@playwright/test\";\nimport { buildReporterConfig } from \"../reporting/allure-setup.js\";\nimport { deepMerge } from \"../utils/deep-merge.js\";\nimport { defaultEnvFiles, loadEnvFiles } from \"./env.js\";\nimport { loadConfig } from \"./loader.js\";\nimport type { AppConfig } from \"./schema.js\";\nimport type { DeepPartial } from \"./types.js\";\n\nexport type BrowserName = \"chromium\" | \"firefox\" | \"webkit\";\n\nexport interface DefineE2EConfigOptions {\n /**\n * Project-specific config overrides merged on top of the env-derived values.\n * Pass a FUNCTION if the overrides read `process.env` — it is called after the env files are\n * loaded, whereas an object literal in a `playwright.config.ts` import would be evaluated\n * before them.\n */\n configOverrides?: DeepPartial<AppConfig> | (() => DeepPartial<AppConfig>);\n /**\n * Environment to use when `TEST_ENV` isn't set. Defaults to `\"dev\"`. Set it for apps that\n * only have one environment (e.g. `\"staging\"`) so nobody has to export `TEST_ENV` by hand.\n */\n defaultEnvironment?: string;\n /**\n * Env files to load. Defaults to the standard cascade for the resolved environment:\n * `.env`, `.env.local`, `.env.<environment>`, `.env.<environment>.local` — later files win,\n * but real environment variables always win over files. Pass an explicit list to take full\n * control, or `false` to load none.\n */\n envFile?: string | string[] | false;\n /** Defaults to `./tests`. */\n testDir?: string;\n /** Browser projects to generate. Defaults to `[\"chromium\"]`. */\n browsers?: BrowserName[];\n /** Path to a global setup module (see `buildGlobalSetup`). */\n globalSetup?: string;\n globalTeardown?: string;\n /**\n * Deep-merged over everything generated here — the escape hatch for any Playwright option\n * core doesn't model. Anything set here wins.\n */\n playwright?: PlaywrightTestConfig;\n}\n\nfunction resolveEnvFiles(options: DefineE2EConfigOptions, environment: string): string[] {\n if (options.envFile === false) return [];\n if (typeof options.envFile === \"string\") return [options.envFile];\n if (Array.isArray(options.envFile)) return options.envFile;\n return defaultEnvFiles(environment);\n}\n\n/**\n * Builds a project's `playwright.config.ts` from its `AppConfig`, so each project doesn't\n * re-implement the same env-loading + config-plumbing boilerplate.\n *\n * Order matters and is handled here: env files are loaded FIRST, then `configOverrides` is\n * evaluated (hence the function form), then the config is validated, then the Playwright config\n * is assembled from it.\n *\n * ```ts\n * // playwright.config.ts\n * import { defineE2EConfig } from \"@emesoft_company/playwright-core/config\";\n * export default defineE2EConfig();\n * ```\n */\nexport function defineE2EConfig(options: DefineE2EConfigOptions = {}): PlaywrightTestConfig {\n const environment = process.env.TEST_ENV ?? options.defaultEnvironment ?? \"dev\";\n loadEnvFiles(resolveEnvFiles(options, environment));\n\n const overrides =\n typeof options.configOverrides === \"function\" ? options.configOverrides() : options.configOverrides;\n // The resolved environment is a base, not an override: an explicit `environment` in the\n // project's own overrides still wins.\n const appConfig = loadConfig({ overrides: deepMerge({ environment }, overrides) });\n\n const browsers = options.browsers ?? [\"chromium\"];\n const deviceByBrowser: Record<BrowserName, string> = {\n chromium: \"Desktop Chrome\",\n firefox: \"Desktop Firefox\",\n webkit: \"Desktop Safari\",\n };\n\n const generated: PlaywrightTestConfig = {\n testDir: options.testDir ?? \"./tests\",\n fullyParallel: true,\n retries: appConfig.retry.retries,\n timeout: appConfig.timeouts.testMs,\n reporter: buildReporterConfig(appConfig),\n // `globalSetup` is only wired when a path is given AND a storage state path is configured —\n // otherwise a project that hasn't set up login yet would fail on every run.\n ...(options.globalSetup && appConfig.auth.storageStatePath\n ? { globalSetup: options.globalSetup }\n : {}),\n ...(options.globalTeardown ? { globalTeardown: options.globalTeardown } : {}),\n use: {\n baseURL: appConfig.baseUrl,\n actionTimeout: appConfig.timeouts.actionMs,\n navigationTimeout: appConfig.timeouts.navigationMs,\n trace: \"on-first-retry\",\n screenshot: \"only-on-failure\",\n ...(options.globalSetup && appConfig.auth.storageStatePath\n ? { storageState: appConfig.auth.storageStatePath }\n : {}),\n },\n projects: browsers.map((browser) => ({\n name: browser,\n use: { ...devices[deviceByBrowser[browser]] },\n })),\n };\n\n return deepMerge(generated, options.playwright);\n}\n"]}
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ // src/data/generators.ts
4
+ var counter = 0;
5
+ var ALPHANUMERIC = "abcdefghijklmnopqrstuvwxyz0123456789";
6
+ function uniqueId(prefix) {
7
+ counter += 1;
8
+ const id = `${Date.now().toString(36)}${process.pid.toString(36)}${counter.toString(36)}`;
9
+ return prefix ? `${prefix}-${id}` : id;
10
+ }
11
+ function uniqueEmail(domain = "example.test", prefix = "qa") {
12
+ return `${prefix}-${uniqueId()}@${domain}`;
13
+ }
14
+ function randomString(length = 8, charset = ALPHANUMERIC) {
15
+ let result = "";
16
+ for (let i = 0; i < length; i++) {
17
+ result += charset.charAt(Math.floor(Math.random() * charset.length));
18
+ }
19
+ return result;
20
+ }
21
+ function randomInt(min, max) {
22
+ if (min > max) throw new RangeError(`randomInt: min (${min}) must be <= max (${max})`);
23
+ return Math.floor(Math.random() * (max - min + 1)) + min;
24
+ }
25
+ function randomBool() {
26
+ return Math.random() < 0.5;
27
+ }
28
+ function pickOne(items) {
29
+ if (items.length === 0) throw new RangeError("pickOne: cannot pick from an empty array");
30
+ return items[randomInt(0, items.length - 1)];
31
+ }
32
+ function pickSome(items, count) {
33
+ if (count > items.length) {
34
+ throw new RangeError(`pickSome: cannot pick ${count} items from an array of ${items.length}`);
35
+ }
36
+ const pool = [...items];
37
+ const picked = [];
38
+ for (let i = 0; i < count; i++) {
39
+ const [item] = pool.splice(randomInt(0, pool.length - 1), 1);
40
+ picked.push(item);
41
+ }
42
+ return picked;
43
+ }
44
+ function isoDate(dayOffset = 0, from = /* @__PURE__ */ new Date()) {
45
+ const date = new Date(from);
46
+ date.setDate(date.getDate() + dayOffset);
47
+ return date.toISOString().slice(0, 10);
48
+ }
49
+
50
+ exports.isoDate = isoDate;
51
+ exports.pickOne = pickOne;
52
+ exports.pickSome = pickSome;
53
+ exports.randomBool = randomBool;
54
+ exports.randomInt = randomInt;
55
+ exports.randomString = randomString;
56
+ exports.uniqueEmail = uniqueEmail;
57
+ exports.uniqueId = uniqueId;
58
+ //# sourceMappingURL=index.cjs.map
59
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/generators.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Domain-neutral value generators. Core deliberately ships no test *data* (that belongs to\n * each project — see `docs/MULTI_TENANT.md`); these only produce unique/random primitives so\n * every project doesn't reinvent \"make me a name nothing else in the DB has\".\n */\n\nlet counter = 0;\n\nconst ALPHANUMERIC = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\n/**\n * An identifier unique across parallel workers and repeated runs: timestamp + process id +\n * an in-process counter. Playwright shards tests across processes, so a bare counter or a\n * bare timestamp will collide.\n */\nexport function uniqueId(prefix?: string): string {\n counter += 1;\n const id = `${Date.now().toString(36)}${process.pid.toString(36)}${counter.toString(36)}`;\n return prefix ? `${prefix}-${id}` : id;\n}\n\n/** A unique, syntactically valid email — for signup/invite flows that reject duplicates. */\nexport function uniqueEmail(domain = \"example.test\", prefix = \"qa\"): string {\n return `${prefix}-${uniqueId()}@${domain}`;\n}\n\nexport function randomString(length = 8, charset: string = ALPHANUMERIC): string {\n let result = \"\";\n for (let i = 0; i < length; i++) {\n result += charset.charAt(Math.floor(Math.random() * charset.length));\n }\n return result;\n}\n\n/** Random integer in `[min, max]`, both inclusive. */\nexport function randomInt(min: number, max: number): number {\n if (min > max) throw new RangeError(`randomInt: min (${min}) must be <= max (${max})`);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport function randomBool(): boolean {\n return Math.random() < 0.5;\n}\n\n/** One random element. Throws on an empty array rather than returning `undefined`. */\nexport function pickOne<T>(items: readonly T[]): T {\n if (items.length === 0) throw new RangeError(\"pickOne: cannot pick from an empty array\");\n return items[randomInt(0, items.length - 1)] as T;\n}\n\n/** `count` distinct random elements, in random order. */\nexport function pickSome<T>(items: readonly T[], count: number): T[] {\n if (count > items.length) {\n throw new RangeError(`pickSome: cannot pick ${count} items from an array of ${items.length}`);\n }\n const pool = [...items];\n const picked: T[] = [];\n for (let i = 0; i < count; i++) {\n const [item] = pool.splice(randomInt(0, pool.length - 1), 1);\n picked.push(item as T);\n }\n return picked;\n}\n\n/** ISO date (`YYYY-MM-DD`) offset from today — e.g. `isoDate(-30)` for a month ago. */\nexport function isoDate(dayOffset = 0, from: Date = new Date()): string {\n const date = new Date(from);\n date.setDate(date.getDate() + dayOffset);\n return date.toISOString().slice(0, 10);\n}\n"]}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Domain-neutral value generators. Core deliberately ships no test *data* (that belongs to
3
+ * each project — see `docs/MULTI_TENANT.md`); these only produce unique/random primitives so
4
+ * every project doesn't reinvent "make me a name nothing else in the DB has".
5
+ */
6
+ /**
7
+ * An identifier unique across parallel workers and repeated runs: timestamp + process id +
8
+ * an in-process counter. Playwright shards tests across processes, so a bare counter or a
9
+ * bare timestamp will collide.
10
+ */
11
+ declare function uniqueId(prefix?: string): string;
12
+ /** A unique, syntactically valid email — for signup/invite flows that reject duplicates. */
13
+ declare function uniqueEmail(domain?: string, prefix?: string): string;
14
+ declare function randomString(length?: number, charset?: string): string;
15
+ /** Random integer in `[min, max]`, both inclusive. */
16
+ declare function randomInt(min: number, max: number): number;
17
+ declare function randomBool(): boolean;
18
+ /** One random element. Throws on an empty array rather than returning `undefined`. */
19
+ declare function pickOne<T>(items: readonly T[]): T;
20
+ /** `count` distinct random elements, in random order. */
21
+ declare function pickSome<T>(items: readonly T[], count: number): T[];
22
+ /** ISO date (`YYYY-MM-DD`) offset from today — e.g. `isoDate(-30)` for a month ago. */
23
+ declare function isoDate(dayOffset?: number, from?: Date): string;
24
+
25
+ export { isoDate, pickOne, pickSome, randomBool, randomInt, randomString, uniqueEmail, uniqueId };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Domain-neutral value generators. Core deliberately ships no test *data* (that belongs to
3
+ * each project — see `docs/MULTI_TENANT.md`); these only produce unique/random primitives so
4
+ * every project doesn't reinvent "make me a name nothing else in the DB has".
5
+ */
6
+ /**
7
+ * An identifier unique across parallel workers and repeated runs: timestamp + process id +
8
+ * an in-process counter. Playwright shards tests across processes, so a bare counter or a
9
+ * bare timestamp will collide.
10
+ */
11
+ declare function uniqueId(prefix?: string): string;
12
+ /** A unique, syntactically valid email — for signup/invite flows that reject duplicates. */
13
+ declare function uniqueEmail(domain?: string, prefix?: string): string;
14
+ declare function randomString(length?: number, charset?: string): string;
15
+ /** Random integer in `[min, max]`, both inclusive. */
16
+ declare function randomInt(min: number, max: number): number;
17
+ declare function randomBool(): boolean;
18
+ /** One random element. Throws on an empty array rather than returning `undefined`. */
19
+ declare function pickOne<T>(items: readonly T[]): T;
20
+ /** `count` distinct random elements, in random order. */
21
+ declare function pickSome<T>(items: readonly T[], count: number): T[];
22
+ /** ISO date (`YYYY-MM-DD`) offset from today — e.g. `isoDate(-30)` for a month ago. */
23
+ declare function isoDate(dayOffset?: number, from?: Date): string;
24
+
25
+ export { isoDate, pickOne, pickSome, randomBool, randomInt, randomString, uniqueEmail, uniqueId };
@@ -0,0 +1,50 @@
1
+ // src/data/generators.ts
2
+ var counter = 0;
3
+ var ALPHANUMERIC = "abcdefghijklmnopqrstuvwxyz0123456789";
4
+ function uniqueId(prefix) {
5
+ counter += 1;
6
+ const id = `${Date.now().toString(36)}${process.pid.toString(36)}${counter.toString(36)}`;
7
+ return prefix ? `${prefix}-${id}` : id;
8
+ }
9
+ function uniqueEmail(domain = "example.test", prefix = "qa") {
10
+ return `${prefix}-${uniqueId()}@${domain}`;
11
+ }
12
+ function randomString(length = 8, charset = ALPHANUMERIC) {
13
+ let result = "";
14
+ for (let i = 0; i < length; i++) {
15
+ result += charset.charAt(Math.floor(Math.random() * charset.length));
16
+ }
17
+ return result;
18
+ }
19
+ function randomInt(min, max) {
20
+ if (min > max) throw new RangeError(`randomInt: min (${min}) must be <= max (${max})`);
21
+ return Math.floor(Math.random() * (max - min + 1)) + min;
22
+ }
23
+ function randomBool() {
24
+ return Math.random() < 0.5;
25
+ }
26
+ function pickOne(items) {
27
+ if (items.length === 0) throw new RangeError("pickOne: cannot pick from an empty array");
28
+ return items[randomInt(0, items.length - 1)];
29
+ }
30
+ function pickSome(items, count) {
31
+ if (count > items.length) {
32
+ throw new RangeError(`pickSome: cannot pick ${count} items from an array of ${items.length}`);
33
+ }
34
+ const pool = [...items];
35
+ const picked = [];
36
+ for (let i = 0; i < count; i++) {
37
+ const [item] = pool.splice(randomInt(0, pool.length - 1), 1);
38
+ picked.push(item);
39
+ }
40
+ return picked;
41
+ }
42
+ function isoDate(dayOffset = 0, from = /* @__PURE__ */ new Date()) {
43
+ const date = new Date(from);
44
+ date.setDate(date.getDate() + dayOffset);
45
+ return date.toISOString().slice(0, 10);
46
+ }
47
+
48
+ export { isoDate, pickOne, pickSome, randomBool, randomInt, randomString, uniqueEmail, uniqueId };
49
+ //# sourceMappingURL=index.js.map
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/data/generators.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Domain-neutral value generators. Core deliberately ships no test *data* (that belongs to\n * each project — see `docs/MULTI_TENANT.md`); these only produce unique/random primitives so\n * every project doesn't reinvent \"make me a name nothing else in the DB has\".\n */\n\nlet counter = 0;\n\nconst ALPHANUMERIC = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n\n/**\n * An identifier unique across parallel workers and repeated runs: timestamp + process id +\n * an in-process counter. Playwright shards tests across processes, so a bare counter or a\n * bare timestamp will collide.\n */\nexport function uniqueId(prefix?: string): string {\n counter += 1;\n const id = `${Date.now().toString(36)}${process.pid.toString(36)}${counter.toString(36)}`;\n return prefix ? `${prefix}-${id}` : id;\n}\n\n/** A unique, syntactically valid email — for signup/invite flows that reject duplicates. */\nexport function uniqueEmail(domain = \"example.test\", prefix = \"qa\"): string {\n return `${prefix}-${uniqueId()}@${domain}`;\n}\n\nexport function randomString(length = 8, charset: string = ALPHANUMERIC): string {\n let result = \"\";\n for (let i = 0; i < length; i++) {\n result += charset.charAt(Math.floor(Math.random() * charset.length));\n }\n return result;\n}\n\n/** Random integer in `[min, max]`, both inclusive. */\nexport function randomInt(min: number, max: number): number {\n if (min > max) throw new RangeError(`randomInt: min (${min}) must be <= max (${max})`);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nexport function randomBool(): boolean {\n return Math.random() < 0.5;\n}\n\n/** One random element. Throws on an empty array rather than returning `undefined`. */\nexport function pickOne<T>(items: readonly T[]): T {\n if (items.length === 0) throw new RangeError(\"pickOne: cannot pick from an empty array\");\n return items[randomInt(0, items.length - 1)] as T;\n}\n\n/** `count` distinct random elements, in random order. */\nexport function pickSome<T>(items: readonly T[], count: number): T[] {\n if (count > items.length) {\n throw new RangeError(`pickSome: cannot pick ${count} items from an array of ${items.length}`);\n }\n const pool = [...items];\n const picked: T[] = [];\n for (let i = 0; i < count; i++) {\n const [item] = pool.splice(randomInt(0, pool.length - 1), 1);\n picked.push(item as T);\n }\n return picked;\n}\n\n/** ISO date (`YYYY-MM-DD`) offset from today — e.g. `isoDate(-30)` for a month ago. */\nexport function isoDate(dayOffset = 0, from: Date = new Date()): string {\n const date = new Date(from);\n date.setDate(date.getDate() + dayOffset);\n return date.toISOString().slice(0, 10);\n}\n"]}
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ // src/errors/app-error.ts
4
+ var AppError = class extends Error {
5
+ code;
6
+ context;
7
+ constructor(message, options) {
8
+ super(message, { cause: options.cause });
9
+ this.name = new.target.name;
10
+ this.code = options.code;
11
+ this.context = options.context;
12
+ Object.setPrototypeOf(this, new.target.prototype);
13
+ }
14
+ };
15
+
16
+ // src/di/container.ts
17
+ var ServiceContainer = class {
18
+ factories = /* @__PURE__ */ new Map();
19
+ singletons = /* @__PURE__ */ new Map();
20
+ register(key, factory) {
21
+ this.factories.set(key, factory);
22
+ this.singletons.delete(key);
23
+ }
24
+ /** Replaces an existing registration — this is how a project overrides a core-provided service. */
25
+ override(key, factory) {
26
+ this.register(key, factory);
27
+ }
28
+ resolve(key) {
29
+ if (this.singletons.has(key)) {
30
+ return this.singletons.get(key);
31
+ }
32
+ const factory = this.factories.get(key);
33
+ if (!factory) {
34
+ throw new AppError(`No provider registered for service "${key}"`, { code: "DI_UNRESOLVED" });
35
+ }
36
+ const instance = factory(this);
37
+ this.singletons.set(key, instance);
38
+ return instance;
39
+ }
40
+ has(key) {
41
+ return this.factories.has(key);
42
+ }
43
+ };
44
+
45
+ exports.ServiceContainer = ServiceContainer;
46
+ //# sourceMappingURL=index.cjs.map
47
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/errors/app-error.ts","../../src/di/container.ts"],"names":[],"mappings":";;;AAWO,IAAM,QAAA,GAAN,cAAuB,KAAA,CAAM;AAAA,EACzB,IAAA;AAAA,EACA,OAAA;AAAA,EAET,WAAA,CAAY,SAAiB,OAAA,EAA0B;AACrD,IAAA,KAAA,CAAM,OAAA,EAAS,EAAE,KAAA,EAAO,OAAA,CAAQ,OAAO,CAAA;AACvC,IAAA,IAAA,CAAK,OAAO,GAAA,CAAA,MAAA,CAAW,IAAA;AACvB,IAAA,IAAA,CAAK,OAAO,OAAA,CAAQ,IAAA;AACpB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,GAAA,CAAA,MAAA,CAAW,SAAS,CAAA;AAAA,EAClD;AACF,CAAA;;;ACXO,IAAM,mBAAN,MAAuB;AAAA,EACX,SAAA,uBAAgB,GAAA,EAA8B;AAAA,EAC9C,UAAA,uBAAiB,GAAA,EAAqB;AAAA,EAEvD,QAAA,CAAY,KAAa,OAAA,EAA2B;AAClD,IAAA,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAA,EAAK,OAA2B,CAAA;AACnD,IAAA,IAAA,CAAK,UAAA,CAAW,OAAO,GAAG,CAAA;AAAA,EAC5B;AAAA;AAAA,EAGA,QAAA,CAAY,KAAa,OAAA,EAA2B;AAClD,IAAA,IAAA,CAAK,QAAA,CAAS,KAAK,OAAO,CAAA;AAAA,EAC5B;AAAA,EAEA,QAAW,GAAA,EAAgB;AACzB,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,MAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA;AAAA,IAChC;AACA,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AACtC,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,IAAI,SAAS,CAAA,oCAAA,EAAuC,GAAG,KAAK,EAAE,IAAA,EAAM,iBAAiB,CAAA;AAAA,IAC7F;AACA,IAAA,MAAM,QAAA,GAAW,QAAQ,IAAI,CAAA;AAC7B,IAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAA,EAAK,QAAQ,CAAA;AACjC,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEA,IAAI,GAAA,EAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA;AAAA,EAC/B;AACF","file":"index.cjs","sourcesContent":["export interface AppErrorOptions {\n code: string;\n cause?: unknown;\n context?: Record<string, unknown>;\n}\n\n/**\n * Base error class for the framework. Every error thrown by core carries a\n * machine-readable `code` and optional `context`, so callers can branch on\n * `error.code` instead of parsing messages.\n */\nexport class AppError extends Error {\n readonly code: string;\n readonly context?: Record<string, unknown>;\n\n constructor(message: string, options: AppErrorOptions) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code;\n this.context = options.context;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class ConfigValidationError extends AppError {\n constructor(message: string, issues: unknown, context?: Record<string, unknown>) {\n super(message, { code: \"CONFIG_VALIDATION_ERROR\", context: { issues, ...context } });\n }\n}\n\nexport class ApiError extends AppError {\n readonly status?: number;\n readonly method?: string;\n readonly url?: string;\n\n constructor(\n message: string,\n options: AppErrorOptions & { status?: number; method?: string; url?: string },\n ) {\n super(message, options);\n this.status = options.status;\n this.method = options.method;\n this.url = options.url;\n }\n}\n\nexport class AuthError extends AppError {\n constructor(message: string, options: Omit<AppErrorOptions, \"code\"> = {}) {\n super(message, { code: \"AUTH_ERROR\", ...options });\n }\n}\n\nexport class ConfigMissingError extends AppError {\n constructor(name: string) {\n super(`Required environment variable \"${name}\" is not set`, {\n code: \"ENV_VAR_MISSING\",\n context: { name },\n });\n }\n}\n","import { AppError } from \"../errors/app-error.js\";\n\nexport type Factory<T> = (container: ServiceContainer) => T;\n\n/**\n * Minimal typed service locator used internally by core (and available to projects)\n * for wiring services that aren't naturally expressed as Playwright fixtures — e.g.\n * a token provider shared by both the API client and UI auth setup. Playwright's own\n * `test.extend()` remains the primary DI mechanism for anything test-scoped; this\n * container is for singletons that outlive a single fixture chain.\n */\nexport class ServiceContainer {\n private readonly factories = new Map<string, Factory<unknown>>();\n private readonly singletons = new Map<string, unknown>();\n\n register<T>(key: string, factory: Factory<T>): void {\n this.factories.set(key, factory as Factory<unknown>);\n this.singletons.delete(key);\n }\n\n /** Replaces an existing registration — this is how a project overrides a core-provided service. */\n override<T>(key: string, factory: Factory<T>): void {\n this.register(key, factory);\n }\n\n resolve<T>(key: string): T {\n if (this.singletons.has(key)) {\n return this.singletons.get(key) as T;\n }\n const factory = this.factories.get(key);\n if (!factory) {\n throw new AppError(`No provider registered for service \"${key}\"`, { code: \"DI_UNRESOLVED\" });\n }\n const instance = factory(this);\n this.singletons.set(key, instance);\n return instance as T;\n }\n\n has(key: string): boolean {\n return this.factories.has(key);\n }\n}\n"]}
@@ -0,0 +1,19 @@
1
+ type Factory<T> = (container: ServiceContainer) => T;
2
+ /**
3
+ * Minimal typed service locator used internally by core (and available to projects)
4
+ * for wiring services that aren't naturally expressed as Playwright fixtures — e.g.
5
+ * a token provider shared by both the API client and UI auth setup. Playwright's own
6
+ * `test.extend()` remains the primary DI mechanism for anything test-scoped; this
7
+ * container is for singletons that outlive a single fixture chain.
8
+ */
9
+ declare class ServiceContainer {
10
+ private readonly factories;
11
+ private readonly singletons;
12
+ register<T>(key: string, factory: Factory<T>): void;
13
+ /** Replaces an existing registration — this is how a project overrides a core-provided service. */
14
+ override<T>(key: string, factory: Factory<T>): void;
15
+ resolve<T>(key: string): T;
16
+ has(key: string): boolean;
17
+ }
18
+
19
+ export { type Factory, ServiceContainer };
@@ -0,0 +1,19 @@
1
+ type Factory<T> = (container: ServiceContainer) => T;
2
+ /**
3
+ * Minimal typed service locator used internally by core (and available to projects)
4
+ * for wiring services that aren't naturally expressed as Playwright fixtures — e.g.
5
+ * a token provider shared by both the API client and UI auth setup. Playwright's own
6
+ * `test.extend()` remains the primary DI mechanism for anything test-scoped; this
7
+ * container is for singletons that outlive a single fixture chain.
8
+ */
9
+ declare class ServiceContainer {
10
+ private readonly factories;
11
+ private readonly singletons;
12
+ register<T>(key: string, factory: Factory<T>): void;
13
+ /** Replaces an existing registration — this is how a project overrides a core-provided service. */
14
+ override<T>(key: string, factory: Factory<T>): void;
15
+ resolve<T>(key: string): T;
16
+ has(key: string): boolean;
17
+ }
18
+
19
+ export { type Factory, ServiceContainer };