@solidxai/core 0.1.15 → 0.1.16-beta.1

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 (176) hide show
  1. package/CHANGELOG.md +1293 -0
  2. package/dist/commands/run-tests.command.d.ts +3 -1
  3. package/dist/commands/run-tests.command.d.ts.map +1 -1
  4. package/dist/commands/run-tests.command.js +6 -2
  5. package/dist/commands/run-tests.command.js.map +1 -1
  6. package/dist/controllers/test-run.controller.d.ts +12 -0
  7. package/dist/controllers/test-run.controller.d.ts.map +1 -0
  8. package/dist/controllers/test-run.controller.js +54 -0
  9. package/dist/controllers/test-run.controller.js.map +1 -0
  10. package/dist/dtos/test-run-request.dto.d.ts +35 -0
  11. package/dist/dtos/test-run-request.dto.d.ts.map +1 -0
  12. package/dist/dtos/test-run-request.dto.js +120 -0
  13. package/dist/dtos/test-run-request.dto.js.map +1 -0
  14. package/dist/guards/access-token.guard.d.ts +5 -1
  15. package/dist/guards/access-token.guard.d.ts.map +1 -1
  16. package/dist/guards/access-token.guard.js +20 -1
  17. package/dist/guards/access-token.guard.js.map +1 -1
  18. package/dist/index.d.ts +12 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +17 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/interfaces/active-user-data.interface.d.ts +1 -0
  23. package/dist/interfaces/active-user-data.interface.d.ts.map +1 -1
  24. package/dist/interfaces/active-user-data.interface.js.map +1 -1
  25. package/dist/jobs/database/test-run-queue-options-database.d.ts +8 -0
  26. package/dist/jobs/database/test-run-queue-options-database.d.ts.map +1 -0
  27. package/dist/jobs/database/test-run-queue-options-database.js +10 -0
  28. package/dist/jobs/database/test-run-queue-options-database.js.map +1 -0
  29. package/dist/jobs/database/test-run-queue-publisher-database.service.d.ts +12 -0
  30. package/dist/jobs/database/test-run-queue-publisher-database.service.d.ts.map +1 -0
  31. package/dist/jobs/database/test-run-queue-publisher-database.service.js +39 -0
  32. package/dist/jobs/database/test-run-queue-publisher-database.service.js.map +1 -0
  33. package/dist/jobs/database/test-run-queue-subscriber-database.service.d.ts +26 -0
  34. package/dist/jobs/database/test-run-queue-subscriber-database.service.d.ts.map +1 -0
  35. package/dist/jobs/database/test-run-queue-subscriber-database.service.js +120 -0
  36. package/dist/jobs/database/test-run-queue-subscriber-database.service.js.map +1 -0
  37. package/dist/resources/images/solid-core-menu-icon.png +0 -0
  38. package/dist/seeders/seed-data/solid-core-metadata.json +4 -2
  39. package/dist/services/access-token-denylist.service.d.ts +16 -0
  40. package/dist/services/access-token-denylist.service.d.ts.map +1 -0
  41. package/dist/services/access-token-denylist.service.js +74 -0
  42. package/dist/services/access-token-denylist.service.js.map +1 -0
  43. package/dist/services/authentication.service.d.ts +9 -1
  44. package/dist/services/authentication.service.d.ts.map +1 -1
  45. package/dist/services/authentication.service.js +39 -4
  46. package/dist/services/authentication.service.js.map +1 -1
  47. package/dist/services/import-transaction.service.d.ts +1 -1
  48. package/dist/services/import-transaction.service.d.ts.map +1 -1
  49. package/dist/services/import-transaction.service.js +13 -23
  50. package/dist/services/import-transaction.service.js.map +1 -1
  51. package/dist/services/module-metadata.service.js +4 -4
  52. package/dist/services/module-metadata.service.js.map +1 -1
  53. package/dist/services/settings/default-settings-provider.service.d.ts +20 -0
  54. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  55. package/dist/services/settings/default-settings-provider.service.js +11 -0
  56. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  57. package/dist/services/workflow-secret.service.d.ts +3 -0
  58. package/dist/services/workflow-secret.service.d.ts.map +1 -1
  59. package/dist/services/workflow-secret.service.js +25 -0
  60. package/dist/services/workflow-secret.service.js.map +1 -1
  61. package/dist/solid-core.module.d.ts.map +1 -1
  62. package/dist/solid-core.module.js +9 -0
  63. package/dist/solid-core.module.js.map +1 -1
  64. package/dist/testing/adapters/ui/browser-provisioner.d.ts +5 -0
  65. package/dist/testing/adapters/ui/browser-provisioner.d.ts.map +1 -0
  66. package/dist/testing/adapters/ui/browser-provisioner.js +102 -0
  67. package/dist/testing/adapters/ui/browser-provisioner.js.map +1 -0
  68. package/dist/testing/adapters/ui/playwright-adapter.d.ts +40 -1
  69. package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -1
  70. package/dist/testing/adapters/ui/playwright-adapter.js +185 -3
  71. package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -1
  72. package/dist/testing/adapters/ui/ui.types.d.ts +6 -0
  73. package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -1
  74. package/dist/testing/adapters/ui/ui.types.js.map +1 -1
  75. package/dist/testing/contracts/runtime-context.types.d.ts +2 -0
  76. package/dist/testing/contracts/runtime-context.types.d.ts.map +1 -1
  77. package/dist/testing/contracts/runtime-context.types.js.map +1 -1
  78. package/dist/testing/core/interpolation.d.ts +1 -0
  79. package/dist/testing/core/interpolation.d.ts.map +1 -1
  80. package/dist/testing/core/interpolation.js +28 -68
  81. package/dist/testing/core/interpolation.js.map +1 -1
  82. package/dist/testing/core/path-resolution.spec.d.ts +2 -0
  83. package/dist/testing/core/path-resolution.spec.d.ts.map +1 -0
  84. package/dist/testing/core/path-resolution.spec.js +106 -0
  85. package/dist/testing/core/path-resolution.spec.js.map +1 -0
  86. package/dist/testing/core/path-segments.d.ts +2 -0
  87. package/dist/testing/core/path-segments.d.ts.map +1 -0
  88. package/dist/testing/core/path-segments.js +70 -0
  89. package/dist/testing/core/path-segments.js.map +1 -0
  90. package/dist/testing/core/resource-store.d.ts.map +1 -1
  91. package/dist/testing/core/resource-store.js +5 -2
  92. package/dist/testing/core/resource-store.js.map +1 -1
  93. package/dist/testing/core/testing-engine.d.ts.map +1 -1
  94. package/dist/testing/core/testing-engine.js +16 -0
  95. package/dist/testing/core/testing-engine.js.map +1 -1
  96. package/dist/testing/reporter/artifact-sink.d.ts +14 -0
  97. package/dist/testing/reporter/artifact-sink.d.ts.map +1 -0
  98. package/dist/testing/reporter/artifact-sink.js +3 -0
  99. package/dist/testing/reporter/artifact-sink.js.map +1 -0
  100. package/dist/testing/reporter/console-reporter.d.ts.map +1 -1
  101. package/dist/testing/reporter/console-reporter.js +7 -0
  102. package/dist/testing/reporter/console-reporter.js.map +1 -1
  103. package/dist/testing/reporter/file-service-artifact-sink.d.ts +22 -0
  104. package/dist/testing/reporter/file-service-artifact-sink.d.ts.map +1 -0
  105. package/dist/testing/reporter/file-service-artifact-sink.js +31 -0
  106. package/dist/testing/reporter/file-service-artifact-sink.js.map +1 -0
  107. package/dist/testing/reporter/lifecycle-events.types.d.ts +67 -0
  108. package/dist/testing/reporter/lifecycle-events.types.d.ts.map +1 -0
  109. package/dist/testing/reporter/lifecycle-events.types.js +3 -0
  110. package/dist/testing/reporter/lifecycle-events.types.js.map +1 -0
  111. package/dist/testing/reporter/lifecycle-webhook-reporter.d.ts +77 -0
  112. package/dist/testing/reporter/lifecycle-webhook-reporter.d.ts.map +1 -0
  113. package/dist/testing/reporter/lifecycle-webhook-reporter.js +275 -0
  114. package/dist/testing/reporter/lifecycle-webhook-reporter.js.map +1 -0
  115. package/dist/testing/reporter/progress-reporter.d.ts +3 -3
  116. package/dist/testing/reporter/progress-reporter.d.ts.map +1 -1
  117. package/dist/testing/reporter/progress-reporter.js.map +1 -1
  118. package/dist/testing/reporter/reporter.types.d.ts +13 -1
  119. package/dist/testing/reporter/reporter.types.d.ts.map +1 -1
  120. package/dist/testing/reporter/reporter.types.js.map +1 -1
  121. package/dist/testing/runner/read-scenarios-file.d.ts +9 -0
  122. package/dist/testing/runner/read-scenarios-file.d.ts.map +1 -0
  123. package/dist/testing/runner/read-scenarios-file.js +56 -0
  124. package/dist/testing/runner/read-scenarios-file.js.map +1 -0
  125. package/dist/testing/runner/run-from-metadata.d.ts +7 -2
  126. package/dist/testing/runner/run-from-metadata.d.ts.map +1 -1
  127. package/dist/testing/runner/run-from-metadata.js +57 -6
  128. package/dist/testing/runner/run-from-metadata.js.map +1 -1
  129. package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -1
  130. package/dist/testing/steps/ui/navigation.step.js +12 -12
  131. package/dist/testing/steps/ui/navigation.step.js.map +1 -1
  132. package/package.json +4 -2
  133. package/postman/instant-logout.postman_collection.json +1493 -0
  134. package/resources/images/solid-core-menu-icon.png +0 -0
  135. package/src/commands/run-tests.command.ts +3 -0
  136. package/src/controllers/test-run.controller.ts +44 -0
  137. package/src/dtos/test-run-request.dto.ts +130 -0
  138. package/src/guards/access-token.guard.ts +48 -0
  139. package/src/index.ts +21 -1
  140. package/src/interfaces/active-user-data.interface.ts +8 -0
  141. package/src/jobs/database/test-run-queue-options-database.ts +9 -0
  142. package/src/jobs/database/test-run-queue-publisher-database.service.ts +24 -0
  143. package/src/jobs/database/test-run-queue-subscriber-database.service.ts +122 -0
  144. package/src/seeders/seed-data/solid-core-metadata.json +4 -2
  145. package/src/services/access-token-denylist.service.ts +145 -0
  146. package/src/services/authentication.service.ts +118 -2
  147. package/src/services/import-transaction.service.ts +19 -36
  148. package/src/services/module-metadata.service.ts +4 -4
  149. package/src/services/settings/default-settings-provider.service.ts +12 -0
  150. package/src/services/workflow-secret.service.ts +61 -2
  151. package/src/solid-core.module.ts +11 -0
  152. package/src/testing/README.md +44 -1
  153. package/src/testing/adapters/ui/browser-provisioner.ts +115 -0
  154. package/src/testing/adapters/ui/playwright-adapter.ts +255 -5
  155. package/src/testing/adapters/ui/ui.types.ts +14 -0
  156. package/src/testing/contracts/runtime-context.types.ts +6 -0
  157. package/src/testing/core/interpolation.ts +39 -69
  158. package/src/testing/core/path-resolution.spec.ts +133 -0
  159. package/src/testing/core/path-segments.ts +81 -0
  160. package/src/testing/core/resource-store.ts +8 -2
  161. package/src/testing/core/testing-engine.ts +22 -0
  162. package/src/testing/reporter/artifact-sink.ts +16 -0
  163. package/src/testing/reporter/console-reporter.ts +9 -0
  164. package/src/testing/reporter/file-service-artifact-sink.ts +46 -0
  165. package/src/testing/reporter/lifecycle-events.types.ts +103 -0
  166. package/src/testing/reporter/lifecycle-webhook-reporter.ts +385 -0
  167. package/src/testing/reporter/progress-reporter.ts +3 -1
  168. package/src/testing/reporter/reporter.types.ts +10 -1
  169. package/src/testing/runner/read-scenarios-file.ts +44 -0
  170. package/src/testing/runner/run-from-metadata.ts +102 -9
  171. package/src/testing/steps/ui/navigation.step.ts +24 -20
  172. package/dist/services/access-token-storage.service.d.ts +0 -1
  173. package/dist/services/access-token-storage.service.d.ts.map +0 -1
  174. package/dist/services/access-token-storage.service.js +0 -1
  175. package/dist/services/access-token-storage.service.js.map +0 -1
  176. package/src/services/access-token-storage.service.ts +0 -0
