@redrockswebdevelopment/formstack-form-testing 0.1.39 → 0.1.40
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 +112 -4
- 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,105 @@ 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") || internalLabel.includes("description") || dataTestId.includes("description"))
|
|
1887
|
+
return "description";
|
|
1888
|
+
if (classes.includes("section") || id.includes("section") || element.querySelector(".fsSectionHeader, [class*='SectionHeader']"))
|
|
1889
|
+
return "section";
|
|
1890
|
+
return "unknown";
|
|
1891
|
+
};
|
|
1892
|
+
const isVisible = (element) => {
|
|
1893
|
+
if (!element || !(element instanceof HTMLElement))
|
|
1894
|
+
return false;
|
|
1895
|
+
const style = window.getComputedStyle(element);
|
|
1896
|
+
return style.display !== "none" &&
|
|
1897
|
+
style.visibility !== "hidden" &&
|
|
1898
|
+
style.opacity !== "0" &&
|
|
1899
|
+
element.getClientRects().length > 0;
|
|
1900
|
+
};
|
|
1901
|
+
const root = stateOptions.selector != null
|
|
1902
|
+
? document.querySelector(stateOptions.selector)
|
|
1903
|
+
: stateOptions.internalLabel != null
|
|
1904
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
1905
|
+
: stateOptions.fieldId == null
|
|
1906
|
+
? null
|
|
1907
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
1908
|
+
const controls = root
|
|
1909
|
+
? Array.from(root.querySelectorAll("input, textarea, select")).map(controlSnapshot)
|
|
1910
|
+
: [];
|
|
1911
|
+
const text = normalizeText(root?.textContent);
|
|
1912
|
+
const inferredKind = inferKind(root);
|
|
1913
|
+
const expectedText = stateOptions.expectedText;
|
|
1914
|
+
const textMatched = expectedText === undefined
|
|
1915
|
+
? true
|
|
1916
|
+
: typeof expectedText === "string"
|
|
1917
|
+
? text.includes(expectedText)
|
|
1918
|
+
: expectedText.test(text);
|
|
1919
|
+
const expectedVisible = stateOptions.expectedVisible;
|
|
1920
|
+
const visible = isVisible(root);
|
|
1921
|
+
const visibilityMatched = expectedVisible === undefined || visible === expectedVisible;
|
|
1922
|
+
const kindMatched = stateOptions.expectedKind === undefined || inferredKind === stateOptions.expectedKind;
|
|
1923
|
+
const controlsMatched = stateOptions.allowControls === true || controls.length === 0;
|
|
1924
|
+
return {
|
|
1925
|
+
matched: Boolean(root) && kindMatched && textMatched && visibilityMatched && controlsMatched,
|
|
1926
|
+
elementMatched: Boolean(root),
|
|
1927
|
+
id: root?.getAttribute("id") ?? null,
|
|
1928
|
+
internalLabel: root?.getAttribute("data-internal-label") ?? null,
|
|
1929
|
+
inferredKind,
|
|
1930
|
+
visible,
|
|
1931
|
+
text,
|
|
1932
|
+
controlCount: controls.length,
|
|
1933
|
+
controls,
|
|
1934
|
+
...(stateOptions.expectedKind === undefined ? {} : { expectedKind: stateOptions.expectedKind }),
|
|
1935
|
+
...(expectedText === undefined ? {} : { expectedText: String(expectedText) }),
|
|
1936
|
+
...(expectedVisible === undefined ? {} : { expectedVisible }),
|
|
1937
|
+
kindMatched,
|
|
1938
|
+
textMatched,
|
|
1939
|
+
visibilityMatched,
|
|
1940
|
+
controlsMatched,
|
|
1941
|
+
};
|
|
1942
|
+
}, options);
|
|
1943
|
+
}
|
|
1944
|
+
export async function waitForLiveFormStructuralState(page, options) {
|
|
1945
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
1946
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
1947
|
+
const startedAt = Date.now();
|
|
1948
|
+
let latest = await readLiveFormStructuralState(page, options);
|
|
1949
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
1950
|
+
await delay(intervalMs);
|
|
1951
|
+
latest = await readLiveFormStructuralState(page, options);
|
|
1952
|
+
}
|
|
1953
|
+
return latest;
|
|
1954
|
+
}
|
|
1856
1955
|
export async function readLiveFormProductState(page, options) {
|
|
1857
1956
|
if (!hasEvaluate(page)) {
|
|
1858
1957
|
throw new Error("readLiveFormProductState requires a Playwright page with evaluate().");
|
|
@@ -6365,6 +6464,15 @@ export const liveFormScenarioSteps = {
|
|
|
6365
6464
|
}
|
|
6366
6465
|
},
|
|
6367
6466
|
}),
|
|
6467
|
+
expectStructuralField: (name, options) => ({
|
|
6468
|
+
name,
|
|
6469
|
+
run: async (harness) => {
|
|
6470
|
+
const state = await waitForLiveFormStructuralState(harness.page, options);
|
|
6471
|
+
if (!state.matched) {
|
|
6472
|
+
throw new Error(`Expected structural form element ${options.internalLabel ?? options.fieldId ?? options.selector ?? "unknown"} to match.`);
|
|
6473
|
+
}
|
|
6474
|
+
},
|
|
6475
|
+
}),
|
|
6368
6476
|
expectValidationErrors: (name, options = {}) => ({
|
|
6369
6477
|
name,
|
|
6370
6478
|
run: async (harness) => {
|