@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.
- package/README.md +62 -0
- package/dist/api/index.cjs +248 -0
- package/dist/api/index.cjs.map +1 -0
- package/dist/api/index.d.cts +12 -0
- package/dist/api/index.d.ts +12 -0
- package/dist/api/index.js +245 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api-client-C_StjpPX.d.ts +58 -0
- package/dist/api-client-CpXo_NOU.d.cts +58 -0
- package/dist/auth/index.cjs +310 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +98 -0
- package/dist/auth/index.d.ts +98 -0
- package/dist/auth/index.js +304 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/config/index.cjs +268 -0
- package/dist/config/index.cjs.map +1 -0
- package/dist/config/index.d.cts +105 -0
- package/dist/config/index.d.ts +105 -0
- package/dist/config/index.js +254 -0
- package/dist/config/index.js.map +1 -0
- package/dist/data/index.cjs +59 -0
- package/dist/data/index.cjs.map +1 -0
- package/dist/data/index.d.cts +25 -0
- package/dist/data/index.d.ts +25 -0
- package/dist/data/index.js +50 -0
- package/dist/data/index.js.map +1 -0
- package/dist/di/index.cjs +47 -0
- package/dist/di/index.cjs.map +1 -0
- package/dist/di/index.d.cts +19 -0
- package/dist/di/index.d.ts +19 -0
- package/dist/di/index.js +45 -0
- package/dist/di/index.js.map +1 -0
- package/dist/errors/index.cjs +90 -0
- package/dist/errors/index.cjs.map +1 -0
- package/dist/errors/index.d.cts +38 -0
- package/dist/errors/index.d.ts +38 -0
- package/dist/errors/index.js +83 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/expect/index.cjs +36 -0
- package/dist/expect/index.cjs.map +1 -0
- package/dist/expect/index.d.cts +30 -0
- package/dist/expect/index.d.ts +30 -0
- package/dist/expect/index.js +34 -0
- package/dist/expect/index.js.map +1 -0
- package/dist/fixtures/index.cjs +660 -0
- package/dist/fixtures/index.cjs.map +1 -0
- package/dist/fixtures/index.d.cts +68 -0
- package/dist/fixtures/index.d.ts +68 -0
- package/dist/fixtures/index.js +651 -0
- package/dist/fixtures/index.js.map +1 -0
- package/dist/index.cjs +1322 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1248 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/index.cjs +32 -0
- package/dist/logging/index.cjs.map +1 -0
- package/dist/logging/index.d.cts +18 -0
- package/dist/logging/index.d.ts +18 -0
- package/dist/logging/index.js +25 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/reporting/index.cjs +59 -0
- package/dist/reporting/index.cjs.map +1 -0
- package/dist/reporting/index.d.cts +63 -0
- package/dist/reporting/index.d.ts +63 -0
- package/dist/reporting/index.js +52 -0
- package/dist/reporting/index.js.map +1 -0
- package/dist/retry-XqnSC8YL.d.cts +27 -0
- package/dist/retry-XqnSC8YL.d.ts +27 -0
- package/dist/schema-y5w2hbT-.d.cts +292 -0
- package/dist/schema-y5w2hbT-.d.ts +292 -0
- package/dist/types-CbWnSYEK.d.cts +5 -0
- package/dist/types-CbWnSYEK.d.ts +5 -0
- package/dist/types-CopwwpSW.d.cts +8 -0
- package/dist/types-CopwwpSW.d.ts +8 -0
- package/dist/types-hInbkPXf.d.cts +59 -0
- package/dist/types-hInbkPXf.d.ts +59 -0
- package/dist/ui/index.cjs +366 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.cts +190 -0
- package/dist/ui/index.d.ts +190 -0
- package/dist/ui/index.js +348 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/utils/index.cjs +72 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +27 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/index.js +66 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +133 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/app-error.ts","../../src/auth/token-provider.ts","../../src/utils/sleep.ts","../../src/errors/retry.ts","../../src/api/interceptors.ts","../../src/api/api-client.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","import { AuthError } from \"../errors/app-error.js\";\nimport type { Logger } from \"../logging/logger.js\";\nimport type { TokenProvider } from \"./types.js\";\n\n/** Token provider that always returns a fixed token. Useful for tests/local dev with a pre-issued token. */\nexport class StaticTokenProvider implements TokenProvider {\n constructor(private readonly token: string) {}\n\n async getToken(): Promise<string> {\n return this.token;\n }\n\n invalidate(): void {\n // no-op: nothing to invalidate for a static token\n }\n}\n\n/** No-op provider for endpoints that don't require authentication. */\nexport class NullTokenProvider implements TokenProvider {\n async getToken(): Promise<string> {\n return \"\";\n }\n\n invalidate(): void {\n // no-op\n }\n}\n\nexport interface JwtTokenProviderOptions {\n tokenUrl: string;\n clientId?: string;\n clientSecret?: string;\n username?: string;\n password?: string;\n /** Seconds subtracted from the token's `expires_in` to refresh proactively. Default: 30. */\n refreshSkewSeconds?: number;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\n/**\n * JWT/OAuth2 token provider supporting client-credentials or password grants.\n * Caches the token in-memory and transparently refreshes it before expiry.\n */\nexport class JwtTokenProvider implements TokenProvider {\n private cached?: CachedToken;\n private inFlight?: Promise<string>;\n\n constructor(private readonly options: JwtTokenProviderOptions) {}\n\n async getToken(): Promise<string> {\n if (this.cached && this.cached.expiresAt > Date.now()) {\n return this.cached.token;\n }\n if (!this.inFlight) {\n this.inFlight = this.fetchToken().finally(() => {\n this.inFlight = undefined;\n });\n }\n return this.inFlight;\n }\n\n invalidate(): void {\n this.cached = undefined;\n }\n\n private async fetchToken(): Promise<string> {\n const { tokenUrl, clientId, clientSecret, username, password, refreshSkewSeconds = 30, logger, fetchImpl = fetch } = this.options;\n\n const body = new URLSearchParams();\n if (username && password) {\n body.set(\"grant_type\", \"password\");\n body.set(\"username\", username);\n body.set(\"password\", password);\n } else {\n body.set(\"grant_type\", \"client_credentials\");\n }\n if (clientId) body.set(\"client_id\", clientId);\n if (clientSecret) body.set(\"client_secret\", clientSecret);\n\n logger?.debug({ tokenUrl }, \"fetching auth token\");\n\n let response: Response;\n try {\n response = await fetchImpl(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n } catch (cause) {\n throw new AuthError(`Failed to reach token endpoint ${tokenUrl}`, { cause });\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new AuthError(`Token endpoint returned ${response.status}: ${text}`, {\n context: { status: response.status, tokenUrl },\n });\n }\n\n const data = (await response.json()) as { access_token?: string; expires_in?: number };\n if (!data.access_token) {\n throw new AuthError(\"Token endpoint response missing access_token\", { context: { tokenUrl } });\n }\n\n const expiresInSeconds = data.expires_in ?? 300;\n this.cached = {\n token: data.access_token,\n expiresAt: Date.now() + Math.max(expiresInSeconds - refreshSkewSeconds, 0) * 1000,\n };\n return this.cached.token;\n }\n}\n","export function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","import { sleep } from \"../utils/sleep.js\";\n\nexport interface RetryOptions {\n /** Number of retry attempts after the initial try. Default: 2. */\n retries?: number;\n minDelayMs?: number;\n maxDelayMs?: number;\n /** Exponential backoff factor. Default: 2. */\n factor?: number;\n /** Return true to retry this error, false to fail fast. Default: retry everything. */\n retryOn?: (error: unknown) => boolean;\n /** Called before each retry (not before the first attempt). */\n onRetry?: (error: unknown, attempt: number) => void;\n /** Called once when giving up, with the total number of attempts made. */\n onGiveUp?: (error: unknown, attempts: number) => void;\n}\n\nfunction backoffDelay(attempt: number, minDelayMs: number, maxDelayMs: number, factor: number): number {\n const exponential = minDelayMs * factor ** (attempt - 1);\n const jitter = Math.random() * minDelayMs;\n return Math.min(exponential + jitter, maxDelayMs);\n}\n\n/**\n * Runs `fn` with exponential-backoff retry. Used for both flaky API calls and\n * flaky UI actions so the retry policy stays consistent across the framework.\n *\n * Always rethrows the ORIGINAL error — whether it failed fast (`retryOn` returned\n * false) or exhausted its retries. That's what makes `catch (e) { e instanceof ApiError }`\n * and `expect(...).rejects.toThrow(ApiError)` work in tests; wrapping the error in a\n * retry-specific type would hide the only thing the caller actually wants to assert on.\n * Use `onGiveUp` if you need the attempt count.\n */\nexport async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {\n const {\n retries = 2,\n minDelayMs = 200,\n maxDelayMs = 5_000,\n factor = 2,\n retryOn = () => true,\n onRetry,\n onGiveUp,\n } = options;\n\n const maxAttempts = retries + 1;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n const isLastAttempt = attempt >= maxAttempts;\n if (isLastAttempt || !retryOn(error)) {\n onGiveUp?.(error, attempt);\n throw error;\n }\n onRetry?.(error, attempt);\n await sleep(backoffDelay(attempt, minDelayMs, maxDelayMs, factor));\n }\n }\n\n // Unreachable: the loop either returns or throws.\n throw new Error(\"withRetry: unreachable state\");\n}\n","/** 429 (rate-limited) and any 5xx are considered transient and safe to retry. */\nexport function isRetryableStatus(status: number): boolean {\n return status === 429 || (status >= 500 && status <= 599);\n}\n","import type { APIRequestContext, APIResponse } from \"@playwright/test\";\nimport { NullTokenProvider } from \"../auth/token-provider.js\";\nimport type { TokenProvider } from \"../auth/types.js\";\nimport { ApiError, AppError } from \"../errors/app-error.js\";\nimport { withRetry, type RetryOptions } from \"../errors/retry.js\";\nimport type { Logger } from \"../logging/logger.js\";\nimport { isRetryableStatus } from \"./interceptors.js\";\nimport type { ApiClientInterceptors, ApiRequestOptions, ApiResponse } from \"./types.js\";\n\nexport interface ApiClientOptions {\n /** Playwright's `APIRequestContext` (from `request` fixture or `context.request`). */\n request: APIRequestContext;\n baseUrl?: string;\n tokenProvider?: TokenProvider;\n logger?: Logger;\n retry?: Partial<RetryOptions>;\n defaultHeaders?: Record<string, string>;\n interceptors?: ApiClientInterceptors;\n}\n\n/** Internal signal used to drive retry backoff; never escapes `send()`. */\nclass TransientHttpError extends Error {\n constructor(readonly status: number) {\n super(`Transient HTTP status ${status}`);\n }\n}\n\n/**\n * Thin, project-agnostic abstraction over Playwright's `APIRequestContext`:\n * base URL resolution, bearer-token injection, structured error mapping, and\n * retry-on-5xx/429. Projects extend this for domain-specific API clients\n * instead of calling `request.fetch` directly in tests.\n *\n * Two levels of API, on purpose:\n * - `get`/`post`/... return the body and THROW `ApiError` on an unexpected status — the right\n * default for arranging test data, where a failed call should stop the test loudly.\n * - `send()` returns the full `ApiResponse` (status, headers, body) and never throws for an\n * HTTP status — for tests that assert on error responses themselves.\n */\nexport class ApiClient {\n private readonly request: APIRequestContext;\n private readonly baseUrl?: string;\n private readonly tokenProvider: TokenProvider;\n private readonly logger?: Logger;\n private readonly retryOptions: RetryOptions;\n private readonly defaultHeaders: Record<string, string>;\n private readonly interceptors: ApiClientInterceptors;\n\n constructor(options: ApiClientOptions) {\n this.request = options.request;\n this.baseUrl = options.baseUrl;\n this.tokenProvider = options.tokenProvider ?? new NullTokenProvider();\n this.logger = options.logger;\n this.defaultHeaders = options.defaultHeaders ?? {};\n this.interceptors = options.interceptors ?? {};\n this.retryOptions = { ...options.retry };\n }\n\n get<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T> {\n return this.requestBody<T>(\"GET\", path, options);\n }\n\n post<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T> {\n return this.requestBody<T>(\"POST\", path, options);\n }\n\n put<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T> {\n return this.requestBody<T>(\"PUT\", path, options);\n }\n\n patch<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T> {\n return this.requestBody<T>(\"PATCH\", path, options);\n }\n\n delete<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T> {\n return this.requestBody<T>(\"DELETE\", path, options);\n }\n\n /**\n * Full response including status and headers. Retries transient statuses, then returns\n * whatever came back — a 404 or 500 is a normal return value here, not an exception.\n * Network/timeout failures still throw.\n */\n async send<T = unknown>(\n method: string,\n path: string,\n options: ApiRequestOptions<T> = {},\n ): Promise<ApiResponse<T>> {\n const url = this.resolveUrl(path, options.params);\n let lastResponse: ApiResponse<T> | undefined;\n\n try {\n await withRetry(\n async () => {\n lastResponse = await this.fetchOnce<T>(method, url, options);\n if (isRetryableStatus(lastResponse.status) && !this.isExpectedStatus(lastResponse.status, options)) {\n throw new TransientHttpError(lastResponse.status);\n }\n },\n {\n ...this.retryOptions,\n retryOn: (error) => error instanceof TransientHttpError,\n onRetry: (error, attempt) =>\n this.logger?.warn({ method, url, attempt, err: error }, \"retrying API request\"),\n },\n );\n } catch (error) {\n // A transient status that never recovered is still a response the caller may want to\n // inspect; anything else (DNS, TLS, timeout) is a real failure and must propagate.\n if (!(error instanceof TransientHttpError)) throw error;\n }\n\n if (!lastResponse) {\n throw new AppError(`API request produced no response: ${method} ${url}`, {\n code: \"API_NO_RESPONSE\",\n context: { method, url },\n });\n }\n return lastResponse;\n }\n\n private async requestBody<T>(\n method: string,\n path: string,\n options: ApiRequestOptions<T> = {},\n ): Promise<T> {\n const response = await this.send<T>(method, path, options);\n\n if (!this.isExpectedStatus(response.status, options)) {\n if (response.status === 401) this.tokenProvider.invalidate();\n this.logger?.error(\n { status: response.status, method, url: response.url, body: response.data },\n \"API request failed\",\n );\n throw new ApiError(\n `API request failed: ${method} ${response.url} -> ${response.status}` +\n (options.expectStatus === undefined ? \"\" : ` (expected ${String(options.expectStatus)})`),\n {\n code: \"API_REQUEST_FAILED\",\n status: response.status,\n method,\n url: response.url,\n context: { body: response.data },\n },\n );\n }\n return response.data;\n }\n\n private isExpectedStatus(status: number, options: ApiRequestOptions<unknown>): boolean {\n if (options.expectStatus === undefined) return status >= 200 && status < 300;\n const expected = Array.isArray(options.expectStatus) ? options.expectStatus : [options.expectStatus];\n return expected.includes(status);\n }\n\n private async fetchOnce<T>(\n method: string,\n url: string,\n options: ApiRequestOptions<T>,\n ): Promise<ApiResponse<T>> {\n const headers = await this.buildHeaders(method, url, options);\n const response = await this.request.fetch(url, {\n method,\n headers,\n data: options.data,\n form: options.form,\n multipart: options.multipart,\n timeout: options.timeoutMs,\n });\n await this.interceptors.onResponse?.({ method, url, status: response.status() });\n\n return {\n status: response.status(),\n ok: response.ok(),\n headers: response.headers(),\n data: await this.parseBody<T>(response, options),\n url,\n method,\n };\n }\n\n private resolveUrl(path: string, params?: ApiRequestOptions<unknown>[\"params\"]): string {\n const base = this.baseUrl ? new URL(path, this.baseUrl).toString() : path;\n if (!params) return base;\n const url = new URL(base);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n return url.toString();\n }\n\n private async buildHeaders(\n method: string,\n url: string,\n options: ApiRequestOptions<unknown>,\n ): Promise<Record<string, string>> {\n let headers: Record<string, string> = { ...this.defaultHeaders, ...options.headers };\n if (!options.skipAuth) {\n const token = await this.tokenProvider.getToken();\n if (token) headers = { ...headers, Authorization: `Bearer ${token}` };\n }\n const overridden = await this.interceptors.onRequest?.({ method, url, headers });\n return overridden ?? headers;\n }\n\n private async parseBody<T>(response: APIResponse, options: ApiRequestOptions<T>): Promise<T> {\n const raw = await this.readBody(response);\n\n if (!options.schema) return raw as T;\n\n const result = options.schema.safeParse(raw);\n if (!result.success) {\n throw new ApiError(`API response did not match the expected schema: ${response.url()}`, {\n code: \"API_SCHEMA_MISMATCH\",\n status: response.status(),\n url: response.url(),\n context: { issues: result.error.issues, body: raw },\n });\n }\n return result.data;\n }\n\n private async readBody(response: APIResponse): Promise<unknown> {\n const contentType = response.headers()[\"content-type\"] ?? \"\";\n if (contentType.includes(\"application/json\")) {\n // A JSON content-type with an empty body (some 204s) would otherwise throw on parse.\n const text = await response.text();\n if (text.trim() === \"\") return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n return response.text();\n }\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { T as TokenProvider } from './types-CopwwpSW.js';
|
|
3
|
+
import { R as RetryOptions } from './retry-XqnSC8YL.js';
|
|
4
|
+
import { Logger } from 'pino';
|
|
5
|
+
import { A as ApiClientInterceptors, a as ApiRequestOptions, b as ApiResponse } from './types-hInbkPXf.js';
|
|
6
|
+
|
|
7
|
+
interface ApiClientOptions {
|
|
8
|
+
/** Playwright's `APIRequestContext` (from `request` fixture or `context.request`). */
|
|
9
|
+
request: APIRequestContext;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
tokenProvider?: TokenProvider;
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
retry?: Partial<RetryOptions>;
|
|
14
|
+
defaultHeaders?: Record<string, string>;
|
|
15
|
+
interceptors?: ApiClientInterceptors;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thin, project-agnostic abstraction over Playwright's `APIRequestContext`:
|
|
19
|
+
* base URL resolution, bearer-token injection, structured error mapping, and
|
|
20
|
+
* retry-on-5xx/429. Projects extend this for domain-specific API clients
|
|
21
|
+
* instead of calling `request.fetch` directly in tests.
|
|
22
|
+
*
|
|
23
|
+
* Two levels of API, on purpose:
|
|
24
|
+
* - `get`/`post`/... return the body and THROW `ApiError` on an unexpected status — the right
|
|
25
|
+
* default for arranging test data, where a failed call should stop the test loudly.
|
|
26
|
+
* - `send()` returns the full `ApiResponse` (status, headers, body) and never throws for an
|
|
27
|
+
* HTTP status — for tests that assert on error responses themselves.
|
|
28
|
+
*/
|
|
29
|
+
declare class ApiClient {
|
|
30
|
+
private readonly request;
|
|
31
|
+
private readonly baseUrl?;
|
|
32
|
+
private readonly tokenProvider;
|
|
33
|
+
private readonly logger?;
|
|
34
|
+
private readonly retryOptions;
|
|
35
|
+
private readonly defaultHeaders;
|
|
36
|
+
private readonly interceptors;
|
|
37
|
+
constructor(options: ApiClientOptions);
|
|
38
|
+
get<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
39
|
+
post<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
40
|
+
put<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
41
|
+
patch<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
42
|
+
delete<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Full response including status and headers. Retries transient statuses, then returns
|
|
45
|
+
* whatever came back — a 404 or 500 is a normal return value here, not an exception.
|
|
46
|
+
* Network/timeout failures still throw.
|
|
47
|
+
*/
|
|
48
|
+
send<T = unknown>(method: string, path: string, options?: ApiRequestOptions<T>): Promise<ApiResponse<T>>;
|
|
49
|
+
private requestBody;
|
|
50
|
+
private isExpectedStatus;
|
|
51
|
+
private fetchOnce;
|
|
52
|
+
private resolveUrl;
|
|
53
|
+
private buildHeaders;
|
|
54
|
+
private parseBody;
|
|
55
|
+
private readBody;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { ApiClient as A, type ApiClientOptions as a };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { APIRequestContext } from '@playwright/test';
|
|
2
|
+
import { T as TokenProvider } from './types-CopwwpSW.cjs';
|
|
3
|
+
import { R as RetryOptions } from './retry-XqnSC8YL.cjs';
|
|
4
|
+
import { Logger } from 'pino';
|
|
5
|
+
import { A as ApiClientInterceptors, a as ApiRequestOptions, b as ApiResponse } from './types-hInbkPXf.cjs';
|
|
6
|
+
|
|
7
|
+
interface ApiClientOptions {
|
|
8
|
+
/** Playwright's `APIRequestContext` (from `request` fixture or `context.request`). */
|
|
9
|
+
request: APIRequestContext;
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
tokenProvider?: TokenProvider;
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
retry?: Partial<RetryOptions>;
|
|
14
|
+
defaultHeaders?: Record<string, string>;
|
|
15
|
+
interceptors?: ApiClientInterceptors;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thin, project-agnostic abstraction over Playwright's `APIRequestContext`:
|
|
19
|
+
* base URL resolution, bearer-token injection, structured error mapping, and
|
|
20
|
+
* retry-on-5xx/429. Projects extend this for domain-specific API clients
|
|
21
|
+
* instead of calling `request.fetch` directly in tests.
|
|
22
|
+
*
|
|
23
|
+
* Two levels of API, on purpose:
|
|
24
|
+
* - `get`/`post`/... return the body and THROW `ApiError` on an unexpected status — the right
|
|
25
|
+
* default for arranging test data, where a failed call should stop the test loudly.
|
|
26
|
+
* - `send()` returns the full `ApiResponse` (status, headers, body) and never throws for an
|
|
27
|
+
* HTTP status — for tests that assert on error responses themselves.
|
|
28
|
+
*/
|
|
29
|
+
declare class ApiClient {
|
|
30
|
+
private readonly request;
|
|
31
|
+
private readonly baseUrl?;
|
|
32
|
+
private readonly tokenProvider;
|
|
33
|
+
private readonly logger?;
|
|
34
|
+
private readonly retryOptions;
|
|
35
|
+
private readonly defaultHeaders;
|
|
36
|
+
private readonly interceptors;
|
|
37
|
+
constructor(options: ApiClientOptions);
|
|
38
|
+
get<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
39
|
+
post<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
40
|
+
put<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
41
|
+
patch<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
42
|
+
delete<T = unknown>(path: string, options?: ApiRequestOptions<T>): Promise<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Full response including status and headers. Retries transient statuses, then returns
|
|
45
|
+
* whatever came back — a 404 or 500 is a normal return value here, not an exception.
|
|
46
|
+
* Network/timeout failures still throw.
|
|
47
|
+
*/
|
|
48
|
+
send<T = unknown>(method: string, path: string, options?: ApiRequestOptions<T>): Promise<ApiResponse<T>>;
|
|
49
|
+
private requestBody;
|
|
50
|
+
private isExpectedStatus;
|
|
51
|
+
private fetchOnce;
|
|
52
|
+
private resolveUrl;
|
|
53
|
+
private buildHeaders;
|
|
54
|
+
private parseBody;
|
|
55
|
+
private readBody;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { ApiClient as A, type ApiClientOptions as a };
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var test = require('@playwright/test');
|
|
6
|
+
var zod = require('zod');
|
|
7
|
+
|
|
8
|
+
// src/errors/app-error.ts
|
|
9
|
+
var AppError = class extends Error {
|
|
10
|
+
code;
|
|
11
|
+
context;
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, { cause: options.cause });
|
|
14
|
+
this.name = new.target.name;
|
|
15
|
+
this.code = options.code;
|
|
16
|
+
this.context = options.context;
|
|
17
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var ConfigValidationError = class extends AppError {
|
|
21
|
+
constructor(message, issues, context) {
|
|
22
|
+
super(message, { code: "CONFIG_VALIDATION_ERROR", context: { issues, ...context } });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var AuthError = class extends AppError {
|
|
26
|
+
constructor(message, options = {}) {
|
|
27
|
+
super(message, { code: "AUTH_ERROR", ...options });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/auth/token-provider.ts
|
|
32
|
+
var StaticTokenProvider = class {
|
|
33
|
+
constructor(token) {
|
|
34
|
+
this.token = token;
|
|
35
|
+
}
|
|
36
|
+
token;
|
|
37
|
+
async getToken() {
|
|
38
|
+
return this.token;
|
|
39
|
+
}
|
|
40
|
+
invalidate() {
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var NullTokenProvider = class {
|
|
44
|
+
async getToken() {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
invalidate() {
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var JwtTokenProvider = class {
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.options = options;
|
|
53
|
+
}
|
|
54
|
+
options;
|
|
55
|
+
cached;
|
|
56
|
+
inFlight;
|
|
57
|
+
async getToken() {
|
|
58
|
+
if (this.cached && this.cached.expiresAt > Date.now()) {
|
|
59
|
+
return this.cached.token;
|
|
60
|
+
}
|
|
61
|
+
if (!this.inFlight) {
|
|
62
|
+
this.inFlight = this.fetchToken().finally(() => {
|
|
63
|
+
this.inFlight = void 0;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return this.inFlight;
|
|
67
|
+
}
|
|
68
|
+
invalidate() {
|
|
69
|
+
this.cached = void 0;
|
|
70
|
+
}
|
|
71
|
+
async fetchToken() {
|
|
72
|
+
const { tokenUrl, clientId, clientSecret, username, password, refreshSkewSeconds = 30, logger, fetchImpl = fetch } = this.options;
|
|
73
|
+
const body = new URLSearchParams();
|
|
74
|
+
if (username && password) {
|
|
75
|
+
body.set("grant_type", "password");
|
|
76
|
+
body.set("username", username);
|
|
77
|
+
body.set("password", password);
|
|
78
|
+
} else {
|
|
79
|
+
body.set("grant_type", "client_credentials");
|
|
80
|
+
}
|
|
81
|
+
if (clientId) body.set("client_id", clientId);
|
|
82
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
83
|
+
logger?.debug({ tokenUrl }, "fetching auth token");
|
|
84
|
+
let response;
|
|
85
|
+
try {
|
|
86
|
+
response = await fetchImpl(tokenUrl, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
89
|
+
body
|
|
90
|
+
});
|
|
91
|
+
} catch (cause) {
|
|
92
|
+
throw new AuthError(`Failed to reach token endpoint ${tokenUrl}`, { cause });
|
|
93
|
+
}
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const text = await response.text().catch(() => "");
|
|
96
|
+
throw new AuthError(`Token endpoint returned ${response.status}: ${text}`, {
|
|
97
|
+
context: { status: response.status, tokenUrl }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const data = await response.json();
|
|
101
|
+
if (!data.access_token) {
|
|
102
|
+
throw new AuthError("Token endpoint response missing access_token", { context: { tokenUrl } });
|
|
103
|
+
}
|
|
104
|
+
const expiresInSeconds = data.expires_in ?? 300;
|
|
105
|
+
this.cached = {
|
|
106
|
+
token: data.access_token,
|
|
107
|
+
expiresAt: Date.now() + Math.max(expiresInSeconds - refreshSkewSeconds, 0) * 1e3
|
|
108
|
+
};
|
|
109
|
+
return this.cached.token;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/utils/deep-merge.ts
|
|
114
|
+
function isPlainObject(value) {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
+
}
|
|
117
|
+
function deepMerge(base, override) {
|
|
118
|
+
if (override === void 0) return base;
|
|
119
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
120
|
+
return override;
|
|
121
|
+
}
|
|
122
|
+
const result = { ...base };
|
|
123
|
+
for (const [key, value] of Object.entries(override)) {
|
|
124
|
+
if (value === void 0) continue;
|
|
125
|
+
const baseValue = base[key];
|
|
126
|
+
result[key] = isPlainObject(baseValue) && isPlainObject(value) ? deepMerge(baseValue, value) : value;
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function pruneUndefined(value) {
|
|
131
|
+
if (Array.isArray(value)) return value.map(pruneUndefined);
|
|
132
|
+
if (isPlainObject(value)) {
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [key, val] of Object.entries(value)) {
|
|
135
|
+
if (val === void 0) continue;
|
|
136
|
+
const pruned = pruneUndefined(val);
|
|
137
|
+
if (isPlainObject(pruned) && Object.keys(pruned).length === 0) continue;
|
|
138
|
+
result[key] = pruned;
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
var EnvironmentSchema = zod.z.string().min(1);
|
|
145
|
+
var TimeoutsSchema = zod.z.object({
|
|
146
|
+
actionMs: zod.z.number().int().positive().default(15e3),
|
|
147
|
+
navigationMs: zod.z.number().int().positive().default(3e4),
|
|
148
|
+
testMs: zod.z.number().int().positive().default(6e4),
|
|
149
|
+
/** Default budget for `waitForCondition` and other non-locator polling. */
|
|
150
|
+
waitMs: zod.z.number().int().positive().default(3e4)
|
|
151
|
+
});
|
|
152
|
+
var AuthConfigSchema = zod.z.object({
|
|
153
|
+
tokenUrl: zod.z.string().url().optional(),
|
|
154
|
+
clientId: zod.z.string().optional(),
|
|
155
|
+
clientSecret: zod.z.string().optional(),
|
|
156
|
+
username: zod.z.string().optional(),
|
|
157
|
+
password: zod.z.string().optional(),
|
|
158
|
+
/** Single-session storage state file (see `buildGlobalSetup`). */
|
|
159
|
+
storageStatePath: zod.z.string().optional(),
|
|
160
|
+
/** Directory used for per-role storage state files in multi-role setups. */
|
|
161
|
+
storageStateDir: zod.z.string().default(".auth")
|
|
162
|
+
}).default({});
|
|
163
|
+
var ReportingConfigSchema = zod.z.object({
|
|
164
|
+
allureEnabled: zod.z.boolean().default(false),
|
|
165
|
+
resultsDir: zod.z.string().default("allure-results"),
|
|
166
|
+
/** `{id}` is replaced with a test-case id — see `meta()` in `@emesoft_company/playwright-core/reporting`. */
|
|
167
|
+
testCaseUrlTemplate: zod.z.string().optional()
|
|
168
|
+
}).default({});
|
|
169
|
+
var RetryConfigSchema = zod.z.object({
|
|
170
|
+
retries: zod.z.number().int().min(0).default(2),
|
|
171
|
+
minDelayMs: zod.z.number().int().positive().default(200),
|
|
172
|
+
maxDelayMs: zod.z.number().int().positive().default(5e3)
|
|
173
|
+
}).default({});
|
|
174
|
+
var DiagnosticsConfigSchema = zod.z.object({
|
|
175
|
+
/** Collect browser console errors + failed requests and attach them to the report. */
|
|
176
|
+
enabled: zod.z.boolean().default(true),
|
|
177
|
+
/** Fail the test if the page logged a console error. Off by default — many apps are noisy. */
|
|
178
|
+
failOnConsoleError: zod.z.boolean().default(false),
|
|
179
|
+
/** Fail the test on an uncaught page exception. */
|
|
180
|
+
failOnPageError: zod.z.boolean().default(false),
|
|
181
|
+
/** Console messages matching any of these substrings are ignored entirely. */
|
|
182
|
+
ignoreConsolePatterns: zod.z.array(zod.z.string()).default([])
|
|
183
|
+
}).default({});
|
|
184
|
+
var AppConfigSchema = zod.z.object({
|
|
185
|
+
environment: EnvironmentSchema,
|
|
186
|
+
baseUrl: zod.z.string().url(),
|
|
187
|
+
apiBaseUrl: zod.z.string().url().optional(),
|
|
188
|
+
tenant: zod.z.string().optional(),
|
|
189
|
+
timeouts: TimeoutsSchema.default({}),
|
|
190
|
+
auth: AuthConfigSchema,
|
|
191
|
+
reporting: ReportingConfigSchema,
|
|
192
|
+
retry: RetryConfigSchema,
|
|
193
|
+
diagnostics: DiagnosticsConfigSchema,
|
|
194
|
+
logLevel: zod.z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]).default("info"),
|
|
195
|
+
custom: zod.z.record(zod.z.string(), zod.z.unknown()).default({})
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// src/config/loader.ts
|
|
199
|
+
function readEnvConfig(env) {
|
|
200
|
+
return {
|
|
201
|
+
environment: env.TEST_ENV ?? "dev",
|
|
202
|
+
baseUrl: env.BASE_URL,
|
|
203
|
+
apiBaseUrl: env.API_BASE_URL,
|
|
204
|
+
tenant: env.TENANT,
|
|
205
|
+
logLevel: env.LOG_LEVEL,
|
|
206
|
+
timeouts: {
|
|
207
|
+
actionMs: env.ACTION_TIMEOUT_MS ? Number(env.ACTION_TIMEOUT_MS) : void 0,
|
|
208
|
+
navigationMs: env.NAVIGATION_TIMEOUT_MS ? Number(env.NAVIGATION_TIMEOUT_MS) : void 0,
|
|
209
|
+
testMs: env.TEST_TIMEOUT_MS ? Number(env.TEST_TIMEOUT_MS) : void 0,
|
|
210
|
+
waitMs: env.WAIT_TIMEOUT_MS ? Number(env.WAIT_TIMEOUT_MS) : void 0
|
|
211
|
+
},
|
|
212
|
+
auth: {
|
|
213
|
+
tokenUrl: env.AUTH_TOKEN_URL,
|
|
214
|
+
clientId: env.AUTH_CLIENT_ID,
|
|
215
|
+
clientSecret: env.AUTH_CLIENT_SECRET,
|
|
216
|
+
username: env.AUTH_USERNAME,
|
|
217
|
+
password: env.AUTH_PASSWORD,
|
|
218
|
+
storageStatePath: env.AUTH_STORAGE_STATE_PATH,
|
|
219
|
+
storageStateDir: env.AUTH_STORAGE_STATE_DIR
|
|
220
|
+
},
|
|
221
|
+
reporting: {
|
|
222
|
+
allureEnabled: env.REPORT_ALLURE === "true",
|
|
223
|
+
resultsDir: env.ALLURE_RESULTS_DIR,
|
|
224
|
+
testCaseUrlTemplate: env.TEST_CASE_URL_TEMPLATE
|
|
225
|
+
},
|
|
226
|
+
retry: {
|
|
227
|
+
retries: env.RETRY_COUNT ? Number(env.RETRY_COUNT) : void 0,
|
|
228
|
+
minDelayMs: env.RETRY_MIN_DELAY_MS ? Number(env.RETRY_MIN_DELAY_MS) : void 0,
|
|
229
|
+
maxDelayMs: env.RETRY_MAX_DELAY_MS ? Number(env.RETRY_MAX_DELAY_MS) : void 0
|
|
230
|
+
},
|
|
231
|
+
diagnostics: {
|
|
232
|
+
enabled: env.DIAGNOSTICS === void 0 ? void 0 : env.DIAGNOSTICS !== "false",
|
|
233
|
+
failOnConsoleError: env.FAIL_ON_CONSOLE_ERROR === void 0 ? void 0 : env.FAIL_ON_CONSOLE_ERROR === "true",
|
|
234
|
+
failOnPageError: env.FAIL_ON_PAGE_ERROR === void 0 ? void 0 : env.FAIL_ON_PAGE_ERROR === "true"
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function loadConfig(options = {}) {
|
|
239
|
+
const { env = process.env, overrides } = options;
|
|
240
|
+
const fromEnv = pruneUndefined(readEnvConfig(env));
|
|
241
|
+
const merged = deepMerge(fromEnv, overrides);
|
|
242
|
+
const result = AppConfigSchema.safeParse(merged);
|
|
243
|
+
if (!result.success) {
|
|
244
|
+
throw new ConfigValidationError(
|
|
245
|
+
`Invalid application config: ${result.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ")}`,
|
|
246
|
+
result.error.issues
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return result.data;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/auth/global-setup.ts
|
|
253
|
+
function roleStorageState(role, storageStateDir = process.env.AUTH_STORAGE_STATE_DIR ?? ".auth") {
|
|
254
|
+
return path.join(storageStateDir, `${role}.json`);
|
|
255
|
+
}
|
|
256
|
+
function buildGlobalSetup(options) {
|
|
257
|
+
if (!options.login && !options.roles) {
|
|
258
|
+
throw new AppError("buildGlobalSetup requires either `login` or `roles`", {
|
|
259
|
+
code: "GLOBAL_SETUP_NO_LOGIN"
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return async function globalSetup() {
|
|
263
|
+
const config = loadConfig({ overrides: options.configOverrides });
|
|
264
|
+
const sessions = resolveSessions(options, config);
|
|
265
|
+
const browser = await test.chromium.launch(options.launchOptions);
|
|
266
|
+
try {
|
|
267
|
+
for (const session of sessions) {
|
|
268
|
+
fs.mkdirSync(path.dirname(session.storageStatePath), { recursive: true });
|
|
269
|
+
const context = await browser.newContext({
|
|
270
|
+
baseURL: config.baseUrl,
|
|
271
|
+
...session.contextOptions ?? options.contextOptions
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
const page = await context.newPage();
|
|
275
|
+
await session.login(page, config);
|
|
276
|
+
await context.storageState({ path: session.storageStatePath });
|
|
277
|
+
} finally {
|
|
278
|
+
await context.close();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} finally {
|
|
282
|
+
await browser.close();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function resolveSessions(options, config) {
|
|
287
|
+
if (options.roles) {
|
|
288
|
+
return Object.entries(options.roles).map(([role, setup]) => ({
|
|
289
|
+
login: setup.login,
|
|
290
|
+
storageStatePath: setup.storageStatePath ?? roleStorageState(role, config.auth.storageStateDir),
|
|
291
|
+
contextOptions: setup.contextOptions
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
const storageStatePath = config.auth.storageStatePath;
|
|
295
|
+
if (!storageStatePath) {
|
|
296
|
+
throw new AppError(
|
|
297
|
+
"buildGlobalSetup requires `auth.storageStatePath` to be set (env AUTH_STORAGE_STATE_PATH or a config override) when using single-session `login`",
|
|
298
|
+
{ code: "MISSING_STORAGE_STATE_PATH" }
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return [{ login: options.login, storageStatePath }];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
exports.JwtTokenProvider = JwtTokenProvider;
|
|
305
|
+
exports.NullTokenProvider = NullTokenProvider;
|
|
306
|
+
exports.StaticTokenProvider = StaticTokenProvider;
|
|
307
|
+
exports.buildGlobalSetup = buildGlobalSetup;
|
|
308
|
+
exports.roleStorageState = roleStorageState;
|
|
309
|
+
//# sourceMappingURL=index.cjs.map
|
|
310
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/errors/app-error.ts","../../src/auth/token-provider.ts","../../src/utils/deep-merge.ts","../../src/config/schema.ts","../../src/config/loader.ts","../../src/auth/global-setup.ts"],"names":["z","join","chromium","mkdirSync","dirname"],"mappings":"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","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 { AuthError } from \"../errors/app-error.js\";\nimport type { Logger } from \"../logging/logger.js\";\nimport type { TokenProvider } from \"./types.js\";\n\n/** Token provider that always returns a fixed token. Useful for tests/local dev with a pre-issued token. */\nexport class StaticTokenProvider implements TokenProvider {\n constructor(private readonly token: string) {}\n\n async getToken(): Promise<string> {\n return this.token;\n }\n\n invalidate(): void {\n // no-op: nothing to invalidate for a static token\n }\n}\n\n/** No-op provider for endpoints that don't require authentication. */\nexport class NullTokenProvider implements TokenProvider {\n async getToken(): Promise<string> {\n return \"\";\n }\n\n invalidate(): void {\n // no-op\n }\n}\n\nexport interface JwtTokenProviderOptions {\n tokenUrl: string;\n clientId?: string;\n clientSecret?: string;\n username?: string;\n password?: string;\n /** Seconds subtracted from the token's `expires_in` to refresh proactively. Default: 30. */\n refreshSkewSeconds?: number;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\n/**\n * JWT/OAuth2 token provider supporting client-credentials or password grants.\n * Caches the token in-memory and transparently refreshes it before expiry.\n */\nexport class JwtTokenProvider implements TokenProvider {\n private cached?: CachedToken;\n private inFlight?: Promise<string>;\n\n constructor(private readonly options: JwtTokenProviderOptions) {}\n\n async getToken(): Promise<string> {\n if (this.cached && this.cached.expiresAt > Date.now()) {\n return this.cached.token;\n }\n if (!this.inFlight) {\n this.inFlight = this.fetchToken().finally(() => {\n this.inFlight = undefined;\n });\n }\n return this.inFlight;\n }\n\n invalidate(): void {\n this.cached = undefined;\n }\n\n private async fetchToken(): Promise<string> {\n const { tokenUrl, clientId, clientSecret, username, password, refreshSkewSeconds = 30, logger, fetchImpl = fetch } = this.options;\n\n const body = new URLSearchParams();\n if (username && password) {\n body.set(\"grant_type\", \"password\");\n body.set(\"username\", username);\n body.set(\"password\", password);\n } else {\n body.set(\"grant_type\", \"client_credentials\");\n }\n if (clientId) body.set(\"client_id\", clientId);\n if (clientSecret) body.set(\"client_secret\", clientSecret);\n\n logger?.debug({ tokenUrl }, \"fetching auth token\");\n\n let response: Response;\n try {\n response = await fetchImpl(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n } catch (cause) {\n throw new AuthError(`Failed to reach token endpoint ${tokenUrl}`, { cause });\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new AuthError(`Token endpoint returned ${response.status}: ${text}`, {\n context: { status: response.status, tokenUrl },\n });\n }\n\n const data = (await response.json()) as { access_token?: string; expires_in?: number };\n if (!data.access_token) {\n throw new AuthError(\"Token endpoint response missing access_token\", { context: { tokenUrl } });\n }\n\n const expiresInSeconds = data.expires_in ?? 300;\n this.cached = {\n token: data.access_token,\n expiresAt: Date.now() + Math.max(expiresInSeconds - refreshSkewSeconds, 0) * 1000,\n };\n return this.cached.token;\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 { mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { chromium, type BrowserContextOptions, type Page } from \"@playwright/test\";\nimport { loadConfig } from \"../config/loader.js\";\nimport type { AppConfig } from \"../config/schema.js\";\nimport type { DeepPartial } from \"../config/types.js\";\nimport { AppError } from \"../errors/app-error.js\";\n\nexport type LoginFn = (page: Page, config: AppConfig) => Promise<void>;\n\nexport interface RoleSetup {\n /** Performs this role's login; storage state is captured right after it returns. */\n login: LoginFn;\n /** Defaults to `<auth.storageStateDir>/<role>.json`. */\n storageStatePath?: string;\n /** Per-role context options, falling back to the top-level `contextOptions`. */\n contextOptions?: BrowserContextOptions;\n}\n\nexport interface GlobalSetupOptions {\n /**\n * Single-session login. Storage state goes to `auth.storageStatePath`.\n * Mutually exclusive with `roles`.\n */\n login?: LoginFn;\n /**\n * Multi-role login — one storage state file per role, so tests can pick who they run as\n * with `test.use({ storageState: roleStorageState(\"admin\") })`.\n */\n roles?: Record<string, RoleSetup>;\n configOverrides?: DeepPartial<AppConfig>;\n /**\n * Context options (e.g. `devices[\"Desktop Chrome\"]`) to log in with. Pass the SAME options\n * your test projects run with — some apps tie a session to fingerprint details like\n * User-Agent, so logging in under different context options than the tests that reuse the\n * session can silently invalidate it server-side.\n */\n contextOptions?: BrowserContextOptions;\n /**\n * Options for `chromium.launch()` — e.g. `{ headless: false }`. Defaults to Playwright's own\n * default (headless). Some apps never finish loading/rendering in headless mode (confirmed\n * for real against a staging target in `bec.e2e`); pass `headless: false` here instead of\n * writing a custom `global-setup.ts` from scratch just to change this one thing.\n */\n launchOptions?: Parameters<typeof chromium.launch>[0];\n}\n\n/**\n * Path of a role's storage state file. Use the same role names here as in\n * `buildGlobalSetup({ roles })` so tests and setup agree:\n *\n * ```ts\n * test.use({ storageState: roleStorageState(\"admin\") });\n * ```\n */\nexport function roleStorageState(\n role: string,\n storageStateDir: string = process.env.AUTH_STORAGE_STATE_DIR ?? \".auth\",\n): string {\n return join(storageStateDir, `${role}.json`);\n}\n\n/**\n * Builds a Playwright `globalSetup` function that logs in once and persists the resulting\n * storage state, so per-test runs reuse an authenticated session instead of logging in for\n * every test. Wire the returned function into `playwright.config.ts`'s `globalSetup` option.\n * See `docs/EXTENDING.md`.\n */\nexport function buildGlobalSetup(options: GlobalSetupOptions) {\n if (!options.login && !options.roles) {\n throw new AppError(\"buildGlobalSetup requires either `login` or `roles`\", {\n code: \"GLOBAL_SETUP_NO_LOGIN\",\n });\n }\n\n return async function globalSetup(): Promise<void> {\n const config = loadConfig({ overrides: options.configOverrides });\n const sessions = resolveSessions(options, config);\n\n const browser = await chromium.launch(options.launchOptions);\n try {\n // Sequential on purpose: parallel logins to the same app can trip rate limits or\n // invalidate each other's sessions on single-session-per-user backends.\n for (const session of sessions) {\n mkdirSync(dirname(session.storageStatePath), { recursive: true });\n const context = await browser.newContext({\n baseURL: config.baseUrl,\n ...(session.contextOptions ?? options.contextOptions),\n });\n try {\n const page = await context.newPage();\n await session.login(page, config);\n await context.storageState({ path: session.storageStatePath });\n } finally {\n await context.close();\n }\n }\n } finally {\n await browser.close();\n }\n };\n}\n\ninterface ResolvedSession {\n login: LoginFn;\n storageStatePath: string;\n contextOptions?: BrowserContextOptions;\n}\n\nfunction resolveSessions(options: GlobalSetupOptions, config: AppConfig): ResolvedSession[] {\n if (options.roles) {\n return Object.entries(options.roles).map(([role, setup]) => ({\n login: setup.login,\n storageStatePath: setup.storageStatePath ?? roleStorageState(role, config.auth.storageStateDir),\n contextOptions: setup.contextOptions,\n }));\n }\n\n const storageStatePath = config.auth.storageStatePath;\n if (!storageStatePath) {\n throw new AppError(\n \"buildGlobalSetup requires `auth.storageStatePath` to be set (env AUTH_STORAGE_STATE_PATH or a config override) when using single-session `login`\",\n { code: \"MISSING_STORAGE_STATE_PATH\" },\n );\n }\n return [{ login: options.login as LoginFn, storageStatePath }];\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { T as TokenProvider } from '../types-CopwwpSW.cjs';
|
|
2
|
+
import { Logger } from 'pino';
|
|
3
|
+
import { Page, BrowserContextOptions, chromium } from '@playwright/test';
|
|
4
|
+
import { A as AppConfig } from '../schema-y5w2hbT-.cjs';
|
|
5
|
+
import { D as DeepPartial } from '../types-CbWnSYEK.cjs';
|
|
6
|
+
import 'zod';
|
|
7
|
+
|
|
8
|
+
/** Token provider that always returns a fixed token. Useful for tests/local dev with a pre-issued token. */
|
|
9
|
+
declare class StaticTokenProvider implements TokenProvider {
|
|
10
|
+
private readonly token;
|
|
11
|
+
constructor(token: string);
|
|
12
|
+
getToken(): Promise<string>;
|
|
13
|
+
invalidate(): void;
|
|
14
|
+
}
|
|
15
|
+
/** No-op provider for endpoints that don't require authentication. */
|
|
16
|
+
declare class NullTokenProvider implements TokenProvider {
|
|
17
|
+
getToken(): Promise<string>;
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
}
|
|
20
|
+
interface JwtTokenProviderOptions {
|
|
21
|
+
tokenUrl: string;
|
|
22
|
+
clientId?: string;
|
|
23
|
+
clientSecret?: string;
|
|
24
|
+
username?: string;
|
|
25
|
+
password?: string;
|
|
26
|
+
/** Seconds subtracted from the token's `expires_in` to refresh proactively. Default: 30. */
|
|
27
|
+
refreshSkewSeconds?: number;
|
|
28
|
+
logger?: Logger;
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* JWT/OAuth2 token provider supporting client-credentials or password grants.
|
|
33
|
+
* Caches the token in-memory and transparently refreshes it before expiry.
|
|
34
|
+
*/
|
|
35
|
+
declare class JwtTokenProvider implements TokenProvider {
|
|
36
|
+
private readonly options;
|
|
37
|
+
private cached?;
|
|
38
|
+
private inFlight?;
|
|
39
|
+
constructor(options: JwtTokenProviderOptions);
|
|
40
|
+
getToken(): Promise<string>;
|
|
41
|
+
invalidate(): void;
|
|
42
|
+
private fetchToken;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type LoginFn = (page: Page, config: AppConfig) => Promise<void>;
|
|
46
|
+
interface RoleSetup {
|
|
47
|
+
/** Performs this role's login; storage state is captured right after it returns. */
|
|
48
|
+
login: LoginFn;
|
|
49
|
+
/** Defaults to `<auth.storageStateDir>/<role>.json`. */
|
|
50
|
+
storageStatePath?: string;
|
|
51
|
+
/** Per-role context options, falling back to the top-level `contextOptions`. */
|
|
52
|
+
contextOptions?: BrowserContextOptions;
|
|
53
|
+
}
|
|
54
|
+
interface GlobalSetupOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Single-session login. Storage state goes to `auth.storageStatePath`.
|
|
57
|
+
* Mutually exclusive with `roles`.
|
|
58
|
+
*/
|
|
59
|
+
login?: LoginFn;
|
|
60
|
+
/**
|
|
61
|
+
* Multi-role login — one storage state file per role, so tests can pick who they run as
|
|
62
|
+
* with `test.use({ storageState: roleStorageState("admin") })`.
|
|
63
|
+
*/
|
|
64
|
+
roles?: Record<string, RoleSetup>;
|
|
65
|
+
configOverrides?: DeepPartial<AppConfig>;
|
|
66
|
+
/**
|
|
67
|
+
* Context options (e.g. `devices["Desktop Chrome"]`) to log in with. Pass the SAME options
|
|
68
|
+
* your test projects run with — some apps tie a session to fingerprint details like
|
|
69
|
+
* User-Agent, so logging in under different context options than the tests that reuse the
|
|
70
|
+
* session can silently invalidate it server-side.
|
|
71
|
+
*/
|
|
72
|
+
contextOptions?: BrowserContextOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Options for `chromium.launch()` — e.g. `{ headless: false }`. Defaults to Playwright's own
|
|
75
|
+
* default (headless). Some apps never finish loading/rendering in headless mode (confirmed
|
|
76
|
+
* for real against a staging target in `bec.e2e`); pass `headless: false` here instead of
|
|
77
|
+
* writing a custom `global-setup.ts` from scratch just to change this one thing.
|
|
78
|
+
*/
|
|
79
|
+
launchOptions?: Parameters<typeof chromium.launch>[0];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Path of a role's storage state file. Use the same role names here as in
|
|
83
|
+
* `buildGlobalSetup({ roles })` so tests and setup agree:
|
|
84
|
+
*
|
|
85
|
+
* ```ts
|
|
86
|
+
* test.use({ storageState: roleStorageState("admin") });
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
declare function roleStorageState(role: string, storageStateDir?: string): string;
|
|
90
|
+
/**
|
|
91
|
+
* Builds a Playwright `globalSetup` function that logs in once and persists the resulting
|
|
92
|
+
* storage state, so per-test runs reuse an authenticated session instead of logging in for
|
|
93
|
+
* every test. Wire the returned function into `playwright.config.ts`'s `globalSetup` option.
|
|
94
|
+
* See `docs/EXTENDING.md`.
|
|
95
|
+
*/
|
|
96
|
+
declare function buildGlobalSetup(options: GlobalSetupOptions): () => Promise<void>;
|
|
97
|
+
|
|
98
|
+
export { type GlobalSetupOptions, JwtTokenProvider, type JwtTokenProviderOptions, type LoginFn, NullTokenProvider, type RoleSetup, StaticTokenProvider, TokenProvider, buildGlobalSetup, roleStorageState };
|