@redrockswebdevelopment/formstack-form-testing 0.1.39 → 0.1.41
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/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +129 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ export const liveFormFieldTypeCoverage = {
|
|
|
60
60
|
domInteraction: false,
|
|
61
61
|
domAssertion: true,
|
|
62
62
|
reason: "Description fields do not collect user values; test rendered content, visibility, and styling.",
|
|
63
|
-
recommendedHelpers: ["
|
|
63
|
+
recommendedHelpers: ["readLiveFormStructuralState", "waitForLiveFormStructuralState", "liveFormScenarioSteps.expectStructuralField"],
|
|
64
64
|
},
|
|
65
65
|
embed: {
|
|
66
66
|
kind: "embed",
|
|
@@ -69,7 +69,7 @@ export const liveFormFieldTypeCoverage = {
|
|
|
69
69
|
domInteraction: false,
|
|
70
70
|
domAssertion: true,
|
|
71
71
|
reason: "Embed fields generally provide scripts/styles/markup; test side effects and diagnostics, not value writes.",
|
|
72
|
-
recommendedHelpers: ["
|
|
72
|
+
recommendedHelpers: ["readLiveFormStructuralState", "createFormBrowserMonitor", "waitForConsoleMessage", "waitForWindowFlag"],
|
|
73
73
|
},
|
|
74
74
|
email: {
|
|
75
75
|
kind: "email",
|
|
@@ -123,7 +123,7 @@ export const liveFormFieldTypeCoverage = {
|
|
|
123
123
|
domInteraction: true,
|
|
124
124
|
domAssertion: true,
|
|
125
125
|
reason: "Page breaks do not collect values; test navigation, visible page state, and progress UI.",
|
|
126
|
-
recommendedHelpers: ["clickNext", "clickPrevious", "waitForLiveFormVisibility"],
|
|
126
|
+
recommendedHelpers: ["readLiveFormStructuralState", "clickNext", "clickPrevious", "waitForLiveFormVisibility"],
|
|
127
127
|
},
|
|
128
128
|
payment: {
|
|
129
129
|
kind: "payment",
|
|
@@ -178,7 +178,7 @@ export const liveFormFieldTypeCoverage = {
|
|
|
178
178
|
domInteraction: false,
|
|
179
179
|
domAssertion: true,
|
|
180
180
|
reason: "Sections group content and logic visibility; test rendered content and conditional visibility.",
|
|
181
|
-
recommendedHelpers: ["
|
|
181
|
+
recommendedHelpers: ["readLiveFormStructuralState", "waitForLiveFormStructuralState", "liveFormScenarioSteps.expectStructuralField"],
|
|
182
182
|
},
|
|
183
183
|
select: {
|
|
184
184
|
kind: "select",
|
|
@@ -1853,6 +1853,112 @@ export async function waitForLiveFormDomFieldState(page, options) {
|
|
|
1853
1853
|
}
|
|
1854
1854
|
return latest;
|
|
1855
1855
|
}
|
|
1856
|
+
export async function readLiveFormStructuralState(page, options) {
|
|
1857
|
+
if (!hasEvaluate(page)) {
|
|
1858
|
+
throw new Error("readLiveFormStructuralState requires a Playwright page with evaluate().");
|
|
1859
|
+
}
|
|
1860
|
+
return page.evaluate((stateOptions) => {
|
|
1861
|
+
const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
|
|
1862
|
+
const controlSnapshot = (control) => {
|
|
1863
|
+
const input = control;
|
|
1864
|
+
return {
|
|
1865
|
+
tagName: control.tagName.toLowerCase(),
|
|
1866
|
+
type: control.getAttribute("type"),
|
|
1867
|
+
id: control.getAttribute("id"),
|
|
1868
|
+
name: control.getAttribute("name"),
|
|
1869
|
+
value: control.getAttribute("value"),
|
|
1870
|
+
currentValue: "value" in input ? input.value : null,
|
|
1871
|
+
checked: control instanceof HTMLInputElement && ["checkbox", "radio"].includes(input.type) ? control.checked : null,
|
|
1872
|
+
};
|
|
1873
|
+
};
|
|
1874
|
+
const inferKind = (element) => {
|
|
1875
|
+
if (!element)
|
|
1876
|
+
return "unknown";
|
|
1877
|
+
const internalLabel = element.getAttribute("data-internal-label")?.toLowerCase() ?? "";
|
|
1878
|
+
const id = element.getAttribute("id")?.toLowerCase() ?? "";
|
|
1879
|
+
const classes = Array.from(element.classList).join(" ").toLowerCase();
|
|
1880
|
+
const dataTestId = element.getAttribute("data-testid")?.toLowerCase() ?? "";
|
|
1881
|
+
const text = normalizeText(element.textContent).toLowerCase();
|
|
1882
|
+
if (internalLabel.includes("code_embed") || text.startsWith(":root{") || text.includes("<script"))
|
|
1883
|
+
return "embed";
|
|
1884
|
+
if (classes.includes("pagebreak") || classes.includes("page-break") || dataTestId.includes("pagebreak") || id.includes("pagebreak"))
|
|
1885
|
+
return "pagebreak";
|
|
1886
|
+
if (classes.includes("description") ||
|
|
1887
|
+
classes.includes("richtext") ||
|
|
1888
|
+
internalLabel.includes("description") ||
|
|
1889
|
+
internalLabel.includes("richtext") ||
|
|
1890
|
+
dataTestId.includes("description") ||
|
|
1891
|
+
dataTestId.includes("richtext"))
|
|
1892
|
+
return "description";
|
|
1893
|
+
if (classes.includes("section") || id.includes("section") || element.querySelector(".fsSectionHeader, [class*='SectionHeader']"))
|
|
1894
|
+
return "section";
|
|
1895
|
+
return "unknown";
|
|
1896
|
+
};
|
|
1897
|
+
const isVisible = (element) => {
|
|
1898
|
+
if (!element || !(element instanceof HTMLElement))
|
|
1899
|
+
return false;
|
|
1900
|
+
const style = window.getComputedStyle(element);
|
|
1901
|
+
return style.display !== "none" &&
|
|
1902
|
+
style.visibility !== "hidden" &&
|
|
1903
|
+
style.opacity !== "0" &&
|
|
1904
|
+
element.getClientRects().length > 0;
|
|
1905
|
+
};
|
|
1906
|
+
const root = stateOptions.selector != null
|
|
1907
|
+
? document.querySelector(stateOptions.selector)
|
|
1908
|
+
: stateOptions.internalLabel != null
|
|
1909
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
1910
|
+
: stateOptions.fieldId == null
|
|
1911
|
+
? null
|
|
1912
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
1913
|
+
const controls = root
|
|
1914
|
+
? Array.from(root.querySelectorAll("input, textarea, select")).map(controlSnapshot)
|
|
1915
|
+
: [];
|
|
1916
|
+
const text = normalizeText(root?.textContent);
|
|
1917
|
+
const inferredKind = inferKind(root);
|
|
1918
|
+
const expectedText = stateOptions.expectedText;
|
|
1919
|
+
const textMatched = expectedText === undefined
|
|
1920
|
+
? true
|
|
1921
|
+
: typeof expectedText === "string"
|
|
1922
|
+
? text.includes(expectedText)
|
|
1923
|
+
: expectedText.test(text);
|
|
1924
|
+
const expectedVisible = stateOptions.expectedVisible;
|
|
1925
|
+
const visible = isVisible(root);
|
|
1926
|
+
const visibilityMatched = expectedVisible === undefined || visible === expectedVisible;
|
|
1927
|
+
const kindMatched = stateOptions.expectedKind === undefined || inferredKind === stateOptions.expectedKind;
|
|
1928
|
+
const controlsMatched = stateOptions.allowControls === true ||
|
|
1929
|
+
(stateOptions.allowControls !== false && inferredKind === "section") ||
|
|
1930
|
+
controls.length === 0;
|
|
1931
|
+
return {
|
|
1932
|
+
matched: Boolean(root) && kindMatched && textMatched && visibilityMatched && controlsMatched,
|
|
1933
|
+
elementMatched: Boolean(root),
|
|
1934
|
+
id: root?.getAttribute("id") ?? null,
|
|
1935
|
+
internalLabel: root?.getAttribute("data-internal-label") ?? null,
|
|
1936
|
+
inferredKind,
|
|
1937
|
+
visible,
|
|
1938
|
+
text,
|
|
1939
|
+
controlCount: controls.length,
|
|
1940
|
+
controls,
|
|
1941
|
+
...(stateOptions.expectedKind === undefined ? {} : { expectedKind: stateOptions.expectedKind }),
|
|
1942
|
+
...(expectedText === undefined ? {} : { expectedText: String(expectedText) }),
|
|
1943
|
+
...(expectedVisible === undefined ? {} : { expectedVisible }),
|
|
1944
|
+
kindMatched,
|
|
1945
|
+
textMatched,
|
|
1946
|
+
visibilityMatched,
|
|
1947
|
+
controlsMatched,
|
|
1948
|
+
};
|
|
1949
|
+
}, options);
|
|
1950
|
+
}
|
|
1951
|
+
export async function waitForLiveFormStructuralState(page, options) {
|
|
1952
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
1953
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
1954
|
+
const startedAt = Date.now();
|
|
1955
|
+
let latest = await readLiveFormStructuralState(page, options);
|
|
1956
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
1957
|
+
await delay(intervalMs);
|
|
1958
|
+
latest = await readLiveFormStructuralState(page, options);
|
|
1959
|
+
}
|
|
1960
|
+
return latest;
|
|
1961
|
+
}
|
|
1856
1962
|
export async function readLiveFormProductState(page, options) {
|
|
1857
1963
|
if (!hasEvaluate(page)) {
|
|
1858
1964
|
throw new Error("readLiveFormProductState requires a Playwright page with evaluate().");
|
|
@@ -3339,10 +3445,18 @@ export async function discoverLiveFormFieldShapes(page, options = {}) {
|
|
|
3339
3445
|
if (classes.includes("pagebreak") || classes.includes("page-break") || dataTestId.includes("pagebreak") || id.includes("pagebreak")) {
|
|
3340
3446
|
return "pagebreak";
|
|
3341
3447
|
}
|
|
3342
|
-
if (classes.includes("description") ||
|
|
3448
|
+
if (classes.includes("description") ||
|
|
3449
|
+
classes.includes("richtext") ||
|
|
3450
|
+
internalLabel.includes("description") ||
|
|
3451
|
+
internalLabel.includes("richtext") ||
|
|
3452
|
+
dataTestId.includes("description") ||
|
|
3453
|
+
dataTestId.includes("richtext")) {
|
|
3343
3454
|
return "description";
|
|
3344
3455
|
}
|
|
3345
|
-
|
|
3456
|
+
if (classes.includes("section") || id.includes("section") || element.querySelector(".fsSectionHeader, [class*='SectionHeader']")) {
|
|
3457
|
+
return "section";
|
|
3458
|
+
}
|
|
3459
|
+
return "description";
|
|
3346
3460
|
}
|
|
3347
3461
|
if (classes.includes("matrix") || internalLabel.includes("matrix") || element.querySelector("[id^='matrix-row-'], table"))
|
|
3348
3462
|
return "matrix";
|
|
@@ -6365,6 +6479,15 @@ export const liveFormScenarioSteps = {
|
|
|
6365
6479
|
}
|
|
6366
6480
|
},
|
|
6367
6481
|
}),
|
|
6482
|
+
expectStructuralField: (name, options) => ({
|
|
6483
|
+
name,
|
|
6484
|
+
run: async (harness) => {
|
|
6485
|
+
const state = await waitForLiveFormStructuralState(harness.page, options);
|
|
6486
|
+
if (!state.matched) {
|
|
6487
|
+
throw new Error(`Expected structural form element ${options.internalLabel ?? options.fieldId ?? options.selector ?? "unknown"} to match.`);
|
|
6488
|
+
}
|
|
6489
|
+
},
|
|
6490
|
+
}),
|
|
6368
6491
|
expectValidationErrors: (name, options = {}) => ({
|
|
6369
6492
|
name,
|
|
6370
6493
|
run: async (harness) => {
|