@sudobility/testomniac_runner_service 0.1.45 → 0.1.47

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 (89) hide show
  1. package/dist/analyzer/page-analyzer.d.ts +78 -1
  2. package/dist/analyzer/page-analyzer.d.ts.map +1 -1
  3. package/dist/analyzer/page-analyzer.js +1600 -6
  4. package/dist/analyzer/page-analyzer.js.map +1 -1
  5. package/dist/browser/control-snapshot.d.ts +4 -0
  6. package/dist/browser/control-snapshot.d.ts.map +1 -0
  7. package/dist/browser/control-snapshot.js +181 -0
  8. package/dist/browser/control-snapshot.js.map +1 -0
  9. package/dist/browser/dom-snapshot.d.ts.map +1 -1
  10. package/dist/browser/dom-snapshot.js +8 -0
  11. package/dist/browser/dom-snapshot.js.map +1 -1
  12. package/dist/browser/ui-snapshot.d.ts +9 -0
  13. package/dist/browser/ui-snapshot.d.ts.map +1 -0
  14. package/dist/browser/ui-snapshot.js +53 -0
  15. package/dist/browser/ui-snapshot.js.map +1 -0
  16. package/dist/expertise/content-expertise.d.ts +1 -0
  17. package/dist/expertise/content-expertise.d.ts.map +1 -1
  18. package/dist/expertise/content-expertise.js +38 -0
  19. package/dist/expertise/content-expertise.js.map +1 -1
  20. package/dist/expertise/tester/commerce-checks.d.ts +14 -0
  21. package/dist/expertise/tester/commerce-checks.d.ts.map +1 -0
  22. package/dist/expertise/tester/commerce-checks.js +132 -0
  23. package/dist/expertise/tester/commerce-checks.js.map +1 -0
  24. package/dist/expertise/tester/control-state.d.ts +31 -0
  25. package/dist/expertise/tester/control-state.d.ts.map +1 -0
  26. package/dist/expertise/tester/control-state.js +46 -0
  27. package/dist/expertise/tester/control-state.js.map +1 -0
  28. package/dist/expertise/tester/core-checks.d.ts +5 -0
  29. package/dist/expertise/tester/core-checks.d.ts.map +1 -0
  30. package/dist/expertise/tester/core-checks.js +46 -0
  31. package/dist/expertise/tester/core-checks.js.map +1 -0
  32. package/dist/expertise/tester/dialog-feedback-checks.d.ts +13 -0
  33. package/dist/expertise/tester/dialog-feedback-checks.d.ts.map +1 -0
  34. package/dist/expertise/tester/dialog-feedback-checks.js +82 -0
  35. package/dist/expertise/tester/dialog-feedback-checks.js.map +1 -0
  36. package/dist/expertise/tester/form-checks.d.ts +8 -0
  37. package/dist/expertise/tester/form-checks.d.ts.map +1 -0
  38. package/dist/expertise/tester/form-checks.js +50 -0
  39. package/dist/expertise/tester/form-checks.js.map +1 -0
  40. package/dist/expertise/tester/keyboard-disclosure-checks.d.ts +7 -0
  41. package/dist/expertise/tester/keyboard-disclosure-checks.d.ts.map +1 -0
  42. package/dist/expertise/tester/keyboard-disclosure-checks.js +46 -0
  43. package/dist/expertise/tester/keyboard-disclosure-checks.js.map +1 -0
  44. package/dist/expertise/tester/navigation-checks.d.ts +8 -0
  45. package/dist/expertise/tester/navigation-checks.d.ts.map +1 -0
  46. package/dist/expertise/tester/navigation-checks.js +64 -0
  47. package/dist/expertise/tester/navigation-checks.js.map +1 -0
  48. package/dist/expertise/tester/network-intent-checks.d.ts +7 -0
  49. package/dist/expertise/tester/network-intent-checks.d.ts.map +1 -0
  50. package/dist/expertise/tester/network-intent-checks.js +56 -0
  51. package/dist/expertise/tester/network-intent-checks.js.map +1 -0
  52. package/dist/expertise/tester/page-behavior-checks.d.ts +17 -0
  53. package/dist/expertise/tester/page-behavior-checks.d.ts.map +1 -0
  54. package/dist/expertise/tester/page-behavior-checks.js +144 -0
  55. package/dist/expertise/tester/page-behavior-checks.js.map +1 -0
  56. package/dist/expertise/tester/persistence-checks.d.ts +12 -0
  57. package/dist/expertise/tester/persistence-checks.d.ts.map +1 -0
  58. package/dist/expertise/tester/persistence-checks.js +109 -0
  59. package/dist/expertise/tester/persistence-checks.js.map +1 -0
  60. package/dist/expertise/tester/search-checks.d.ts +10 -0
  61. package/dist/expertise/tester/search-checks.d.ts.map +1 -0
  62. package/dist/expertise/tester/search-checks.js +111 -0
  63. package/dist/expertise/tester/search-checks.js.map +1 -0
  64. package/dist/expertise/tester/selection-control-checks.d.ts +7 -0
  65. package/dist/expertise/tester/selection-control-checks.d.ts.map +1 -0
  66. package/dist/expertise/tester/selection-control-checks.js +128 -0
  67. package/dist/expertise/tester/selection-control-checks.js.map +1 -0
  68. package/dist/expertise/tester/text-input-checks.d.ts +8 -0
  69. package/dist/expertise/tester/text-input-checks.d.ts.map +1 -0
  70. package/dist/expertise/tester/text-input-checks.js +194 -0
  71. package/dist/expertise/tester/text-input-checks.js.map +1 -0
  72. package/dist/expertise/tester/validation-checks.d.ts +10 -0
  73. package/dist/expertise/tester/validation-checks.d.ts.map +1 -0
  74. package/dist/expertise/tester/validation-checks.js +59 -0
  75. package/dist/expertise/tester/validation-checks.js.map +1 -0
  76. package/dist/expertise/tester-expertise.d.ts +0 -3
  77. package/dist/expertise/tester-expertise.d.ts.map +1 -1
  78. package/dist/expertise/tester-expertise.js +75 -49
  79. package/dist/expertise/tester-expertise.js.map +1 -1
  80. package/dist/expertise/types.d.ts +12 -1
  81. package/dist/expertise/types.d.ts.map +1 -1
  82. package/dist/extractors/form-extractor.d.ts +19 -0
  83. package/dist/extractors/form-extractor.d.ts.map +1 -1
  84. package/dist/extractors/form-extractor.js +271 -42
  85. package/dist/extractors/form-extractor.js.map +1 -1
  86. package/dist/orchestrator/test-element-executor.d.ts.map +1 -1
  87. package/dist/orchestrator/test-element-executor.js +74 -9
  88. package/dist/orchestrator/test-element-executor.js.map +1 -1
  89. package/package.json +3 -3
