@elench/testkit 0.1.132 → 0.1.134

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 (27) hide show
  1. package/lib/cli/commands/guard/unsafe-entrypoint.mjs +27 -0
  2. package/lib/cli/entrypoint.mjs +3 -0
  3. package/lib/env/index.d.ts +27 -0
  4. package/lib/env/index.mjs +54 -0
  5. package/lib/runtime/index.d.ts +16 -4
  6. package/lib/runtime-src/k6/http-assertions.js +73 -14
  7. package/lib/runtime-src/shared/assertion-details.mjs +98 -0
  8. package/lib/ui/index.d.ts +13 -0
  9. package/lib/ui/index.mjs +3 -0
  10. package/lib/ui/provisioning.mjs +19 -11
  11. package/lib/ui/sandbox.mjs +47 -1
  12. package/node_modules/@elench/next-analysis/package.json +1 -1
  13. package/node_modules/@elench/testkit-bridge/package.json +2 -2
  14. package/node_modules/@elench/testkit-protocol/package.json +1 -1
  15. package/node_modules/@elench/ts-analysis/package.json +1 -1
  16. package/package.json +5 -5
  17. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts +188 -0
  18. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.d.ts.map +1 -0
  19. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js +293 -0
  20. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/dist/index.js.map +1 -0
  21. package/packages/testkit-bridge/node_modules/@elench/testkit-protocol/package.json +25 -0
  22. package/node_modules/es-toolkit/CHANGELOG.md +0 -801
  23. package/node_modules/es-toolkit/src/compat/_internal/Equals.d.ts +0 -1
  24. package/node_modules/es-toolkit/src/compat/_internal/IsWritable.d.ts +0 -3
  25. package/node_modules/es-toolkit/src/compat/_internal/MutableList.d.ts +0 -4
  26. package/node_modules/es-toolkit/src/compat/_internal/RejectReadonly.d.ts +0 -4
  27. package/node_modules/esprima/ChangeLog +0 -235
@@ -0,0 +1,27 @@
1
+ import { Command, Flags } from "@oclif/core";
2
+
3
+ export default class GuardUnsafeEntrypointCommand extends Command {
4
+ static summary = "Fail a local script that must be run through Testkit";
5
+
6
+ static flags = {
7
+ command: Flags.string({
8
+ char: "c",
9
+ description: "Disabled command name shown in the error message",
10
+ required: true,
11
+ }),
12
+ use: Flags.string({
13
+ char: "u",
14
+ description: "Replacement command shown in the error message",
15
+ required: true,
16
+ }),
17
+ };
18
+
19
+ async run() {
20
+ const { flags } = await this.parse(GuardUnsafeEntrypointCommand);
21
+ this.error(
22
+ `${flags.command} is disabled because DB-touching tests must run through the isolated testkit harness.\n` +
23
+ `Use ${flags.use} instead.`,
24
+ { exit: 1 }
25
+ );
26
+ }
27
+ }
@@ -16,6 +16,7 @@ export function normalizeCliArgs(argv) {
16
16
  "lint",
17
17
  "browser",
18
18
  "db",
19
+ "guard",
19
20
  ]);
20
21
  const runTypeShortcuts = new Set(publicTestTypeList({ includeAll: true }));
