@sudobility/testomniac_runner_service 0.1.53 → 0.1.55

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 (66) hide show
  1. package/dist/analyzer/page-analyzer/generators/content.d.ts +3 -0
  2. package/dist/analyzer/page-analyzer/generators/content.d.ts.map +1 -0
  3. package/dist/analyzer/page-analyzer/generators/content.js +34 -0
  4. package/dist/analyzer/page-analyzer/generators/content.js.map +1 -0
  5. package/dist/analyzer/page-analyzer/generators/dialogs.d.ts +3 -0
  6. package/dist/analyzer/page-analyzer/generators/dialogs.d.ts.map +1 -0
  7. package/dist/analyzer/page-analyzer/generators/dialogs.js +38 -0
  8. package/dist/analyzer/page-analyzer/generators/dialogs.js.map +1 -0
  9. package/dist/analyzer/page-analyzer/generators/e2e.d.ts +3 -0
  10. package/dist/analyzer/page-analyzer/generators/e2e.d.ts.map +1 -0
  11. package/dist/analyzer/page-analyzer/generators/e2e.js +28 -0
  12. package/dist/analyzer/page-analyzer/generators/e2e.js.map +1 -0
  13. package/dist/analyzer/page-analyzer/generators/forms.d.ts +3 -0
  14. package/dist/analyzer/page-analyzer/generators/forms.d.ts.map +1 -0
  15. package/dist/analyzer/page-analyzer/generators/forms.js +69 -0
  16. package/dist/analyzer/page-analyzer/generators/forms.js.map +1 -0
  17. package/dist/analyzer/page-analyzer/generators/hover-follow-up.d.ts +4 -0
  18. package/dist/analyzer/page-analyzer/generators/hover-follow-up.d.ts.map +1 -0
  19. package/dist/analyzer/page-analyzer/generators/hover-follow-up.js +34 -0
  20. package/dist/analyzer/page-analyzer/generators/hover-follow-up.js.map +1 -0
  21. package/dist/analyzer/page-analyzer/generators/keyboard-disclosure.d.ts +3 -0
  22. package/dist/analyzer/page-analyzer/generators/keyboard-disclosure.d.ts.map +1 -0
  23. package/dist/analyzer/page-analyzer/generators/keyboard-disclosure.js +30 -0
  24. package/dist/analyzer/page-analyzer/generators/keyboard-disclosure.js.map +1 -0
  25. package/dist/analyzer/page-analyzer/generators/navigation.d.ts +3 -0
  26. package/dist/analyzer/page-analyzer/generators/navigation.d.ts.map +1 -0
  27. package/dist/analyzer/page-analyzer/generators/navigation.js +22 -0
  28. package/dist/analyzer/page-analyzer/generators/navigation.js.map +1 -0
  29. package/dist/analyzer/page-analyzer/generators/render.d.ts +3 -0
  30. package/dist/analyzer/page-analyzer/generators/render.d.ts.map +1 -0
  31. package/dist/analyzer/page-analyzer/generators/render.js +26 -0
  32. package/dist/analyzer/page-analyzer/generators/render.js.map +1 -0
  33. package/dist/analyzer/page-analyzer/generators/scaffolds.d.ts +3 -0
  34. package/dist/analyzer/page-analyzer/generators/scaffolds.d.ts.map +1 -0
  35. package/dist/analyzer/page-analyzer/generators/scaffolds.js +36 -0
  36. package/dist/analyzer/page-analyzer/generators/scaffolds.js.map +1 -0
  37. package/dist/analyzer/page-analyzer/generators/semantic-journeys.d.ts +3 -0
  38. package/dist/analyzer/page-analyzer/generators/semantic-journeys.d.ts.map +1 -0
  39. package/dist/analyzer/page-analyzer/generators/semantic-journeys.js +30 -0
  40. package/dist/analyzer/page-analyzer/generators/semantic-journeys.js.map +1 -0
  41. package/dist/analyzer/page-analyzer/generators/variants.d.ts +3 -0
  42. package/dist/analyzer/page-analyzer/generators/variants.d.ts.map +1 -0
  43. package/dist/analyzer/page-analyzer/generators/variants.js +30 -0
  44. package/dist/analyzer/page-analyzer/generators/variants.js.map +1 -0
  45. package/dist/analyzer/{page-analyzer.d.ts → page-analyzer/index.d.ts} +7 -39
  46. package/dist/analyzer/page-analyzer/index.d.ts.map +1 -0
  47. package/dist/analyzer/{page-analyzer.js → page-analyzer/index.js} +63 -405
  48. package/dist/analyzer/page-analyzer/index.js.map +1 -0
  49. package/dist/analyzer/page-analyzer/types.d.ts +28 -0
  50. package/dist/analyzer/page-analyzer/types.d.ts.map +1 -0
  51. package/dist/analyzer/page-analyzer/types.js +1 -0
  52. package/dist/analyzer/page-analyzer/types.js.map +1 -0
  53. package/dist/expertise/tester/core-checks.d.ts.map +1 -1
  54. package/dist/expertise/tester/core-checks.js +2 -1
  55. package/dist/expertise/tester/core-checks.js.map +1 -1
  56. package/dist/orchestrator/runner.d.ts.map +1 -1
  57. package/dist/orchestrator/runner.js +19 -5
  58. package/dist/orchestrator/runner.js.map +1 -1
  59. package/dist/orchestrator/test-element-executor.d.ts.map +1 -1
  60. package/dist/orchestrator/test-element-executor.js +102 -25
  61. package/dist/orchestrator/test-element-executor.js.map +1 -1
  62. package/dist/orchestrator/types.d.ts +2 -0
  63. package/dist/orchestrator/types.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/dist/analyzer/page-analyzer.d.ts.map +0 -1
  66. package/dist/analyzer/page-analyzer.js.map +0 -1