@@ -0,0 +1,44 @@
1
+ import * as fs from "fs";
2
+
3
+ import type { ScenarioSpec, TestingDataRecord } from "../contracts/testing-metadata.types";
4
+
5
+ /**
6
+ * Result of reading a scenario JSON file. `ok:false` means the file was missing,
7
+ * unreadable, or contained invalid JSON — never a thrown error.
8
+ */
9
+ export interface ReadScenariosResult {
10
+ scenarios: ScenarioSpec[];
11
+ data?: TestingDataRecord[];
12
+ ok: boolean;
13
+ error?: string;
14
+ }
15
+
16
+ /**
17
+ * Read + parse an application's scenario file `{ testing: { scenarios, data } }` from an
18
+ * absolute path, on the WORKER tier (which shares the filesystem with the caller in the
19
+ * default single-process deployment). Tolerant by design — mirrors
20
+ * ScenarioFileService.resolvePayload in testingHub — so a concurrent editor half-writing
21
+ * the file, a deleted file (TOCTOU between existsSync and readFileSync), or invalid JSON
22
+ * returns `{ ok:false }` instead of throwing. The worker fails the run loudly on `ok:false`.
23
+ *
24
+ * Lives in core (not testingHub) because the worker is a core component and must not import
25
+ * testingHub code.
26
+ */
27
+ export function readScenariosFile(absPath: string): ReadScenariosResult {
28
+ if (!absPath || !fs.existsSync(absPath)) {
29
+ return { scenarios: [], ok: false, error: "file_not_found" };
30
+ }
31
+ try {
32
+ const parsed = JSON.parse(fs.readFileSync(absPath, "utf-8"));
33
+ const rawScenarios =
34
+ parsed?.testing?.scenarios ??
35
+ parsed?.scenarios ??
36
+ (Array.isArray(parsed) ? parsed : []);
37
+ const scenarios: ScenarioSpec[] = Array.isArray(rawScenarios) ? rawScenarios : [];
38
+ const rawData = parsed?.testing?.data ?? parsed?.data;
39
+ const data: TestingDataRecord[] | undefined = Array.isArray(rawData) ? rawData : undefined;
40
+ return { scenarios, data, ok: true };
41
+ } catch (err) {
42
+ return { scenarios: [], ok: false, error: (err as Error)?.message || "read_error" };
43
+ }
44
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Reporter } from "../reporter/reporter.types";
2
- import type { TestingMetadata, TestingDataRecord } from "../contracts/testing-metadata.types";
2
+ import type { TestingMetadata, TestingDataRecord, ScenarioSpec } from "../contracts/testing-metadata.types";
3
3
  import type { ApiAdapterOptions } from "../adapters/api/api.types";