@@ -1,18 +1,29 @@
1
- import type { Expectation, NetworkLogEntry } from "@sudobility/testomniac_types";
1
+ import type { Expectation, ExpectationSeverity, NetworkLogEntry } from "@sudobility/testomniac_types";
2
2
  import type { DetectedScaffoldRegion } from "../scanner/component-detector";
3
3
  import type { DetectedPatternWithInstances } from "../scanner/pattern-detector";
4
+ import type { ControlState } from "./tester/control-state";
5
+ import type { UiSnapshot } from "../browser/ui-snapshot";
4
6
  export interface Outcome {
5
7
  expected: string;
6
8
  observed: string;
7
9
  result: "pass" | "warning" | "error";
10
+ severity?: ExpectationSeverity;
8
11
  }
9
12
  export interface ExpertiseContext {
10
13
  html: string;
14
+ initialHtml: string;
11
15
  scaffolds: DetectedScaffoldRegion[];
12
16
  patterns: DetectedPatternWithInstances[];
13
17
  consoleLogs: string[];
14
18
  networkLogs: NetworkLogEntry[];
15
19
  expectations: Expectation[];
20
+ initialUrl?: string;
21
+ currentUrl?: string;
22
+ startingPath?: string;
23
+ initialUiSnapshot: UiSnapshot;
24
+ finalUiSnapshot: UiSnapshot;
25
+ initialControlStates: ControlState[];
26
+ finalControlStates: ControlState[];
16
27
  }