@@ -1,30 +1,6 @@
1
- import type { TestElement, Expectation, ActionableItem, SizeClass, TestSurfaceResponse, TestSurfaceBundleRunResponse, FormInfo, TestStep } from "@sudobility/testomniac_types";
2
- import type { ApiClient } from "../api/client";
3
- import type { DetectedScaffoldRegion } from "../scanner/component-detector";
4
- import type { ScanEventHandler } from "../orchestrator/types";
5
- export interface AnalyzerContext {
6
- runnerId: number;
7
- sizeClass: SizeClass;
8
- uid?: string;
9
- currentTestElementId: number;
10
- currentTestSurfaceId: number;
11
- currentSurfaceRunId: number | null;
12
- html: string;
13
- currentPageStateId: number;
14
- beginningPageStateId: number;
15
- currentPath: string;
16
- pageId: number;
17
- pageRequiresLogin: boolean;
18
- scaffolds: DetectedScaffoldRegion[];
19
- scaffoldSelectorByItemSelector: Record<string, string>;
20
- actionableItems: ActionableItem[];
21
- forms: FormInfo[];
22
- journeySteps: TestStep[];
23
- navigationSurface: TestSurfaceResponse;
24
- bundleRun: TestSurfaceBundleRunResponse;
25
- api: ApiClient;
26
- events: ScanEventHandler;
27
- }
1
+ import type { TestElement, Expectation } from "@sudobility/testomniac_types";
2
+ import type { AnalyzerContext } from "./types";
3
+ export type { AnalyzerContext } from "./types";
28
4
  /**
29
5
  * PageAnalyzer generates expectations and discovers new test elements
30
6
  * during discovery mode.
@@ -40,18 +16,7 @@ export declare class PageAnalyzer {
40
16
  * Called AFTER expertises evaluate and the target page state is established.
41
17
  */
42
18
  generateTestElements(testElement: TestElement, context: AnalyzerContext): Promise<void>;
43
- private generateHoverFollowUpCases;
44
- private generateNavigationTestElements;
45
- private generateScaffoldTestElements;
46
19
  private getScaffoldSurfaceItems;
47
- private generateRenderTestElements;
48
- private generateFormTestElements;
49
- private generateE2ETestElements;
50
- private generateSemanticJourneyTestElements;
51
- private generateDialogLifecycleTestElements;
52
- private generateKeyboardAndDisclosureTestElements;
53
- private generateVariantTestElements;
54
- private generateContentTestElements;
55
20
  private ensureSurfaceRun;
56
21
  private isMouseActionable;
57
22
  private isSurfaceCandidate;
