@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.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: ["snapshotLiveFormDomFields", "waitForLiveFormVisibility"],
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: ["createFormBrowserMonitor", "waitForConsoleMessage", "waitForWindowFlag", "snapshotLiveFormDomFields"],
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: ["snapshotLiveFormDomFields", "waitForLiveFormVisibility"],
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) => {