17
28
  export interface Expertise {
18
29
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/expertise/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAEhF,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,sBAAsB,EAAE,CAAC;IACpC,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,YAAY,EAAE,WAAW,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,EAAE,CAAC;CAChD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/expertise/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACrC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,sBAAsB,EAAE,CAAC;IACpC,QAAQ,EAAE,4BAA4B,EAAE,CAAC;IACzC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,UAAU,CAAC;IAC9B,eAAe,EAAE,UAAU,CAAC;IAC5B,oBAAoB,EAAE,YAAY,EAAE,CAAC;IACrC,kBAAkB,EAAE,YAAY,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,EAAE,CAAC;CAChD"}
@@ -1,4 +1,23 @@
1
1
  import type { BrowserAdapter } from "../adapter";
2
2
  import type { FormInfo } from "../domain/types";
3
+ type AnalyzerField = FormInfo["fields"][number] & {
4
+ disabled?: boolean;
5
+ readOnly?: boolean;
6
+ appearanceHint?: string;
7
+ };
3
8
  export declare function extractForms(adapter: BrowserAdapter): Promise<FormInfo[]>;
9
+ export declare function extractFormsFromRoot(root: Document | Element): FormInfo[];
10
+ declare function inferMethod(fields: AnalyzerField[]): string;
11
+ declare function matchesFormSignalText(value: string): boolean;
12
+ declare function isSubmitLikeText(value: string): boolean;
13
+ export declare const __test__: {
14
+ inferMethod: typeof inferMethod;
15
+ shouldCreatePseudoFormDescriptor(input: {
16
+ fieldTypes: string[];
17
+ hasSubmitCandidate: boolean;
18
+ }): boolean;
19
+ matchesFormSignalText: typeof matchesFormSignalText;
20
+ isSubmitLikeText: typeof isSubmitLikeText;
21
+ };
22
+ export {};
4
23
  //# sourceMappingURL=form-extractor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-extractor.d.ts","sourceRoot":"","sources":["../../src/extractors/form-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAmDrB"}
1
+ {"version":3,"file":"form-extractor.d.ts","sourceRoot":"","sources":["../../src/extractors/form-extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,KAAK,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAErB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,EAAE,CAiDzE;AAsOD,iBAAS,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAEpD;AA+CD,iBAAS,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIrD;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIhD;AAED,eAAO,MAAM,QAAQ;;4CAEqB;QACtC,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,kBAAkB,EAAE,OAAO,CAAC;KAC7B,GAAG,OAAO;;;CAmBZ,CAAC"}
@@ -1,47 +1,276 @@
1
1
  export async function extractForms(adapter) {
2
- return adapter.evaluate(() => {
3
- function bestSelector(el) {
4
- if (el.id)
5
- return "#" + el.id;
6
- const name = el.getAttribute("name");
7
- if (name)
8
- return `[name="${name}"]`;
9
- return el.tagName.toLowerCase();
10
- }
11
- const forms = [];
12
- document.querySelectorAll("form").forEach((form, idx) => {
13
- const fields = [];
14
- form.querySelectorAll("input, textarea, select").forEach(el => {
15
- const labelEl = el.id
16
- ? document.querySelector(`label[for="${el.id}"]`)
17
- : null;
18
- fields.push({
19
- selector: bestSelector(el),
20
- name: el.getAttribute("name") || "",
21
- type: el.type || el.tagName.toLowerCase(),
22
- label: el.getAttribute("aria-label") ||
23
- el.getAttribute("placeholder") ||
24
- labelEl?.textContent?.trim() ||
25
- "",
26
- required: el.hasAttribute("required") ||
27
- el.getAttribute("aria-required") === "true",
28
- placeholder: el.getAttribute("placeholder") || undefined,
29
- options: el.tagName === "SELECT"
30
- ? Array.from(el.options).map(o => o.value)
31
- : undefined,
32
- });
33
- });
34
- const submitBtn = form.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
35
- forms.push({
36
- selector: form.id ? "#" + form.id : `form:nth-of-type(${idx + 1})`,
37
- action: form.getAttribute("action") || "",
38
- method: (form.getAttribute("method") || "GET").toUpperCase(),
39
- fields,
40
- submitSelector: submitBtn ? bestSelector(submitBtn) : undefined,
41
- fieldCount: fields.length,
42
- });
2
+ return adapter.evaluate(() => extractFormsFromRoot(document));
3
+ }
4
+ export function extractFormsFromRoot(root) {
5
+ const documentRef = root instanceof Document ? root : root.ownerDocument;
6
+ if (!documentRef)
7
+ return [];
8
+ const allControls = Array.from(root.querySelectorAll("input, textarea, select")).filter(isSupportedControl);
9
+ const explicitForms = Array.from(root.querySelectorAll("form"));
10
+ const forms = [];
11
+ const assignedControls = new Set();
12
+ explicitForms.forEach((form, index) => {
13
+ const fields = collectFields(form, documentRef);
14
+ fields.forEach(field => {
15
+ const fieldEl = safeQuerySelector(documentRef, field.selector);
16
+ if (fieldEl)
17
+ assignedControls.add(fieldEl);
18
+ });
19
+ if (fields.length === 0)
20
+ return;
21
+ forms.push(buildFormInfo(form, fields, index));
22
+ });
23
+ const orphanControls = allControls.filter(control => !assignedControls.has(control));
24
+ const grouped = groupOrphanControls(orphanControls);
25
+ grouped.forEach((controls, index) => {
26
+ const container = findGroupingContainer(controls[0]);
27
+ const fields = dedupeBySelector(controls.map(control => buildField(control, documentRef)));
28
+ if (!shouldCreatePseudoForm(fields, container))
29
+ return;
30
+ forms.push({
31
+ selector: container
32
+ ? bestSelector(container)
33
+ : `pseudo-form-${index + 1}`,
34
+ action: "",
35
+ method: inferMethod(fields),
36
+ fields,
37
+ submitSelector: container
38
+ ? findSubmitSelector(container, documentRef)
39
+ : undefined,
40
+ fieldCount: fields.length,
43
41
  });
44
- return forms;
45
42
  });
43
+ return dedupeForms(forms);
44
+ }
45
+ function groupOrphanControls(controls) {
46
+ const groups = new Map();
47
+ for (const control of controls) {
48
+ const container = findGroupingContainer(control) ?? control.parentElement;
49
+ if (!container)
50
+ continue;
51
+ const existing = groups.get(container) ?? [];
52
+ existing.push(control);
53
+ groups.set(container, existing);
54
+ }
55
+ return Array.from(groups.values());
56
+ }
57
+ function collectFields(container, documentRef) {
58
+ const fields = Array.from(container.querySelectorAll("input, textarea, select")).filter(isSupportedControl);
59
+ return dedupeBySelector(fields.map(field => buildField(field, documentRef)));
60
+ }
61
+ function buildFormInfo(form, fields, index) {
62
+ return {
63
+ selector: form.id ? `#${form.id}` : `form:nth-of-type(${index + 1})`,
64
+ action: form.getAttribute("action") || "",
65
+ method: (form.getAttribute("method") || inferMethod(fields)).toUpperCase(),
66
+ fields,
67
+ submitSelector: findSubmitSelector(form, form.ownerDocument),
68
+ fieldCount: fields.length,
69
+ };
70
+ }
71
+ function buildField(el, documentRef) {
72
+ const input = el;
73
+ const labelEl = input.id
74
+ ? documentRef.querySelector(`label[for="${input.id}"]`)
75
+ : null;
76
+ const ariaLabelledBy = el.getAttribute("aria-labelledby");
77
+ const labelledByText = ariaLabelledBy
78
+ ? ariaLabelledBy
79
+ .split(/\s+/)
80
+ .map(id => documentRef.getElementById(id)?.textContent?.trim() || "")
81
+ .filter(Boolean)
82
+ .join(" ")
83
+ : "";
84
+ const wrappingLabel = el.closest("label");
85
+ return {
86
+ selector: bestSelector(el),
87
+ name: el.getAttribute("name") || "",
88
+ type: input.type || el.tagName.toLowerCase(),
89
+ label: el.getAttribute("aria-label") ||
90
+ labelledByText ||
91
+ labelEl?.textContent?.trim() ||
92
+ wrappingLabel?.textContent?.trim() ||
93
+ el.getAttribute("placeholder") ||
94
+ "",
95
+ required: el.hasAttribute("required") ||
96
+ el.getAttribute("aria-required") === "true",
97
+ disabled: (el instanceof HTMLInputElement ||
98
+ el instanceof HTMLTextAreaElement ||
99
+ el instanceof HTMLSelectElement) &&
100
+ el.disabled,
101
+ readOnly: (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) &&
102
+ el.readOnly,
103
+ appearanceHint: [
104
+ el.getAttribute("class") || "",
105
+ el.getAttribute("style") || "",
106
+ el.getAttribute("aria-disabled") || "",
107
+ el.getAttribute("data-testid") || "",
108
+ ]
109
+ .join(" ")
110
+ .trim(),
111
+ placeholder: el.getAttribute("placeholder") || undefined,
112
+ options: el.tagName === "SELECT"
113
+ ? Array.from(el.options).map(option => option.value)
114
+ : undefined,
115
+ };
116
+ }
117
+ function bestSelector(el) {
118
+ if (el.id)
119
+ return `#${cssEscape(el.id)}`;
120
+ const name = el.getAttribute("name");
121
+ if (name)
122
+ return `[name="${escapeAttribute(name)}"]`;
123
+ const testId = el.getAttribute("data-testid") || el.getAttribute("data-test") || "";
124
+ if (testId)
125
+ return `[data-testid="${escapeAttribute(testId)}"]`;
126
+ const className = Array.from(el.classList)
127
+ .find(token => token && !token.includes(":"))
128
+ ?.trim();
129
+ if (className) {
130
+ return `${el.tagName.toLowerCase()}.${cssEscape(className)}`;
131
+ }
132
+ return buildNthSelector(el);
133
+ }
134
+ function buildNthSelector(el) {
135
+ const segments = [];
136
+ let current = el;
137
+ while (current && current.tagName !== "BODY" && segments.length < 4) {
138
+ const tag = current.tagName.toLowerCase();
139
+ const parent = current.parentElement;
140
+ if (!parent) {
141
+ segments.unshift(tag);
142
+ break;
143
+ }
144
+ const siblings = Array.from(parent.children).filter((sibling) => sibling.tagName === current?.tagName);
145
+ const index = siblings.indexOf(current) + 1;
146
+ segments.unshift(`${tag}:nth-of-type(${index})`);
147
+ current = parent;
148
+ }
149
+ return segments.join(" > ");
150
+ }
151
+ function findGroupingContainer(control) {
152
+ let current = control.parentElement;
153
+ while (current && current.tagName !== "BODY") {
154
+ const controlCount = current.querySelectorAll("input, textarea, select").length;
155
+ const hasSubmit = hasSubmitCandidate(current);
156
+ const hasFormSignals = controlCount > 1 ||
157
+ hasSubmit ||
158
+ current.getAttribute("role") === "form" ||
159
+ matchesFormSignalText([
160
+ current.className,
161
+ current.id,
162
+ current.getAttribute("aria-label") || "",
163
+ current.getAttribute("data-testid") || "",
164
+ ].join(" "));
165
+ if (hasFormSignals && controlCount <= 12) {
166
+ return current;
167
+ }
168
+ current = current.parentElement;
169
+ }
170
+ return control.parentElement;
171
+ }
172
+ function hasSubmitCandidate(container) {
173
+ return Boolean(findSubmitElement(container));
174
+ }
175
+ function findSubmitSelector(container, documentRef) {
176
+ const submitElement = findSubmitElement(container);
177
+ if (!submitElement)
178
+ return undefined;
179
+ const selector = bestSelector(submitElement);
180
+ return safeQuerySelector(documentRef, selector) ? selector : undefined;
181
+ }
182
+ function findSubmitElement(container) {
183
+ const candidates = Array.from(container.querySelectorAll('button, input[type="submit"], input[type="button"], a, [role="button"]'));
184
+ const submitCandidate = candidates.find(candidate => {
185
+ const text = [
186
+ candidate.textContent || "",
187
+ candidate.getAttribute("value") || "",
188
+ candidate.getAttribute("aria-label") || "",
189
+ candidate.getAttribute("title") || "",
190
+ candidate.getAttribute("name") || "",
191
+ candidate.getAttribute("id") || "",
192
+ candidate.className || "",
193
+ ]
194
+ .join(" ")
195
+ .toLowerCase();
196
+ return (candidate.getAttribute("type") === "submit" || isSubmitLikeText(text));
197
+ });
198
+ return submitCandidate ?? null;
199
+ }
200
+ function shouldCreatePseudoForm(fields, container) {
201
+ if (fields.length === 0)
202
+ return false;
203
+ if (fields.length > 1)
204
+ return true;
205
+ if (!container)
206
+ return false;
207
+ if (hasSubmitCandidate(container))
208
+ return true;
209
+ const field = fields[0];
210
+ return /^(email|password|search|tel|url|number|date|textarea|select-one|select)$/i.test(field.type);
211
+ }
212
+ function inferMethod(fields) {
213
+ return fields.some(field => /^(search)$/i.test(field.type)) ? "GET" : "POST";
214
+ }
215
+ function dedupeForms(forms) {
216
+ const seen = new Set();
217
+ return forms.filter(form => {
218
+ const key = `${form.selector}|${form.fields.map(field => field.selector).join(",")}`;
219
+ if (seen.has(key))
220
+ return false;
221
+ seen.add(key);
222
+ return true;
223
+ });
224
+ }
225
+ function dedupeBySelector(fields) {
226
+ const seen = new Set();
227
+ return fields.filter(field => {
228
+ if (!field.selector || seen.has(field.selector))
229
+ return false;
230
+ seen.add(field.selector);
231
+ return true;
232
+ });
233
+ }
234
+ function isSupportedControl(el) {
235
+ const type = (el.type || "").toLowerCase();
236
+ return !["hidden", "submit", "button", "reset", "image", "file"].includes(type);
237
+ }
238
+ function safeQuerySelector(documentRef, selector) {
239
+ try {
240
+ return documentRef.querySelector(selector);
241
+ }
242
+ catch {
243
+ return null;
244
+ }
245
+ }
246
+ function escapeAttribute(value) {
247
+ return value.replace(/"/g, '\\"');
248
+ }
249
+ function cssEscape(value) {
250
+ return value.replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
251
+ }
252
+ function matchesFormSignalText(value) {
253
+ return /\b(form|search|login|signin|sign-in|register|account|contact|checkout)\b/i.test(value);
254
+ }
255
+ function isSubmitLikeText(value) {
256
+ return /\b(send|submit|search|sign in|signin|log in|login|register|create account|apply|continue|save|place order|checkout)\b/.test(value);
46
257
  }
258
+ export const __test__ = {
259
+ inferMethod,
260
+ shouldCreatePseudoFormDescriptor(input) {
261
+ const fields = input.fieldTypes.map(type => ({
262
+ selector: type,
263
+ name: type,
264
+ type,
265
+ label: type,
266
+ required: false,
267
+ }));
268
+ return input.fieldTypes.length > 1
269
+ ? true
270
+ : input.hasSubmitCandidate ||
271
+ shouldCreatePseudoForm(fields, input.hasSubmitCandidate ? {} : null);
272
+ },
273
+ matchesFormSignalText,
274
+ isSubmitLikeText,
275
+ };
47
276
  //# sourceMappingURL=form-extractor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"form-extractor.js","sourceRoot":"","sources":["../../src/extractors/form-extractor.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAuB;IAEvB,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,EAAE;QAC3B,SAAS,YAAY,CAAC,EAAW;YAC/B,IAAI,EAAE,CAAC,EAAE;gBAAE,OAAO,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACrC,IAAI,IAAI;gBAAE,OAAO,UAAU,IAAI,IAAI,CAAC;YACpC,OAAO,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,KAAK,GAAU,EAAE,CAAC;QACxB,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YACtD,MAAM,MAAM,GAAU,EAAE,CAAC;YACzB,IAAI,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE;gBAC5D,MAAM,OAAO,GAAI,EAAuB,CAAC,EAAE;oBACzC,CAAC,CAAC,QAAQ,CAAC,aAAa,CACpB,cAAe,EAAuB,CAAC,EAAE,IAAI,CAC9C;oBACH,CAAC,CAAC,IAAI,CAAC;gBACT,MAAM,CAAC,IAAI,CAAC;oBACV,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;oBAC1B,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE;oBACnC,IAAI,EAAG,EAAuB,CAAC,IAAI,IAAI,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE;oBAC/D,KAAK,EACH,EAAE,CAAC,YAAY,CAAC,YAAY,CAAC;wBAC7B,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC;wBAC9B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE;wBAC5B,EAAE;oBACJ,QAAQ,EACN,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC;wBAC3B,EAAE,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,MAAM;oBAC7C,WAAW,EAAE,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,SAAS;oBACxD,OAAO,EACL,EAAE,CAAC,OAAO,KAAK,QAAQ;wBACrB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAE,EAAwB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;wBACjE,CAAC,CAAC,SAAS;iBAChB,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAClC,iEAAiE,CAClE,CAAC;YACF,KAAK,CAAC,IAAI,CAAC;gBACT,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,oBAAoB,GAAG,GAAG,CAAC,GAAG;gBAClE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE;gBACzC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE;gBAC5D,MAAM;gBACN,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC/D,UAAU,EAAE,MAAM,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"form-extractor.js","sourceRoot":"","sources":["../../src/extractors/form-extractor.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-element-executor.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-element-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAA6B,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAKjE;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,sBAAsB,EACtC,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,SAAS,EAAE,EACvB,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,CAAC,EAAE;IACjB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,SAAS,EAAE,4BAA4B,CAAC;CACzC,GACA,OAAO,CAAC,IAAI,CAAC,CAsQf"}
1
+ {"version":3,"file":"test-element-executor.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-element-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAA6B,MAAM,oBAAoB,CAAC;AAC/E,OAAO,KAAK,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAQjE;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,cAAc,EAAE,sBAAsB,EACtC,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,SAAS,EAAE,EACvB,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,gBAAgB,EACxB,gBAAgB,CAAC,EAAE;IACjB,iBAAiB,EAAE,mBAAmB,CAAC;IACvC,SAAS,EAAE,4BAA4B,CAAC;CACzC,GACA,OAAO,CAAC,IAAI,CAAC,CAsSf"}
@@ -1,4 +1,8 @@
1
+ import { ExpectationSeverity } from "@sudobility/testomniac_types";
1
2
  import { extractActionableItems } from "../extractors";
3
+ import { extractForms } from "../extractors/form-extractor";
4
+ import { captureControlStates } from "../browser/control-snapshot";
5
+ import { captureUiSnapshot } from "../browser/ui-snapshot";
2
6
  import { detectScaffoldRegions } from "../scanner/component-detector";
3
7
  import { detectPatternsWithInstances } from "../scanner/pattern-detector";
4
8
  /**
@@ -35,6 +39,7 @@ export async function executeTestElement(adapter, testElementRun, testRun, exper
35
39
  const steps = parseStoredSteps(testElement.stepsJson);
36
40
  const dependencyChain = buildDependencyChain(testElement, testElementById);
37
41
  const setupCases = dependencyChain.slice(0, -1);
42
+ const journeySteps = dependencyChain.flatMap(item => parseStoredSteps(item.stepsJson));
38
43
  // Record beginning page state
39
44
  const _beginningUrl = await adapter.getUrl();
40
45
  const beginningPageStateId = testElement.startingPageStateId ?? 0;
@@ -55,20 +60,35 @@ export async function executeTestElement(adapter, testElementRun, testRun, exper
55
60
  await executeAction(adapter, step.action, testRun);
56
61
  }
57
62
  }
63
+ const initialHtml = await adapter.content();
64
+ const initialUrl = await adapter.getUrl();
65
+ const initialUiSnapshot = await captureUiSnapshot(adapter);
66
+ const initialControlStates = await captureControlStates(adapter);
58
67
  // Execute test actions
59
68
  for (const step of steps) {
60
- await executeAction(adapter, step.action, testRun);
69
+ try {
70
+ await executeAction(adapter, step.action, testRun);
71
+ }
72
+ catch (error) {
73
+ if (!step.continueOnFailure) {
74
+ throw error;
75
+ }
76
+ }
61
77
  }
62
78
  // Decompose the page using local detectors
63
79
  const html = await adapter.content();
64
80
  const scaffolds = await detectScaffoldRegions(adapter);
65
81
  const patterns = await detectPatternsWithInstances(adapter);
66
82
  const items = await extractActionableItems(adapter);
83
+ const forms = await extractForms(adapter);
84
+ const finalUiSnapshot = await captureUiSnapshot(adapter);
85
+ const finalControlStates = await captureControlStates(adapter);
67
86
  const scaffoldSelectorByItemSelector = await mapItemsToScaffolds(adapter, scaffolds, items);
68
87
  // Parse global expectations
69
88
  const globalExpectations = testElement.globalExpectationsJson ?? [];
89
+ const stepExpectations = steps.flatMap(step => step.expectations ?? []);
70
90
  // If discovery mode: generate baseline expectations
71
- let expectations = [...globalExpectations];
91
+ let expectations = [...stepExpectations, ...globalExpectations];
72
92
  if (analyzer && testElement.stepsJson) {
73
93
  const parsedTestElement = {
74
94
  title: testElement.title,
@@ -87,28 +107,37 @@ export async function executeTestElement(adapter, testElementRun, testRun, exper
87
107
  // Build expertise context
88
108
  const expertiseContext = {
89
109
  html,
110
+ initialHtml,
90
111
  scaffolds,
91
112
  patterns,
92
113
  consoleLogs,
93
114
  networkLogs,
94
115
  expectations: expectations,
116
+ initialUrl,
117
+ currentUrl: await adapter.getUrl(),
118
+ startingPath: testElement.startingPath ?? undefined,
119
+ initialUiSnapshot,
120
+ finalUiSnapshot,
121
+ initialControlStates,
122
+ finalControlStates,
95
123
  };
96
124
  // Evaluate all expertises
97
125
  const allOutcomes = [];
98
126
  for (const expertise of expertises) {
99
127
  const outcomes = expertise.evaluate(expertiseContext);
100
128
  allOutcomes.push(...outcomes);
101
- // Create findings for warnings and errors
129
+ // Create findings for unmet expectations based on configured severity.
102
130
  for (const outcome of outcomes) {
103
- if (outcome.result === "warning" || outcome.result === "error") {
131
+ const findingType = getFindingTypeForOutcome(outcome);
132
+ if (findingType) {
104
133
  await api.createTestRunFinding({
105
134
  testElementRunId: testElementRun.id,
106
- type: outcome.result === "error" ? "error" : "warning",
135
+ type: findingType,
107
136
  title: `[${expertise.name}] ${outcome.expected}`,
108
137
  description: outcome.observed,
109
138
  });
110
139
  events.onFindingCreated({
111
- type: outcome.result,
140
+ type: findingType,
112
141
  title: `[${expertise.name}] ${outcome.expected}`,
113
142
  });
114
143
  }
@@ -117,10 +146,10 @@ export async function executeTestElement(adapter, testElementRun, testRun, exper
117
146
  // Aggregate outcomes
118
147
  const expectedOutcome = allOutcomes.map(o => o.expected).join("\n");
119
148
  const observedOutcome = allOutcomes
120
- .map(o => `[${o.result}] ${o.observed}`)
149
+ .map(o => `[${getFindingTypeForOutcome(o) ?? o.result}/${o.severity ?? "unknown"}] ${o.observed}`)
121
150
  .join("\n");
122
- const hasErrors = allOutcomes.some(o => o.result === "error");
123
- const hasWarnings = allOutcomes.some(o => o.result === "warning");
151
+ const hasErrors = allOutcomes.some(isMustPassFailure);
152
+ const hasWarnings = allOutcomes.some(isNonBlockingFailure);
124
153
  const status = hasErrors
125
154
  ? "failed"
126
155
  : hasWarnings
@@ -171,6 +200,8 @@ export async function executeTestElement(adapter, testElementRun, testRun, exper
171
200
  scaffolds,
172
201
  scaffoldSelectorByItemSelector,
173
202
  actionableItems: items,
203
+ forms,
204
+ journeySteps: journeySteps,
174
205
  navigationSurface: discoveryContext.navigationSurface,
175
206
  bundleRun: discoveryContext.bundleRun,
176
207
  api,
@@ -267,6 +298,19 @@ function buildDependencyChain(testElement, testElementById) {
267
298
  }
268
299
  return chain;
269
300
  }
301
+ function getFindingTypeForOutcome(outcome) {
302
+ if (outcome.result === "pass") {
303
+ return null;
304
+ }
305
+ const severity = outcome.severity ?? ExpectationSeverity.MustPass;
306
+ return severity === ExpectationSeverity.MustPass ? "error" : "warning";
307
+ }
308
+ function isMustPassFailure(outcome) {
309
+ return getFindingTypeForOutcome(outcome) === "error";
310
+ }
311
+ function isNonBlockingFailure(outcome) {
312
+ return getFindingTypeForOutcome(outcome) === "warning";
313
+ }
270
314
  async function executeAction(adapter, action, testRun) {
271
315
  const baseUrl = testRun.scanUrl
272
316
  ? new URL(testRun.scanUrl).origin
@@ -280,6 +324,15 @@ async function executeAction(adapter, action, testRun) {
280
324
  await adapter.goto(url, { waitUntil: "networkidle0" });
281
325
  break;
282
326
  }
327
+ case "reload":
328
+ await adapter.goto(await adapter.getUrl(), { waitUntil: "networkidle0" });
329
+ break;
330
+ case "goBack":
331
+ await adapter.pressKey("Alt+Left");
332
+ break;
333
+ case "goForward":
334
+ await adapter.pressKey("Alt+Right");
335
+ break;
283
336
  case "waitForLoadState":
284
337
  try {
285
338
  await adapter.waitForNavigation({
@@ -303,7 +356,12 @@ async function executeAction(adapter, action, testRun) {
303
356
  if (action.path && action.value != null)
304
357
  await adapter.type(action.path, action.value);
305
358
  break;
359
+ case "type":
360
+ if (action.path && action.value != null)
361
+ await adapter.type(action.path, action.value);
362
+ break;
306
363
  case "select":
364
+ case "selectOption":
307
365
  if (action.path && action.value != null)
308
366
  await adapter.select(action.path, action.value);
309
367
  break;
@@ -317,6 +375,10 @@ async function executeAction(adapter, action, testRun) {
317
375
  if (action.path)
318
376
  await adapter.hover(action.path);
319
377
  break;
378
+ case "focus":
379
+ if (action.path)
380
+ await adapter.click(action.path);
381
+ break;
320
382
  case "press":
321
383
  if (action.value)
322
384
  await adapter.pressKey(action.value);
@@ -324,6 +386,9 @@ async function executeAction(adapter, action, testRun) {
324
386
  case "screenshot":
325
387
  await adapter.screenshot({ type: "png" });
326
388
  break;
389
+ case "waitForTimeout":
390
+ await new Promise(resolve => setTimeout(resolve, Number.parseInt(action.value ?? "500", 10) || 500));
391
+ break;
327
392
  default:
328
393
  break;
329
394
  }