@@ -65,6 +30,9 @@ export declare class PageAnalyzer {
65
30
  private ensureTargetPageState;
66
31
  private ensureScaffolds;
67
32
  private ensureStoredForms;
33
+ private normalizeContext;
34
+ private normalizeActionableItems;
35
+ private normalizeForms;
68
36
  private buildRenderTestElement;
69
37
  private buildFormTestElement;
70
38
  private buildNegativeFormTestElement;
@@ -153,4 +121,4 @@ export declare class PageAnalyzer {
153
121
  private detectPasswordRequirements;
154
122
  private generatePasswordVariants;
155
123
  }
156
- //# sourceMappingURL=page-analyzer.d.ts.map
124
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/analyzer/page-analyzer/index.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,19 @@
1
1
  import { PlaywrightAction, ExpectationType, ExpectationSeverity, } from "@sudobility/testomniac_types";
2
- import { computeHashes } from "../browser/page-utils";
2
+ import { computeHashes } from "../../browser/page-utils";
3
3
  import { createHash } from "node:crypto";
4
- import { fillValuePlanner } from "../planners/fill-value-planner";
5
- import { AUTH_URL_PATTERNS, SIGNUP_URL_PATTERNS } from "../config/constants";
4
+ import { fillValuePlanner } from "../../planners/fill-value-planner";
5
+ import { AUTH_URL_PATTERNS, SIGNUP_URL_PATTERNS } from "../../config/constants";
6
+ import { generateHoverFollowUpCases } from "./generators/hover-follow-up";
7
+ import { generateNavigationTestElements } from "./generators/navigation";
8
+ import { generateScaffoldTestElements } from "./generators/scaffolds";
9
+ import { generateRenderTestElements } from "./generators/render";
10
+ import { generateFormTestElements } from "./generators/forms";
11
+ import { generateE2ETestElements } from "./generators/e2e";
12
+ import { generateSemanticJourneyTestElements } from "./generators/semantic-journeys";
13
+ import { generateDialogLifecycleTestElements } from "./generators/dialogs";
14
+ import { generateKeyboardAndDisclosureTestElements } from "./generators/keyboard-disclosure";
15
+ import { generateVariantTestElements } from "./generators/variants";
16
+ import { generateContentTestElements } from "./generators/content";
6
17
  /**
7
18
  * PageAnalyzer generates expectations and discovers new test elements
8
19
  * during discovery mode.
@@ -13,6 +24,7 @@ export class PageAnalyzer {
13
24
  * Called BEFORE expertises evaluate.
14
25
  */
15
26
  generateExpectations(testElement) {
27
+ const steps = Array.isArray(testElement.steps) ? testElement.steps : [];
16
28
  const expectations = [
17
29
  {
18
30
  expectationType: ExpectationType.PageLoaded,
@@ -28,8 +40,8 @@ export class PageAnalyzer {
28
40
  },
29
41
  ];
30
42
  // If test element has only a navigation action, no more expectations
31
- const isNavigationOnly = testElement.steps.length === 1 &&
32
- testElement.steps[0].action.actionType === PlaywrightAction.Goto;
43
+ const isNavigationOnly = steps.length === 1 &&
44
+ steps[0]?.action?.actionType === PlaywrightAction.Goto;
33
45
  if (!isNavigationOnly) {
34
46
  expectations.push({
35
47
  expectationType: ExpectationType.NoConsoleErrors,
@@ -45,414 +57,35 @@ export class PageAnalyzer {
45
57
  * Called AFTER expertises evaluate and the target page state is established.
46
58
  */
47
59
  async generateTestElements(testElement, context) {
48
- const currentPageStateId = await this.ensureTargetPageState(context);
60
+ const normalizedContext = this.normalizeContext(context);
61
+ const currentPageStateId = await this.ensureTargetPageState(normalizedContext);
49
62
  const resolvedContext = {
50
- ...context,
63
+ ...normalizedContext,
51
64
  currentPageStateId,
52
65
  };
53
66
  if (this.isHoverOnly(testElement)) {
54
- await this.generateHoverFollowUpCases(testElement, resolvedContext);
55
- return;
56
- }
57
- // a. Navigation test elements — for every link on the page
58
- await this.generateNavigationTestElements(resolvedContext);
59
- // b. Render test elements — capture render coverage for the page
60
- await this.generateRenderTestElements(resolvedContext);
61
- // c. Form and password test elements — build form workflows from extracted forms
62
- await this.generateFormTestElements(resolvedContext);
63
- // d. Synthetic journey test elements — build generic business flows from common UI verbs
64
- await this.generateSemanticJourneyTestElements(resolvedContext);
65
- // e. Journey test elements — preserve discovered multi-step flows as standalone e2e tests
66
- await this.generateE2ETestElements(resolvedContext);
67
- // f. Dialog lifecycle cases — only when a dialog is already open in the analyzed state
68
- await this.generateDialogLifecycleTestElements(resolvedContext);
69
- // g. Scaffold test elements — for each scaffold's actionable elements
70
- await this.generateScaffoldTestElements(resolvedContext);
71
- // h. Content test elements — for non-scaffold actionable elements
72
- await this.generateContentTestElements(resolvedContext);
73
- // i. Keyboard/disclosure cases
74
- await this.generateKeyboardAndDisclosureTestElements(resolvedContext);
75
- // j. Variant/option cases
76
- await this.generateVariantTestElements(resolvedContext);
77
- }
78
- async generateHoverFollowUpCases(testElement, context) {
79
- const selector = this.getPrimarySelector(testElement);
80
- if (!selector || !context.currentPageStateId)
81
- return;
82
- const beginningItems = context.beginningPageStateId > 0
83
- ? await context.api.getItemsByPageState(context.beginningPageStateId)
84
- : [];
85
- const beginningKeys = new Set(beginningItems.map(item => this.getItemKey(item)).filter(Boolean));
86
- const revealedItems = this.selectRepresentativeItems(context.actionableItems.filter(item => {
87
- if (!this.isMouseActionable(item))
88
- return false;
89
- const key = this.getItemKey(item);
90
- return Boolean(key) && !beginningKeys.has(key);
91
- }));
92
- const hoveredItem = context.actionableItems.find(item => item.selector === selector) ?? null;
93
- if (revealedItems.length === 0 && hoveredItem) {
94
- const clickCase = this.buildClickTestElement(hoveredItem, context.currentPath, context.sizeClass, context.uid, context.currentPageStateId, context.currentTestElementId);
95
- const tc = await context.api.ensureTestElement(context.runnerId, context.currentTestSurfaceId, clickCase);
96
- await context.api.createTestElementRun({
97
- testElementId: tc.id,
98
- testSurfaceRunId: context.currentSurfaceRunId ?? undefined,
99
- });
100
- return;
101
- }
102
- for (const item of revealedItems) {
103
- const nextHover = this.buildHoverTestElement(item, context.currentPath, context.sizeClass, context.uid, context.currentPageStateId, context.currentTestElementId);
104
- const tc = await context.api.ensureTestElement(context.runnerId, context.currentTestSurfaceId, nextHover);
105
- await context.api.createTestElementRun({
106
- testElementId: tc.id,
107
- testSurfaceRunId: context.currentSurfaceRunId ?? undefined,
108
- });
109
- }
110
- }
111
- async generateNavigationTestElements(context) {
112
- if (context.pageRequiresLogin)
67
+ await generateHoverFollowUpCases(this, testElement, resolvedContext);
113
68
  return;
114
- const { api, runnerId, sizeClass, uid, navigationSurface, bundleRun } = context;
115
- const links = this.selectRepresentativeItems(context.actionableItems.filter(item => item.actionKind === "navigate" && item.href && item.visible));
116
- // Ensure navigation surface is in the bundle
117
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, navigationSurface.id);
118
- // Ensure a surface run exists for the navigation surface under this bundle run
119
- const surfaceRun = await this.ensureSurfaceRun(api, navigationSurface.id, bundleRun.id);
120
- for (const link of links) {
121
- if (!link.href)
122
- continue;
123
- const path = this.extractRelativePath(link.href);
124
- if (!path)
125
- continue;
126
- const testElement = this.buildNavigationTestElement(path, sizeClass, uid, context.currentPageStateId);
127
- const tc = await api.ensureTestElement(runnerId, navigationSurface.id, testElement);
128
- await api.createTestElementRun({
129
- testElementId: tc.id,
130
- testSurfaceRunId: surfaceRun.id,
131
- });
132
- }
133
- }
134
- async generateScaffoldTestElements(context) {
135
- const { api, runnerId, sizeClass, uid, bundleRun } = context;
136
- for (const scaffold of context.scaffolds) {
137
- const scaffoldItems = this.selectRepresentativeItems(this.getScaffoldSurfaceItems(context, scaffold));
138
- if (scaffoldItems.length === 0)
139
- continue;
140
- // Ensure a test surface for this scaffold
141
- const surfaceTitle = `Scaffold: ${scaffold.type}`;
142
- const surface = await api.ensureTestSurface(runnerId, {
143
- title: surfaceTitle,
144
- description: `Tests for ${scaffold.type} scaffold`,
145
- startingPageStateId: context.currentPageStateId,
146
- startingPath: context.currentPath,
147
- sizeClass,
148
- priority: 3,
149
- surface_tags: ["scaffold", scaffold.type],
150
- uid,
151
- });
152
- context.events.onTestSurfaceCreated({
153
- surfaceId: surface.id,
154
- title: surface.title,
155
- });
156
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
157
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
158
- for (const item of scaffoldItems) {
159
- const testElement = this.shouldUseDirectControlInteraction(item)
160
- ? this.buildControlInteractionTestElement(item, context.currentPath, sizeClass, uid, context.currentPageStateId)
161
- : this.buildHoverTestElement(item, context.currentPath, sizeClass, uid, context.currentPageStateId);
162
- const tc = await api.ensureTestElement(runnerId, surface.id, testElement);
163
- await api.createTestElementRun({
164
- testElementId: tc.id,
165
- testSurfaceRunId: surfaceRun.id,
166
- });
167
- }
168
69
  }
70
+ await generateNavigationTestElements(this, resolvedContext);
71
+ await generateRenderTestElements(this, resolvedContext);
72
+ await generateFormTestElements(this, resolvedContext);
73
+ await generateSemanticJourneyTestElements(this, resolvedContext);
74
+ await generateE2ETestElements(this, resolvedContext);
75
+ await generateDialogLifecycleTestElements(this, resolvedContext);
76
+ await generateScaffoldTestElements(this, resolvedContext);
77
+ await generateContentTestElements(this, resolvedContext);
78
+ await generateKeyboardAndDisclosureTestElements(this, resolvedContext);
79
+ await generateVariantTestElements(this, resolvedContext);
169
80
  }
170
81
  getScaffoldSurfaceItems(context, scaffold) {
171
- return context.actionableItems.filter(item => {
82
+ return this.normalizeActionableItems(context.actionableItems).filter(item => {
172
83
  if (!this.isSurfaceCandidate(item) || !item.selector)
173
84
  return false;
174
85
  return (context.scaffoldSelectorByItemSelector[item.selector] ===
175
86
  scaffold.selector);
176
87
  });
177
88
  }
178
- async generateRenderTestElements(context) {
179
- const { api, runnerId, sizeClass, uid, bundleRun } = context;
180
- const surface = await api.ensureTestSurface(runnerId, {
181
- title: `Render: ${context.currentPath}`,
182
- description: `Render validation for ${context.currentPath}`,
183
- startingPageStateId: context.currentPageStateId,
184
- startingPath: context.currentPath,
185
- sizeClass,
186
- priority: 2,
187
- surface_tags: ["render"],
188
- uid,
189
- });
190
- context.events.onTestSurfaceCreated({
191
- surfaceId: surface.id,
192
- title: surface.title,
193
- });
194
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
195
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
196
- const testElement = this.buildRenderTestElement(context.currentPath, sizeClass, uid, context.currentPageStateId, context.pageId);
197
- const tc = await api.ensureTestElement(runnerId, surface.id, testElement);
198
- await api.createTestElementRun({
199
- testElementId: tc.id,
200
- testSurfaceRunId: surfaceRun.id,
201
- });
202
- }
203
- async generateFormTestElements(context) {
204
- if (context.forms.length === 0)
205
- return;
206
- const { api, runnerId, sizeClass, uid, bundleRun } = context;
207
- const surface = await api.ensureTestSurface(runnerId, {
208
- title: `Forms: ${context.currentPath}`,
209
- description: `Form workflows for ${context.currentPath}`,
210
- startingPageStateId: context.currentPageStateId,
211
- startingPath: context.currentPath,
212
- sizeClass,
213
- priority: 2,
214
- surface_tags: ["form"],
215
- uid,
216
- });
217
- context.events.onTestSurfaceCreated({
218
- surfaceId: surface.id,
219
- title: surface.title,
220
- });
221
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
222
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
223
- for (let index = 0; index < context.forms.length; index++) {
224
- const form = context.forms[index];
225
- const formType = this.identifyFormType(form, context.currentPath);
226
- const formLabel = this.describeForm(form, index);
227
- const validValues = this.planFormValues(form, context.actionableItems);
228
- if (this.isSearchForm(form)) {
229
- const searchTests = this.buildSearchTestElements(form, formLabel, context.currentPath, sizeClass, uid, context.currentPageStateId, validValues, context.actionableItems);
230
- for (const searchTest of searchTests) {
231
- const searchElement = await api.ensureTestElement(runnerId, surface.id, searchTest);
232
- await api.createTestElementRun({
233
- testElementId: searchElement.id,
234
- testSurfaceRunId: surfaceRun.id,
235
- });
236
- }
237
- continue;
238
- }
239
- const positive = this.buildFormTestElement(form, formLabel, formType, context.currentPath, sizeClass, uid, context.currentPageStateId, validValues);
240
- const positiveElement = await api.ensureTestElement(runnerId, surface.id, positive);
241
- await api.createTestElementRun({
242
- testElementId: positiveElement.id,
243
- testSurfaceRunId: surfaceRun.id,
244
- });
245
- for (const field of form.fields.filter(field => this.isNegativeCandidateField(field))) {
246
- const negative = this.buildNegativeFormTestElement(form, formLabel, formType, field, context.currentPath, sizeClass, uid, context.currentPageStateId, validValues);
247
- const negativeElement = await api.ensureTestElement(runnerId, surface.id, negative);
248
- await api.createTestElementRun({
249
- testElementId: negativeElement.id,
250
- testSurfaceRunId: surfaceRun.id,
251
- });
252
- const correction = this.buildFormCorrectionTestElement(form, formLabel, formType, field, context.currentPath, sizeClass, uid, context.currentPageStateId, validValues);
253
- const correctionElement = await api.ensureTestElement(runnerId, surface.id, correction);
254
- await api.createTestElementRun({
255
- testElementId: correctionElement.id,
256
- testSurfaceRunId: surfaceRun.id,
257
- });
258
- }
259
- if (this.isPasswordScenario(formType, form)) {
260
- const passwordTests = this.buildPasswordTestElements(form, formLabel, formType, context.currentPath, sizeClass, uid, context.currentPageStateId, validValues, this.detectPasswordRequirements(this.extractVisibleText(context.html)));
261
- for (const passwordTest of passwordTests) {
262
- const passwordElement = await api.ensureTestElement(runnerId, surface.id, passwordTest);
263
- await api.createTestElementRun({
264
- testElementId: passwordElement.id,
265
- testSurfaceRunId: surfaceRun.id,
266
- });
267
- }
268
- }
269
- }
270
- }
271
- async generateE2ETestElements(context) {
272
- if (context.journeySteps.length < 2)
273
- return;
274
- const { api, runnerId, sizeClass, uid, bundleRun } = context;
275
- const surface = await api.ensureTestSurface(runnerId, {
276
- title: `Journeys: ${context.currentPath}`,
277
- description: `End-to-end journeys reaching ${context.currentPath}`,
278
- startingPageStateId: context.currentPageStateId,
279
- startingPath: context.currentPath,
280
- sizeClass,
281
- priority: 2,
282
- surface_tags: ["e2e"],
283
- uid,
284
- });
285
- context.events.onTestSurfaceCreated({
286
- surfaceId: surface.id,
287
- title: surface.title,
288
- });
289
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
290
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
291
- const e2e = this.buildE2ETestElement(context.currentPath, sizeClass, uid, context.currentPageStateId, context.journeySteps);
292
- const tc = await api.ensureTestElement(runnerId, surface.id, e2e);
293
- await api.createTestElementRun({
294
- testElementId: tc.id,
295
- testSurfaceRunId: surfaceRun.id,
296
- });
297
- }
298
- async generateSemanticJourneyTestElements(context) {
299
- const journeys = this.buildSemanticJourneyTestElements(context);
300
- if (journeys.length === 0)
301
- return;
302
- const { api, runnerId, bundleRun } = context;
303
- const surface = await api.ensureTestSurface(runnerId, {
304
- title: `Journeys: ${context.currentPath}`,
305
- description: `Semantic multi-step journeys from ${context.currentPath}`,
306
- startingPageStateId: context.currentPageStateId,
307
- startingPath: context.currentPath,
308
- sizeClass: context.sizeClass,
309
- priority: 2,
310
- surface_tags: ["e2e", "semantic-journey"],
311
- uid: context.uid,
312
- });
313
- context.events.onTestSurfaceCreated({
314
- surfaceId: surface.id,
315
- title: surface.title,
316
- });
317
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
318
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
319
- for (const journey of journeys) {
320
- const tc = await api.ensureTestElement(runnerId, surface.id, journey);
321
- await api.createTestElementRun({
322
- testElementId: tc.id,
323
- testSurfaceRunId: surfaceRun.id,
324
- });
325
- }
326
- }
327
- async generateDialogLifecycleTestElements(context) {
328
- if (!this.pageHasOpenDialog(context.html))
329
- return;
330
- const closeCandidates = this.selectRepresentativeItems(context.actionableItems.filter(item => item.visible &&
331
- !item.disabled &&
332
- item.selector &&
333
- this.isDialogCloseItem(item)));
334
- const tests = [];
335
- for (const item of closeCandidates) {
336
- tests.push(this.buildDialogCloseTestElement(item, context.currentPath, context.sizeClass, context.uid, context.currentPageStateId));
337
- }
338
- tests.push(this.buildEscapeDialogTestElement(context.currentPath, context.sizeClass, context.uid, context.currentPageStateId));
339
- const { api, runnerId, bundleRun } = context;
340
- const surface = await api.ensureTestSurface(runnerId, {
341
- title: `Dialogs: ${context.currentPath}`,
342
- description: `Dialog lifecycle checks for ${context.currentPath}`,
343
- startingPageStateId: context.currentPageStateId,
344
- startingPath: context.currentPath,
345
- sizeClass: context.sizeClass,
346
- priority: 2,
347
- surface_tags: ["dialog"],
348
- uid: context.uid,
349
- });
350
- context.events.onTestSurfaceCreated({
351
- surfaceId: surface.id,
352
- title: surface.title,
353
- });
354
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
355
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
356
- for (const test of tests) {
357
- const tc = await api.ensureTestElement(runnerId, surface.id, test);
358
- await api.createTestElementRun({
359
- testElementId: tc.id,
360
- testSurfaceRunId: surfaceRun.id,
361
- });
362
- }
363
- }
364
- async generateKeyboardAndDisclosureTestElements(context) {
365
- const tests = this.buildKeyboardAndDisclosureTestElements(context);
366
- if (tests.length === 0)
367
- return;
368
- const { api, runnerId, bundleRun } = context;
369
- const surface = await api.ensureTestSurface(runnerId, {
370
- title: `Keyboard: ${context.currentPath}`,
371
- description: `Keyboard parity and disclosure checks for ${context.currentPath}`,
372
- startingPageStateId: context.currentPageStateId,
373
- startingPath: context.currentPath,
374
- sizeClass: context.sizeClass,
375
- priority: 3,
376
- surface_tags: ["keyboard", "disclosure"],
377
- uid: context.uid,
378
- });
379
- context.events.onTestSurfaceCreated({
380
- surfaceId: surface.id,
381
- title: surface.title,
382
- });
383
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
384
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
385
- for (const test of tests) {
386
- const tc = await api.ensureTestElement(runnerId, surface.id, test);
387
- await api.createTestElementRun({
388
- testElementId: tc.id,
389
- testSurfaceRunId: surfaceRun.id,
390
- });
391
- }
392
- }
393
- async generateVariantTestElements(context) {
394
- const tests = this.buildVariantTestElements(context);
395
- if (tests.length === 0)
396
- return;
397
- const { api, runnerId, bundleRun } = context;
398
- const surface = await api.ensureTestSurface(runnerId, {
399
- title: `Variants: ${context.currentPath}`,
400
- description: `Variant and option state checks for ${context.currentPath}`,
401
- startingPageStateId: context.currentPageStateId,
402
- startingPath: context.currentPath,
403
- sizeClass: context.sizeClass,
404
- priority: 2,
405
- surface_tags: ["variant", "option"],
406
- uid: context.uid,
407
- });
408
- context.events.onTestSurfaceCreated({
409
- surfaceId: surface.id,
410
- title: surface.title,
411
- });
412
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
413
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
414
- for (const test of tests) {
415
- const tc = await api.ensureTestElement(runnerId, surface.id, test);
416
- await api.createTestElementRun({
417
- testElementId: tc.id,
418
- testSurfaceRunId: surfaceRun.id,
419
- });
420
- }
421
- }
422
- async generateContentTestElements(context) {
423
- const { api, runnerId, sizeClass, uid, bundleRun, pageId: _pageId, } = context;
424
- // Content items are those NOT in a scaffold
425
- const contentItems = this.selectRepresentativeItems(context.actionableItems.filter(item => item.scaffoldId == null && this.isSurfaceCandidate(item)));
426
- if (contentItems.length === 0)
427
- return;
428
- const surfaceTitle = `Page: ${context.currentPath}`;
429
- const surface = await api.ensureTestSurface(runnerId, {
430
- title: surfaceTitle,
431
- description: `Tests for page content at ${context.currentPath}`,
432
- startingPageStateId: context.currentPageStateId,
433
- startingPath: context.currentPath,
434
- sizeClass,
435
- priority: 3,
436
- surface_tags: ["page-content"],
437
- uid,
438
- });
439
- context.events.onTestSurfaceCreated({
440
- surfaceId: surface.id,
441
- title: surface.title,
442
- });
443
- await api.ensureBundleSurfaceLink(bundleRun.testSurfaceBundleId, surface.id);
444
- const surfaceRun = await this.ensureSurfaceRun(api, surface.id, bundleRun.id);
445
- for (const item of contentItems) {
446
- const testElement = this.shouldUseDirectControlInteraction(item)
447
- ? this.buildControlInteractionTestElement(item, context.currentPath, sizeClass, uid, context.currentPageStateId)
448
- : this.buildHoverTestElement(item, context.currentPath, sizeClass, uid, context.currentPageStateId);
449
- const tc = await api.ensureTestElement(runnerId, surface.id, testElement);
450
- await api.createTestElementRun({
451
- testElementId: tc.id,
452
- testSurfaceRunId: surfaceRun.id,
453
- });
454
- }
455
- }
456
89
  async ensureSurfaceRun(api, testSurfaceId, bundleRunId) {
457
90
  const openSurfaceRuns = await api.getOpenTestSurfaceRuns(bundleRunId);
458
91
  const existing = openSurfaceRuns.find(surfaceRun => surfaceRun.testSurfaceId === testSurfaceId);
@@ -566,11 +199,13 @@ export class PageAnalyzer {
566
199
  };
567
200
  }
568
201
  isHoverOnly(testElement) {
569
- return (testElement.steps.length === 1 &&
570
- testElement.steps[0].action.actionType === PlaywrightAction.Hover);
202
+ const steps = Array.isArray(testElement.steps) ? testElement.steps : [];
203
+ return (steps.length === 1 &&
204
+ steps[0]?.action?.actionType === PlaywrightAction.Hover);
571
205
  }
572
206
  getPrimarySelector(testElement) {
573
- const step = testElement.steps[0];
207
+ const steps = Array.isArray(testElement.steps) ? testElement.steps : [];
208
+ const step = steps[0];
574
209
  return step?.action.path ?? null;
575
210
  }
576
211
  getItemKey(item) {
@@ -649,16 +284,39 @@ export class PageAnalyzer {
649
284
  return scaffoldIdsBySelector;
650
285
  }
651
286
  async ensureStoredForms(pageStateId, context) {
652
- if (context.forms.length === 0)
287
+ const forms = this.normalizeForms(context.forms);
288
+ if (forms.length === 0)
653
289
  return;
654
290
  const existing = await context.api.getFormsByPageState(pageStateId);
655
291
  const existingSelectors = new Set(existing.map(form => form.selector));
656
- for (const form of context.forms) {
292
+ for (const form of forms) {
657
293
  if (existingSelectors.has(form.selector))
658
294
  continue;
659
295
  await context.api.insertForm(pageStateId, form, this.identifyFormType(form, context.currentPath));
660
296
  }
661
297
  }
298
+ normalizeContext(context) {
299
+ return {
300
+ ...context,
301
+ html: typeof context.html === "string" ? context.html : "",
302
+ scaffolds: Array.isArray(context.scaffolds) ? context.scaffolds : [],
303
+ scaffoldSelectorByItemSelector: context.scaffoldSelectorByItemSelector &&
304
+ typeof context.scaffoldSelectorByItemSelector === "object"
305
+ ? context.scaffoldSelectorByItemSelector
306
+ : {},
307
+ actionableItems: this.normalizeActionableItems(context.actionableItems),
308
+ forms: this.normalizeForms(context.forms),
309
+ journeySteps: Array.isArray(context.journeySteps)
310
+ ? context.journeySteps
311
+ : [],
312
+ };
313
+ }
314
+ normalizeActionableItems(items) {
315
+ return Array.isArray(items) ? items : [];
316
+ }
317
+ normalizeForms(forms) {
318
+ return Array.isArray(forms) ? forms : [];
319
+ }
662
320
  buildRenderTestElement(currentPath, sizeClass, uid, startingPageStateId, pageId) {
663
321
  return {
664
322
  title: `Render — ${currentPath}`,
@@ -2550,4 +2208,4 @@ export class PageAnalyzer {
2550
2208
  return variants;
2551
2209
  }
2552
2210
  }
2553
- //# sourceMappingURL=page-analyzer.js.map
2211
+ //# sourceMappingURL=index.js.map