21
22
  const valueFlags = new Set([
@@ -40,6 +41,8 @@ export function normalizeCliArgs(argv) {
40
41
  "--provider-arg",
41
42
  "--message",
42
43
  "--prompt",
44
+ "--command",
45
+ "--use",
43
46
  ]);
44
47
  const positionals = findPositionals(argv, valueFlags);
45
48
  const firstPositional = positionals[0] || null;
@@ -6,6 +6,29 @@ export interface LoadDotenvFilesOptions {
6
6
  override?: boolean;
7
7
  }
8
8
 
9
+ export interface PostgresConnectionEnvFallback {
10
+ database?: string;
11
+ host?: string;
12
+ password?: string;
13
+ port?: number | string;
14
+ ssl?: boolean;
15
+ username?: string;
16
+ }
17
+
18
+ export interface PostgresConnectionEnvOptions {
19
+ env?: NodeJS.ProcessEnv;
20
+ fallback?: PostgresConnectionEnvFallback;
21
+ }
22
+
23
+ export interface PostgresConnectionEnvValue {
24
+ database: string;
25
+ host: string;
26
+ password: string;
27
+ port: number;
28
+ ssl: boolean;
29
+ username: string;
30
+ }
31
+
9
32
  export declare function isManagedRuntime(env?: NodeJS.ProcessEnv): boolean;
10
33
  export declare function shouldLoadDotenv(env?: NodeJS.ProcessEnv): boolean;
11
34
  export declare function loadDotenvFiles(options?: LoadDotenvFilesOptions): {
@@ -15,3 +38,7 @@ export declare function assertLocalDatabaseUrl(
15
38
  env?: NodeJS.ProcessEnv,
16
39
  consumer?: string
17
40
  ): void;
41
+ export declare function readPostgresConnectionFromEnv(
42
+ prefix: string,
43
+ options?: PostgresConnectionEnvOptions
44
+ ): PostgresConnectionEnvValue;
package/lib/env/index.mjs CHANGED
@@ -63,3 +63,57 @@ export function assertLocalDatabaseUrl(env = process.env, consumer = "This comma
63
63
  );
64
64
  }
65
65
  }
