@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.
- package/dist/analyzer/page-analyzer.d.ts +78 -1
- package/dist/analyzer/page-analyzer.d.ts.map +1 -1
- package/dist/analyzer/page-analyzer.js +1600 -6
- package/dist/analyzer/page-analyzer.js.map +1 -1
- package/dist/browser/control-snapshot.d.ts +4 -0
- package/dist/browser/control-snapshot.d.ts.map +1 -0
- package/dist/browser/control-snapshot.js +181 -0
- package/dist/browser/control-snapshot.js.map +1 -0
- package/dist/browser/dom-snapshot.d.ts.map +1 -1
- package/dist/browser/dom-snapshot.js +8 -0
- package/dist/browser/dom-snapshot.js.map +1 -1
- package/dist/browser/ui-snapshot.d.ts +9 -0
- package/dist/browser/ui-snapshot.d.ts.map +1 -0
- package/dist/browser/ui-snapshot.js +53 -0
- package/dist/browser/ui-snapshot.js.map +1 -0
- package/dist/expertise/content-expertise.d.ts +1 -0
- package/dist/expertise/content-expertise.d.ts.map +1 -1
- package/dist/expertise/content-expertise.js +38 -0
- package/dist/expertise/content-expertise.js.map +1 -1
- package/dist/expertise/tester/commerce-checks.d.ts +14 -0
- package/dist/expertise/tester/commerce-checks.d.ts.map +1 -0
- package/dist/expertise/tester/commerce-checks.js +132 -0
- package/dist/expertise/tester/commerce-checks.js.map +1 -0
- package/dist/expertise/tester/control-state.d.ts +31 -0
- package/dist/expertise/tester/control-state.d.ts.map +1 -0
- package/dist/expertise/tester/control-state.js +46 -0
- package/dist/expertise/tester/control-state.js.map +1 -0
- package/dist/expertise/tester/core-checks.d.ts +5 -0
- package/dist/expertise/tester/core-checks.d.ts.map +1 -0
- package/dist/expertise/tester/core-checks.js +46 -0
- package/dist/expertise/tester/core-checks.js.map +1 -0
- package/dist/expertise/tester/dialog-feedback-checks.d.ts +13 -0
- package/dist/expertise/tester/dialog-feedback-checks.d.ts.map +1 -0
- package/dist/expertise/tester/dialog-feedback-checks.js +82 -0
- package/dist/expertise/tester/dialog-feedback-checks.js.map +1 -0
- package/dist/expertise/tester/form-checks.d.ts +8 -0
- package/dist/expertise/tester/form-checks.d.ts.map +1 -0
- package/dist/expertise/tester/form-checks.js +50 -0
- package/dist/expertise/tester/form-checks.js.map +1 -0
- package/dist/expertise/tester/keyboard-disclosure-checks.d.ts +7 -0
- package/dist/expertise/tester/keyboard-disclosure-checks.d.ts.map +1 -0
- package/dist/expertise/tester/keyboard-disclosure-checks.js +46 -0
- package/dist/expertise/tester/keyboard-disclosure-checks.js.map +1 -0
- package/dist/expertise/tester/navigation-checks.d.ts +8 -0
- package/dist/expertise/tester/navigation-checks.d.ts.map +1 -0
- package/dist/expertise/tester/navigation-checks.js +64 -0
- package/dist/expertise/tester/navigation-checks.js.map +1 -0
- package/dist/expertise/tester/network-intent-checks.d.ts +7 -0
- package/dist/expertise/tester/network-intent-checks.d.ts.map +1 -0
- package/dist/expertise/tester/network-intent-checks.js +56 -0
- package/dist/expertise/tester/network-intent-checks.js.map +1 -0
- package/dist/expertise/tester/page-behavior-checks.d.ts +17 -0
- package/dist/expertise/tester/page-behavior-checks.d.ts.map +1 -0
- package/dist/expertise/tester/page-behavior-checks.js +144 -0
- package/dist/expertise/tester/page-behavior-checks.js.map +1 -0
- package/dist/expertise/tester/persistence-checks.d.ts +12 -0
- package/dist/expertise/tester/persistence-checks.d.ts.map +1 -0
- package/dist/expertise/tester/persistence-checks.js +109 -0
- package/dist/expertise/tester/persistence-checks.js.map +1 -0
- package/dist/expertise/tester/search-checks.d.ts +10 -0
- package/dist/expertise/tester/search-checks.d.ts.map +1 -0
- package/dist/expertise/tester/search-checks.js +111 -0
- package/dist/expertise/tester/search-checks.js.map +1 -0
- package/dist/expertise/tester/selection-control-checks.d.ts +7 -0
- package/dist/expertise/tester/selection-control-checks.d.ts.map +1 -0
- package/dist/expertise/tester/selection-control-checks.js +128 -0
- package/dist/expertise/tester/selection-control-checks.js.map +1 -0
- package/dist/expertise/tester/text-input-checks.d.ts +8 -0
- package/dist/expertise/tester/text-input-checks.d.ts.map +1 -0
- package/dist/expertise/tester/text-input-checks.js +194 -0
- package/dist/expertise/tester/text-input-checks.js.map +1 -0
- package/dist/expertise/tester/validation-checks.d.ts +10 -0
- package/dist/expertise/tester/validation-checks.d.ts.map +1 -0
- package/dist/expertise/tester/validation-checks.js +59 -0
- package/dist/expertise/tester/validation-checks.js.map +1 -0
- package/dist/expertise/tester-expertise.d.ts +0 -3
- package/dist/expertise/tester-expertise.d.ts.map +1 -1
- package/dist/expertise/tester-expertise.js +75 -49
- package/dist/expertise/tester-expertise.js.map +1 -1
- package/dist/expertise/types.d.ts +12 -1
- package/dist/expertise/types.d.ts.map +1 -1
- package/dist/extractors/form-extractor.d.ts +19 -0
- package/dist/extractors/form-extractor.d.ts.map +1 -1
- package/dist/extractors/form-extractor.js +271 -42
- package/dist/extractors/form-extractor.js.map +1 -1
- package/dist/orchestrator/test-element-executor.d.ts.map +1 -1
- package/dist/orchestrator/test-element-executor.js +74 -9
- package/dist/orchestrator/test-element-executor.js.map +1 -1
- 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;
|
|
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,
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
|
129
|
+
// Create findings for unmet expectations based on configured severity.
|
|
102
130
|
for (const outcome of outcomes) {
|
|
103
|
-
|
|
131
|
+
const findingType = getFindingTypeForOutcome(outcome);
|
|
132
|
+
if (findingType) {
|
|
104
133
|
await api.createTestRunFinding({
|
|
105
134
|
testElementRunId: testElementRun.id,
|
|
106
|
-
type:
|
|
135
|
+
type: findingType,
|
|
107
136
|
title: `[${expertise.name}] ${outcome.expected}`,
|
|
108
137
|
description: outcome.observed,
|
|
109
138
|
});
|
|
110
139
|
events.onFindingCreated({
|
|
111
|
-
type:
|
|
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(
|
|
123
|
-
const hasWarnings = allOutcomes.some(
|
|
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
|
}
|