4
4
  import type { PlaywrightAdapterOptions } from "../adapters/ui/ui.types";
5
5
  import { ApiAdapter } from "../adapters/api/api-adapter";
@@ -13,7 +13,9 @@ import { StepRegistry } from "../core/step-registry";
13
13
  import { SpecRegistry } from "../core/spec-registry";
14
14
  import { TestingEngine } from "../core/testing-engine";
15
15
  import { filterScenarios } from "./scenario-filter";
16
+ import { collectSecretKeys } from "../core/interpolation";
16
17
  import { ensureUiStarted, scenarioNeedsUi } from "./lifecycle";
18
+ import { ensureChromiumInstalled } from "../adapters/ui/browser-provisioner";
17
19
  import { ConsoleReporter } from "../reporter/console-reporter";
18
20
 
19
21
 
@@ -31,7 +33,11 @@ function buildTestDataIndex(data?: TestingDataRecord[]): Record<string, Record<s
31
33
  }
32
34
 
33
35
  export type RunnerOptions = {
34
- metadata: TestingMetadata;
36
+ metadata?: TestingMetadata;
37
+ scenarios?: ScenarioSpec[];
38
+ data?: TestingDataRecord[];
39
+ externalRunId?: string;
40
+ env?: Record<string, string>;
35
41
  scenarioIds?: string[];
36
42
  includeTags?: string[];
37
43
  skipScenarioIds?: string[];
@@ -41,8 +47,59 @@ export type RunnerOptions = {
41
47
  defaults?: { timeoutMs?: number; retries?: number };
42
48
  options?: { printApiLogs?: boolean };
43
49
  specs?: (registry: SpecRegistry) => void;
50
+ /**
51
+ * Resolves `${secret:...}` references against the secret store. Injected by the
52
+ * caller so this runner stays free of database and DI coupling. Required only if a
53
+ * scenario actually references a secret.
54
+ */
55
+ resolveSecrets?: (keys: string[]) => Promise<Record<string, any>>;
44
56
  };
45
57
 
58
+ /**
59
+ * Resolves every secret referenced by the scenarios about to run, in one call, and
60
+ * returns a per-scenario view of the result.
61
+ *
62
+ * Partitioned deliberately: ctxBase is shared across scenarios, so handing every
63
+ * scenario the whole map would let each read secrets belonging to the others.
64
+ */
65
+ async function resolveSecretsByScenario(
66
+ scenarios: ScenarioSpec[],
67
+ resolveSecrets?: RunnerOptions["resolveSecrets"],
68
+ ): Promise<Map<string, Record<string, any>>> {
69
+ const keysByScenario = new Map<string, string[]>();
70
+ const allKeys = new Set<string>();
71
+
72
+ for (const scenario of scenarios) {
73
+ const keys = collectSecretKeys(scenario);
74
+ if (keys.length === 0) continue;
75
+ keysByScenario.set(scenario.id, keys);
76
+ keys.forEach((key) => allKeys.add(key));
77
+ }
78
+
79
+ if (allKeys.size === 0) return new Map();
80
+
81
+ if (!resolveSecrets) {
82
+ throw new Error(
83
+ `Scenarios reference secrets (${[...allKeys].join(", ")}) but no secret resolver was provided to the runner.`,
84
+ );
85
+ }
86
+
87
+ // Resolved up front so a missing secret fails the run before any scenario executes.
88
+ const resolved = await resolveSecrets([...allKeys]);
89
+
90
+ const byScenario = new Map<string, Record<string, any>>();
91
+ for (const [scenarioId, keys] of keysByScenario) {
92
+ byScenario.set(
93
+ scenarioId,
94
+ keys.reduce((acc, key) => {
95
+ acc[key] = resolved[key];
96
+ return acc;
97
+ }, {} as Record<string, any>),
98
+ );
99
+ }
100
+ return byScenario;
101
+ }
102
+
46
103
  export async function runFromMetadata(opts: RunnerOptions): Promise<void> {
47
104
  const startedAt = Date.now();
48
105
  const registry = new StepRegistry();
@@ -53,37 +110,58 @@ export async function runFromMetadata(opts: RunnerOptions): Promise<void> {
53
110
  registerTestSteps(registry);
54
111
 
55
112
  const engine = new TestingEngine(registry, opts.defaults);
56
- const scenarios = filterScenarios(opts.metadata.testing.scenarios, {
113
+ const allScenarios = opts.scenarios ?? opts.metadata?.testing?.scenarios ?? [];
114
+ const scenarios = filterScenarios(allScenarios, {
57
115
  scenarioIds: opts.scenarioIds,
58
116
  includeTags: opts.includeTags,
59
117
  skipScenarioIds: opts.skipScenarioIds,
60
118
  });
61
119
 
62
120
  const specRegistry = new SpecRegistry();
63
- const testData = buildTestDataIndex(opts.metadata.testing?.data);
121
+ const testData = buildTestDataIndex(opts.data ?? opts.metadata?.testing?.data);
64
122
  if (opts.specs) {
65
123
  opts.specs(specRegistry);
66
124
  }
67
125
 
126
+ // Fetch the browser before the reporter starts drawing its footer, so the download
127
+ // gets a clean terminal and a missing browser fails before any scenario runs.
128
+ if (scenarios.some(scenarioNeedsUi)) {
129
+ await ensureChromiumInstalled();
130
+ }
131
+
68
132
  const resources = new SimpleResourceStore();
69
- const reporter = opts.reporter ?? new ConsoleReporter();
70
- reporter.onRunStart?.({ total: scenarios.length });
133
+ const reporter: Reporter = opts.reporter ?? new ConsoleReporter();
71
134
  const api = new ApiAdapter(opts.api);
72
135
  const { PlaywrightAdapter } = await import("../adapters/ui/playwright-adapter");
73
136
  const ui = new PlaywrightAdapter(opts.ui);
74
- const ctxBase = { resources, reporter, api, ui, specRegistry, testData, options: opts.options };
137
+ const ctxBase = { resources, reporter, api, ui, specRegistry, testData, env: opts.env, options: opts.options };
75
138
  const uiStarted = { value: false };
76
139
  let passed = 0;
77
140
  let failed = 0;
78
141
  let runError: unknown;
79
142
 
143
+ reporter.onRunStart?.({
144
+ total: scenarios.length,
145
+ startedAt: new Date(startedAt).toISOString(),
146
+ scenarioIds: scenarios.map((s) => s.id),
147
+ scenarios: scenarios.map((s) => ({ id: s.id, name: s.name, type: s.type })),
148
+ });
149
+
80
150
  try {
151
+ // One query for the whole run, after onRunStart so a failure here still reports
152
+ // run.end. Keys that do not resolve are not fatal — they surface at the step that
153
+ // references them, where the reporter can name the token and locate the failure.
154
+ const secretsByScenario = await resolveSecretsByScenario(scenarios, opts.resolveSecrets);
155
+
81
156
  for (const scenario of scenarios) {
82
157
  if (scenarioNeedsUi(scenario)) {
83
158
  await ensureUiStarted(ctxBase, uiStarted);
84
159
  }
85
160
  try {
86
- await engine.runScenario(scenario, ctxBase);
161
+ await engine.runScenario(scenario, {
162
+ ...ctxBase,
163
+ secrets: secretsByScenario.get(scenario.id),
164
+ });
87
165
  passed += 1;
88
166
  } catch (error) {
89
167
  failed += 1;
@@ -91,6 +169,11 @@ export async function runFromMetadata(opts: RunnerOptions): Promise<void> {
91
169
  throw error;
92
170
  }
93
171
  }
172
+ } catch (error) {
173
+ // Also catches a setup failure before the loop (unreachable database, absent
174
+ // encryption key), which would otherwise leave runError unset and report ok: true.
175
+ runError = error;
176
+ throw error;
94
177
  } finally {
95
178
  reporter.onRunEnd?.({
96
179
  ok: !runError,
@@ -100,7 +183,17 @@ export async function runFromMetadata(opts: RunnerOptions): Promise<void> {
100
183
  durationMs: Date.now() - startedAt,
101
184
  });
102
185
  if (uiStarted.value) {
103
- await ui.stop();
186
+ // Only KEEP the whole-run video when the run FAILED — passing runs discard it unread.
187
+ const keepVideo = !!runError;
188
+ await ui.stop({ keepVideo });
189
+ // Video bytes are only finalized after ui.stop(); attach it as a run-level artifact so
190
+ // the reporter uploads it and references it on run.end (queued before flushPending runs).
191
+ if (keepVideo) {
192
+ const video = ui.getRunVideo?.();
193
+ if (video && reporter.attachRunArtifact) {
194
+ reporter.attachRunArtifact({ name: video.name, contentType: video.contentType, data: video.data });
195
+ }
196
+ }
104
197
  }
105
198
  }
106
199
  }
@@ -3,7 +3,7 @@ import type { OpStep } from "../../contracts/testing-metadata.types";
3
3
  import { StepRegistry } from "../../core/step-registry";
4
4
 
5
5
  type GotoInput = { url: string; timeoutMs?: number };
6
- type ExpectUrlInput = { equals?: string; contains?: string };
6
+ type ExpectUrlInput = { equals?: string; contains?: string; timeoutMs?: number };
7
7
 
8
8
  function requirePage(ctx: TestContext, op: string) {
9
9
  if (!ctx.ui || !ctx.ui.page) {
@@ -28,28 +28,32 @@ export function registerNavigationSteps(registry: StepRegistry): void {
28
28
  registry.register("ui.expectUrl", async (ctx: TestContext, step: OpStep) => {
29
29
  const page = requirePage(ctx, "ui.expectUrl");
30
30
  const input = (step.with ?? {}) as ExpectUrlInput;
31
- const current = page.url();
32
31
 
33
- if (input.equals !== undefined) {
34
- if (current !== input.equals) {
35
- throw new Error(
36
- `Expected URL to equal "${input.equals}" but got "${current}"`,
37
- );
38
- }
39
- return;
32
+ if (input.equals === undefined && input.contains === undefined) {
33
+ throw new Error(
34
+ 'Missing "equals" or "contains" in step.with for op "ui.expectUrl"',
35
+ );
40
36
  }
41
37
 
42
- if (input.contains !== undefined) {
43
- if (!current.includes(input.contains)) {
44
- throw new Error(
45
- `Expected URL to contain "${input.contains}" but got "${current}"`,
46
- );
47
- }
48
- return;
49
- }
38
+ const expectation =
39
+ input.equals !== undefined
40
+ ? `equal "${input.equals}"`
41
+ : `contain "${input.contains}"`;
50
42
 
51
- throw new Error(
52
- 'Missing "equals" or "contains" in step.with for op "ui.expectUrl"',
53
- );
43
+ // Waits for the URL rather than sampling it, so an assertion placed straight
44
+ // after a click does not race the navigation.
45
+ try {
46
+ await page.waitForURL(
47
+ (url) =>
48
+ input.equals !== undefined
49
+ ? url.toString() === input.equals
50
+ : url.toString().includes(String(input.contains)),
51
+ { timeout: ctx.ui?.resolveNavigationTimeout(input.timeoutMs) },
52
+ );
53
+ } catch {
54
+ throw new Error(
55
+ `Expected URL to ${expectation} but got "${page.url()}"`,
56
+ );
57
+ }
54
58
  });
55
59
  }
@@ -1 +0,0 @@
1
- //# sourceMappingURL=access-token-storage.service.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-token-storage.service.d.ts","sourceRoot":"","sources":["../../src/services/access-token-storage.service.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=access-token-storage.service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"access-token-storage.service.js","sourceRoot":"","sources":["../../src/services/access-token-storage.service.ts"],"names":[],"mappings":"","sourcesContent":[""]}
File without changes