66
+
67
+ export function readPostgresConnectionFromEnv(prefix, options = {}) {
68
+ const env = options.env || process.env;
69
+ const normalizedPrefix = String(prefix || "").trim();
70
+ if (!normalizedPrefix) {
71
+ throw new Error("readPostgresConnectionFromEnv requires an environment variable prefix");
72
+ }
73
+
74
+ const fallback = options.fallback || {};
75
+ return {
76
+ host: readEnvValue(env, `${normalizedPrefix}_HOST`, fallback.host),
77
+ port: readEnvPort(env, `${normalizedPrefix}_PORT`, fallback.port),
78
+ database: readEnvValue(env, `${normalizedPrefix}_NAME`, fallback.database),
79
+ username: readEnvValue(env, `${normalizedPrefix}_USER`, fallback.username),
80
+ password: readEnvValue(env, `${normalizedPrefix}_PASSWORD`, fallback.password),
81
+ ssl: readEnvBoolean(env, `${normalizedPrefix}_SSL`, fallback.ssl ?? false),
82
+ };
83
+ }
84
+
85
+ function readEnvValue(env, name, fallback) {
86
+ const value = env?.[name]?.trim();
87
+ if (value) return value;
88
+ if (fallback != null && String(fallback).trim()) return String(fallback).trim();
89
+ throw new Error(`${name} environment variable is required`);
90
+ }
91
+
92
+ function readEnvPort(env, name, fallback) {
93
+ const raw = env?.[name]?.trim();
94
+ const value = raw || (fallback == null ? "" : String(fallback));
95
+ const parsed = Number.parseInt(value, 10);
96
+ if (!Number.isInteger(parsed) || parsed <= 0) {
97
+ throw new Error(`${name} must be a positive integer`);
98
+ }
99
+ return parsed;
100
+ }
101
+
102
+ function readEnvBoolean(env, name, fallback) {
103
+ const value = env?.[name];
104
+ if (!value) return Boolean(fallback);
105
+ switch (value.trim().toLowerCase()) {
106
+ case "1":
107
+ case "true":
108
+ case "yes":
109
+ case "on":
110
+ return true;
111
+ case "0":
112
+ case "false":
113
+ case "no":
114
+ case "off":
115
+ return false;
116
+ default:
117
+ return Boolean(fallback);
118
+ }
119
+ }
@@ -432,11 +432,23 @@ export declare function expectResponse(
432
432
  export declare function expectValue<T>(
433
433
  value: T,
434
434
  predicate: (value: T) => boolean,
435
- label: string
435
+ label: string,
436
+ options?: {
437
+ actual?: ((value: T) => unknown) | unknown;
438
+ describeFailure?: ((value: T) => string) | string;
439
+ expected?: unknown;
440
+ kind?: string;
441
+ }
436
442
  ): boolean;
437
443
  export declare function expectCondition(
438
444
  predicate: () => boolean,
439
- label: string
445
+ label: string,
446
+ options?: {
447
+ actual?: (() => unknown) | unknown;
448
+ describeFailure?: (() => string) | string;
449
+ expected?: unknown;
450
+ kind?: string;
451
+ }
440
452
  ): boolean;
441
453
  export declare function expectRowCount<T>(
442
454
  rows: T[],
@@ -452,7 +464,7 @@ export declare function expectNoRows<T>(
452
464
  label?: string | null
453
465
  ): boolean;
454
466
  export declare function expectField<T extends Record<string, unknown>>(
455
- row: T,
467
+ row: T | T[],
456
468
  field: keyof T | string,
457
469
  predicateOrExpected: unknown | ((value: unknown) => boolean),
458
470
  label?: string | null
@@ -470,7 +482,7 @@ export declare function expectType(
470
482
  export declare function captureError(fn: () => unknown): unknown | null;
471
483
  export declare function expectError(
472
484
  errorOrFn: unknown | (() => unknown) | null,
473
- predicate?: ((error: unknown) => boolean) | null,
485
+ predicate?: ((error: unknown) => boolean) | string | null,
474
486
  label?: string | null
475
487
  ): boolean;
476
488
 
@@ -3,6 +3,15 @@ import {
3
3
  extractErrorMessageFromBody,
4
4
  hasStandardErrorShape,
5
5
  } from "../shared/error-body.mjs";
6
+ import {
7
+ buildValueAssertionDetail,
8
+ formatAssertionValue,
9
+ normalizeErrorValue,
10
+ resolveActual,
11
+ resolveConditionActual,
12
+ resolveConditionFailureMessage,
13
+ resolveFailureMessage,
14
+ } from "../shared/assertion-details.mjs";
6
15
  import { safeJson, summarizeHttpTrace, toBodyPreview } from "./http.js";
7
16
 
8
17
  export function expectStatus(response, expected, label = null) {
@@ -140,16 +149,34 @@ export function expectResponse(response, predicate, label) {
140
149
  });
141
150
  }
142
151
 
143
- export function expectValue(value, predicate, label) {
144
- return check(value, {
145
- [normalizeLabel(label, "value matches expectation")]: predicate,
146
- });
152
+ export function expectValue(value, predicate, label, options = {}) {
153
+ const title = normalizeLabel(label, "value matches expectation");
154
+ return evaluateCheck(value, title, predicate, () =>
155
+ buildValueAssertionDetail({
156
+ kind: options.kind || "value-assertion",
157
+ title,
158
+ actual: resolveActual(value, options.actual),
159
+ expected: options.expected,
160
+ message: resolveFailureMessage(value, title, options.describeFailure),
161
+ })
162
+ );
147
163
  }
148
164
 
149
- export function expectCondition(predicate, label) {
150
- return check(null, {
151
- [normalizeLabel(label, "condition holds")]: () => Boolean(predicate()),
152
- });
165
+ export function expectCondition(predicate, label, options = {}) {
166
+ const title = normalizeLabel(label, "condition holds");
167
+ return evaluateCheck(
168
+ null,
169
+ title,
170
+ () => Boolean(predicate()),
171
+ () =>
172
+ buildValueAssertionDetail({
173
+ kind: options.kind || "condition-assertion",
174
+ title,
175
+ actual: resolveConditionActual(options.actual),
176
+ expected: options.expected,
177
+ message: resolveConditionFailureMessage(title, options.describeFailure),
178
+ })
179
+ );
153
180
  }
154
181
 
155
182
  export function expectRowCount(rows, expectedCount, label = null) {
@@ -160,7 +187,14 @@ export function expectRowCount(rows, expectedCount, label = null) {
160
187
  return expectValue(
161
188
  rows,
162
189
  (value) => Array.isArray(value) && value.length === expected,
163
- normalizeLabel(label, `row count is ${expected}`)
190
+ normalizeLabel(label, `row count is ${expected}`),
191
+ {
192
+ actual: (value) => (Array.isArray(value) ? value.length : undefined),
193
+ describeFailure: (value) =>
194
+ `expected ${expected} row(s), got ${Array.isArray(value) ? value.length : "non-array value"}`,
195
+ expected,
196
+ kind: "row-count-assertion",
197
+ }
164
198
  );
165
199
  }
166
200
 
@@ -173,12 +207,23 @@ export function expectNoRows(rows, label = "query returns no rows") {
173
207
  return expectRowCount(rows, 0, label);
174
208
  }
175
209
 
176
- export function expectField(row, field, predicateOrExpected, label = null) {
210
+ export function expectField(rowOrRows, field, predicateOrExpected, label = null) {
177
211
  const fieldName = String(field || "");
212
+ const row = Array.isArray(rowOrRows) ? rowOrRows[0] : rowOrRows;
213
+ const expected = typeof predicateOrExpected === "function" ? undefined : predicateOrExpected;
178
214
  return expectValue(
179
215
  row?.[fieldName],
180
216
  normalizeFieldPredicate(predicateOrExpected),
181
- normalizeLabel(label, `${fieldName || "field"} matches expectation`)
217
+ normalizeLabel(label, `${fieldName || "field"} matches expectation`),
218
+ {
219
+ actual: row?.[fieldName],
220
+ describeFailure: () =>
221
+ Array.isArray(rowOrRows) && rowOrRows.length === 0
222
+ ? `expected ${fieldName || "field"}=${formatAssertionValue(expected)}, but query returned 0 rows`
223
+ : `${fieldName || "field"} expected ${formatAssertionValue(expected)}, got ${formatAssertionValue(row?.[fieldName])}`,
224
+ expected,
225
+ kind: "field-assertion",
226
+ }
182
227
  );
183
228
  }
184
229
 
@@ -198,7 +243,13 @@ export function expectType(value, typeName, label = null) {
198
243
  return expectValue(
199
244
  value,
200
245
  (actual) => actualType(actual) === expected,
201
- normalizeLabel(label, `type is ${expected}`)
246
+ normalizeLabel(label, `type is ${expected}`),
247
+ {
248
+ actual: (actual) => actualType(actual),
249
+ describeFailure: (actual) => `expected type ${expected}, got ${actualType(actual)}`,
250
+ expected,
251
+ kind: "type-assertion",
252
+ }
202
253
  );
203
254
  }
204
255
 
@@ -213,13 +264,21 @@ export function captureError(fn) {
213
264
 
214
265
  export function expectError(errorOrFn, predicate = null, label = null) {
215
266
  const error = typeof errorOrFn === "function" ? captureError(errorOrFn) : errorOrFn;
267
+ const predicateFn = typeof predicate === "function" ? predicate : null;
268
+ const title = typeof predicate === "string" && label == null ? predicate : label;
216
269
  return expectValue(
217
270
  error,
218
271
  (value) => {
219
272
  if (!value) return false;
220
- return typeof predicate === "function" ? Boolean(predicate(value)) : true;
273
+ return predicateFn ? Boolean(predicateFn(value)) : true;
221
274
  },
222
- normalizeLabel(label, "error is captured")
275
+ normalizeLabel(title, "error is captured"),
276
+ {
277
+ actual: normalizeErrorValue(error),
278
+ describeFailure: () => "expected operation to throw",
279
+ expected: "error",
280
+ kind: "error-assertion",
281
+ }
223
282
  );
224
283
  }
225
284
 
@@ -0,0 +1,98 @@
1
+ export function buildValueAssertionDetail({ actual, expected, kind, message, title }) {
2
+ const location = captureCallsite();
3
+ return {
4
+ kind,
5
+ key: location ? `${location.path}:${location.line}:${location.column}` : title,
6
+ title,
7
+ message,
8
+ expected: normalizeAssertionValue(expected),
9
+ actual: normalizeAssertionValue(actual),
10
+ location,
11
+ };
12
+ }
13
+
14
+ export function resolveActual(value, actual) {
15
+ if (typeof actual === "function") return actual(value);
16
+ if (actual !== undefined) return actual;
17
+ return value;
18
+ }
19
+
20
+ export function resolveConditionActual(actual) {
21
+ if (typeof actual === "function") return actual();
22
+ return actual;
23
+ }
24
+
25
+ export function resolveFailureMessage(value, label, describeFailure) {
26
+ if (typeof describeFailure === "function") return describeFailure(value);
27
+ if (typeof describeFailure === "string" && describeFailure.trim().length > 0) {
28
+ return describeFailure;
29
+ }
30
+ return label;
31
+ }
32
+
33
+ export function resolveConditionFailureMessage(label, describeFailure) {
34
+ if (typeof describeFailure === "function") return describeFailure();
35
+ if (typeof describeFailure === "string" && describeFailure.trim().length > 0) {
36
+ return describeFailure;
37
+ }
38
+ return label;
39
+ }
40
+
41
+ export function normalizeAssertionValue(value) {
42
+ if (
43
+ value === null ||
44
+ value === undefined ||
45
+ typeof value === "boolean" ||
46
+ typeof value === "number" ||
47
+ typeof value === "string"
48
+ ) {
49
+ return value;
50
+ }
51
+ if (typeof value === "bigint") return value.toString();
52
+ try {
53
+ return JSON.parse(JSON.stringify(value));
54
+ } catch {
55
+ return String(value);
56
+ }
57
+ }
58
+
59
+ export function formatAssertionValue(value) {
60
+ if (typeof value === "string") return JSON.stringify(value);
61
+ if (value === undefined) return "undefined";
62
+ if (typeof value === "bigint") return value.toString();
63
+ try {
64
+ return JSON.stringify(value);
65
+ } catch {
66
+ return String(value);
67
+ }
68
+ }
69
+
70
+ export function normalizeErrorValue(error) {
71
+ if (!error) return null;
72
+ if (error instanceof Error) return error.message;
73
+ return String(error);
74
+ }
75
+
76
+ function captureCallsite() {
77
+ const stack = new Error().stack;
78
+ if (!stack) return null;
79
+
80
+ for (const line of stack.split("\n").slice(1)) {
81
+ const match = line.match(/(file:\/\/[^\s)]+|\/[^\s):]+):(\d+):(\d+)/);
82
+ if (!match) continue;
83
+ const [rawPath, rawLine, rawColumn] = match.slice(1);
84
+ if (!rawPath || !rawLine || !rawColumn) continue;
85
+ const stackPath = rawPath.startsWith("file://") ? rawPath.slice("file://".length) : rawPath;
86
+ if (stackPath.endsWith("/runtime-src/shared/assertion-details.mjs")) continue;
87
+ if (stackPath.endsWith("/runtime-src/k6/http-assertions.js")) continue;
88
+ if (stackPath.endsWith("/runtime-src/k6/checks.js")) continue;
89
+ if (stackPath.endsWith("/runtime/index.mjs")) continue;
90
+ return {
91
+ path: stackPath,
92
+ line: Number(rawLine),
93
+ column: Number(rawColumn),
94
+ };
95
+ }
96
+
97
+ return null;
98
+ }
package/lib/ui/index.d.ts CHANGED
@@ -105,6 +105,19 @@ export declare function waitForAnimationFrames(
105
105
  page: import("@playwright/test").Page,
106
106
  frames?: number
107
107
  ): Promise<void>;
108
+ export declare function waitForNetworkIdle(
109
+ page: import("@playwright/test").Page,
110
+ options?: { frames?: number; timeout?: number }
111
+ ): Promise<void>;
112
+ export declare function waitForElementStable(
113
+ page: import("@playwright/test").Page,
114
+ selector: string,
115
+ options?: { frames?: number; timeout?: number }
116
+ ): Promise<void>;
117
+ export declare function waitForImagesLoaded(
118
+ page: import("@playwright/test").Page,
119
+ containerSelector?: string | null
120
+ ): Promise<void>;
108
121
  export declare function readUiConfig(env?: NodeJS.ProcessEnv): unknown;
109
122
  export declare function provisionUiIdentity(
110
123
  page: import("@playwright/test").Page,
package/lib/ui/index.mjs CHANGED
@@ -10,6 +10,9 @@ export {
10
10
  resolveBackendBaseUrl,
11
11
  resolveFrontendBaseUrl,
12
12
  waitForAnimationFrames,
13
+ waitForElementStable,
14
+ waitForImagesLoaded,
15
+ waitForNetworkIdle,
13
16
  waitForUiSettled,
14
17
  } from "./sandbox.mjs";
15
18
  export {
@@ -196,20 +196,28 @@ async function runProvisionSql({ config, identity, options, profile, session, sq
196
196
  if (!databaseUrl) {
197
197
  throw new Error("UI provisioning SQL requires auth.databaseUrl, TESTKIT_UI_DATABASE_URL, or DATABASE_URL");
198
198
  }
199
- for (const statement of sql) {
200
- const rendered = renderSql(statement, { identity, options, profile, session });
201
- const result = await execa("psql", [databaseUrl, "-v", "ON_ERROR_STOP=1", "-X", "-q", "-c", rendered], {
202
- env: process.env,
203
- reject: false,
204
- stdout: "pipe",
205
- stderr: "pipe",
206
- });
207
- if (result.exitCode !== 0) {
208
- throw new Error(result.stderr || result.shortMessage || `UI provisioning SQL failed with exit code ${result.exitCode}`);
209
- }
199
+ const renderedSql = sql
200
+ .map((statement) => renderSql(statement, { identity, options, profile, session }).trim())
201
+ .filter(Boolean)
202
+ .map(terminateSqlStatement)
203
+ .join("\n");
204
+ if (!renderedSql) return;
205
+
206
+ const result = await execa("psql", [databaseUrl, "-v", "ON_ERROR_STOP=1", "-X", "-q", "-c", renderedSql], {
207
+ env: process.env,
208
+ reject: false,
209
+ stdout: "pipe",
210
+ stderr: "pipe",
211
+ });
212
+ if (result.exitCode !== 0) {
213
+ throw new Error(result.stderr || result.shortMessage || `UI provisioning SQL failed with exit code ${result.exitCode}`);
210
214
  }
211
215
  }
212
216
 
217
+ function terminateSqlStatement(statement) {
218
+ return /;\s*$/.test(statement) ? statement : `${statement};`;
219
+ }
220
+
213
221
  function defaultSignupBody() {
214
222
  return {
215
223
  email: "{identity.email}",
@@ -220,7 +220,7 @@ export async function waitForUiSettled(page, options = {}) {
220
220
  const timeout = options.timeout ?? 5_000;
221
221
  await page.waitForLoadState("domcontentloaded", { timeout });
222
222
  await waitForAnimationFrames(page, options.frames ?? 2);
223
- await page.waitForLoadState("networkidle", { timeout }).catch(() => {});
223
+ await waitForNetworkIdle(page, { frames: options.frames ?? 2, timeout });
224
224
  }
225
225
 
226
226
  export async function waitForAnimationFrames(page, frames = 2) {
@@ -243,6 +243,52 @@ export async function waitForAnimationFrames(page, frames = 2) {
243
243
  );
244
244
  }
245
245
 
246
+ export async function waitForNetworkIdle(page, options = {}) {
247
+ const timeout = options.timeout ?? 2_000;
248
+ try {
249
+ await page.waitForLoadState("networkidle", { timeout });
250
+ } catch {
251
+ await page.waitForLoadState("load", { timeout }).catch(() => {});
252
+ await waitForAnimationFrames(page, options.frames ?? 2);
253
+ }
254
+ }
255
+
256
+ export async function waitForElementStable(page, selector, options = {}) {
257
+ const stableFrames = Math.max(1, Number(options.frames) || 2);
258
+ const element = page.locator(selector);
259
+ await element.waitFor({ state: "visible", timeout: options.timeout });
260
+ await element.evaluate(async (node, requiredStableFrames) => {
261
+ let stable = 0;
262
+ let previous = JSON.stringify(node.getBoundingClientRect().toJSON());
263
+ while (stable < requiredStableFrames) {
264
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
265
+ const current = JSON.stringify(node.getBoundingClientRect().toJSON());
266
+ if (current === previous) {
267
+ stable += 1;
268
+ } else {
269
+ stable = 0;
270
+ }
271
+ previous = current;
272
+ }
273
+ }, stableFrames);
274
+ }
275
+
276
+ export async function waitForImagesLoaded(page, containerSelector = null) {
277
+ const selector = containerSelector ? `${containerSelector} img` : "img";
278
+ const images = page.locator(selector);
279
+ const count = await images.count();
280
+
281
+ for (let index = 0; index < count; index += 1) {
282
+ await images.nth(index).evaluate((element) => {
283
+ if (element.complete) return undefined;
284
+ return new Promise((resolve) => {
285
+ element.addEventListener("load", resolve, { once: true });
286
+ element.addEventListener("error", resolve, { once: true });
287
+ });
288
+ });
289
+ }
290
+ }
291
+
246
292
  function resolveBaseUrl(value) {
247
293
  const normalized = String(value || "").trim();
248
294
  if (!normalized) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/next-analysis",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "SWC-backed Next.js source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-bridge",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "Browser bridge helpers for testkit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "typecheck": "tsc -p tsconfig.json --noEmit"
23
23
  },
24
24
  "dependencies": {
25
- "@elench/testkit-protocol": "0.1.132"
25
+ "@elench/testkit-protocol": "0.1.134"
26
26
  },
27
27
  "private": false
28
28
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit-protocol",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "Shared browser protocol for testkit bridge and extension consumers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/ts-analysis",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "TypeScript compiler-backed source analysis primitives for Erench tools",
5
5
  "type": "module",
6
6
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elench/testkit",
3
- "version": "0.1.132",
3
+ "version": "0.1.134",
4
4
  "description": "Assistant-first CLI for running, inspecting, and debugging local testkit suites",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -95,10 +95,10 @@
95
95
  },
96
96
  "dependencies": {
97
97
  "@babel/code-frame": "^7.29.0",
98
- "@elench/next-analysis": "0.1.132",
99
- "@elench/testkit-bridge": "0.1.132",
100
- "@elench/testkit-protocol": "0.1.132",
101
- "@elench/ts-analysis": "0.1.132",
98
+ "@elench/next-analysis": "0.1.134",
99
+ "@elench/testkit-bridge": "0.1.134",
100
+ "@elench/testkit-protocol": "0.1.134",
101
+ "@elench/ts-analysis": "0.1.134",
102
102
  "@oclif/core": "^4.10.6",
103
103
  "@playwright/test": "^1.52.0",
104
104
  "esbuild": "^0.25.11",