@redrockswebdevelopment/formstack-form-testing 0.1.35 → 0.1.36
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 +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +198 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -104,8 +104,8 @@ export const liveFormFieldTypeCoverage = {
|
|
|
104
104
|
apiWrite: true,
|
|
105
105
|
domInteraction: true,
|
|
106
106
|
domAssertion: true,
|
|
107
|
-
reason: "Name compound fields support direct API values and
|
|
108
|
-
recommendedHelpers: ["buildLiveFormNameValue", "
|
|
107
|
+
reason: "Name compound fields support direct API values and should be asserted by prefix, first, middle, last, and suffix subfields.",
|
|
108
|
+
recommendedHelpers: ["buildLiveFormNameValue", "setLiveFormNameValue", "readLiveFormNameState", "fillName"],
|
|
109
109
|
},
|
|
110
110
|
number: {
|
|
111
111
|
kind: "number",
|
|
@@ -895,6 +895,15 @@ export function buildLiveFormNameValue(value, current = {}) {
|
|
|
895
895
|
...value,
|
|
896
896
|
};
|
|
897
897
|
}
|
|
898
|
+
export function normalizeLiveFormNameValue(value, current = {}) {
|
|
899
|
+
return buildLiveFormNameValue({
|
|
900
|
+
...(value.prefix === undefined ? {} : { prefix: String(value.prefix) }),
|
|
901
|
+
...(value.first === undefined ? {} : { first: String(value.first) }),
|
|
902
|
+
...(value.middle === undefined ? {} : { middle: String(value.middle) }),
|
|
903
|
+
...(value.last === undefined ? {} : { last: String(value.last) }),
|
|
904
|
+
...(value.suffix === undefined ? {} : { suffix: String(value.suffix) }),
|
|
905
|
+
}, current);
|
|
906
|
+
}
|
|
898
907
|
export function buildLiveFormAddressValue(value, current = {}) {
|
|
899
908
|
return {
|
|
900
909
|
...(current && typeof current === "object" ? current : {}),
|
|
@@ -2055,6 +2064,102 @@ export async function waitForLiveFormAddressState(page, options) {
|
|
|
2055
2064
|
}
|
|
2056
2065
|
return latest;
|
|
2057
2066
|
}
|
|
2067
|
+
export async function readLiveFormNameState(page, options) {
|
|
2068
|
+
if (!hasEvaluate(page)) {
|
|
2069
|
+
throw new Error("readLiveFormNameState requires a Playwright page with evaluate().");
|
|
2070
|
+
}
|
|
2071
|
+
return page.evaluate((stateOptions) => {
|
|
2072
|
+
const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
|
|
2073
|
+
const inferPart = (control) => {
|
|
2074
|
+
const label = control.id ? document.querySelector(`label[for="${CSS.escape(control.id)}"]`) : null;
|
|
2075
|
+
const haystack = [
|
|
2076
|
+
control.name,
|
|
2077
|
+
control.id,
|
|
2078
|
+
control.className,
|
|
2079
|
+
control.getAttribute("placeholder"),
|
|
2080
|
+
control.getAttribute("aria-label"),
|
|
2081
|
+
label?.textContent,
|
|
2082
|
+
].map(normalizeText).join(" ").toLowerCase();
|
|
2083
|
+
if (/\b(prefix|title|\[prefix\]|-prefix$|_prefix$)/i.test(haystack))
|
|
2084
|
+
return "prefix";
|
|
2085
|
+
if (/\b(first name|given name|first|\[first\]|-first$|_first$)/i.test(haystack))
|
|
2086
|
+
return "first";
|
|
2087
|
+
if (/\b(middle name|middle initial|middle|\[middle\]|-middle$|_middle$)/i.test(haystack))
|
|
2088
|
+
return "middle";
|
|
2089
|
+
if (/\b(last name|family name|surname|last|\[last\]|-last$|_last$)/i.test(haystack))
|
|
2090
|
+
return "last";
|
|
2091
|
+
if (/\b(suffix|\[suffix\]|-suffix$|_suffix$)/i.test(haystack))
|
|
2092
|
+
return "suffix";
|
|
2093
|
+
return null;
|
|
2094
|
+
};
|
|
2095
|
+
const root = stateOptions.selector != null
|
|
2096
|
+
? document.querySelector(stateOptions.selector)
|
|
2097
|
+
: stateOptions.internalLabel != null
|
|
2098
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
2099
|
+
: stateOptions.fieldId == null
|
|
2100
|
+
? null
|
|
2101
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
2102
|
+
const nativeControls = root
|
|
2103
|
+
? Array.from(root.querySelectorAll("input, textarea, select"))
|
|
2104
|
+
: [];
|
|
2105
|
+
const controls = nativeControls.map((control) => ({
|
|
2106
|
+
tagName: control.tagName.toLowerCase(),
|
|
2107
|
+
type: control.getAttribute("type"),
|
|
2108
|
+
id: control.getAttribute("id"),
|
|
2109
|
+
name: control.getAttribute("name"),
|
|
2110
|
+
value: control.getAttribute("value"),
|
|
2111
|
+
currentValue: "value" in control ? control.value : "",
|
|
2112
|
+
checked: control instanceof HTMLInputElement && ["checkbox", "radio"].includes(control.type) ? control.checked : null,
|
|
2113
|
+
part: inferPart(control),
|
|
2114
|
+
text: control instanceof HTMLSelectElement
|
|
2115
|
+
? normalizeText(control.selectedOptions.item(0)?.textContent)
|
|
2116
|
+
: normalizeText(control.textContent),
|
|
2117
|
+
disabled: "disabled" in control ? Boolean(control.disabled) : null,
|
|
2118
|
+
}));
|
|
2119
|
+
const selectedParts = {};
|
|
2120
|
+
for (const control of controls) {
|
|
2121
|
+
if (control.part && control.disabled !== true && typeof control.currentValue === "string" && control.currentValue.length > 0) {
|
|
2122
|
+
selectedParts[control.part] = control.currentValue;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
const expected = stateOptions.expectedValue ?? {};
|
|
2126
|
+
const expectedEntries = Object.entries(expected).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
|
|
2127
|
+
const availableParts = new Set(controls.filter((control) => control.part && control.disabled !== true).map((control) => control.part));
|
|
2128
|
+
const renderedExpectedEntries = expectedEntries.filter(([part]) => availableParts.has(part));
|
|
2129
|
+
const unavailableParts = expectedEntries
|
|
2130
|
+
.filter(([part]) => !availableParts.has(part))
|
|
2131
|
+
.map(([part]) => part);
|
|
2132
|
+
const missingParts = renderedExpectedEntries
|
|
2133
|
+
.filter(([part]) => selectedParts[part] === undefined || selectedParts[part] === "")
|
|
2134
|
+
.map(([part]) => part);
|
|
2135
|
+
const mismatchedParts = renderedExpectedEntries
|
|
2136
|
+
.filter(([part, value]) => selectedParts[part] !== undefined && selectedParts[part] !== "" && selectedParts[part] !== value)
|
|
2137
|
+
.map(([part]) => part);
|
|
2138
|
+
const renderedValueAvailable = Object.keys(selectedParts).length > 0;
|
|
2139
|
+
return {
|
|
2140
|
+
fieldMatched: Boolean(root),
|
|
2141
|
+
selectedParts,
|
|
2142
|
+
...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
|
|
2143
|
+
renderedValueAvailable,
|
|
2144
|
+
unavailableParts,
|
|
2145
|
+
missingParts,
|
|
2146
|
+
mismatchedParts,
|
|
2147
|
+
matched: Boolean(root) && missingParts.length === 0 && mismatchedParts.length === 0,
|
|
2148
|
+
controls,
|
|
2149
|
+
};
|
|
2150
|
+
}, options);
|
|
2151
|
+
}
|
|
2152
|
+
export async function waitForLiveFormNameState(page, options) {
|
|
2153
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
2154
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
2155
|
+
const startedAt = Date.now();
|
|
2156
|
+
let latest = await readLiveFormNameState(page, options);
|
|
2157
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
2158
|
+
await delay(intervalMs);
|
|
2159
|
+
latest = await readLiveFormNameState(page, options);
|
|
2160
|
+
}
|
|
2161
|
+
return latest;
|
|
2162
|
+
}
|
|
2058
2163
|
export async function readLiveFormCheckboxState(page, options) {
|
|
2059
2164
|
if (!hasEvaluate(page)) {
|
|
2060
2165
|
throw new Error("readLiveFormCheckboxState requires a Playwright page with evaluate().");
|
|
@@ -4391,6 +4496,47 @@ export async function setLiveFormAddressValue(page, options) {
|
|
|
4391
4496
|
(visibility?.matched ?? true),
|
|
4392
4497
|
};
|
|
4393
4498
|
}
|
|
4499
|
+
export async function setLiveFormNameValue(page, options) {
|
|
4500
|
+
const payload = normalizeLiveFormNameValue(options.value, options.current);
|
|
4501
|
+
const transaction = await performLiveFormFieldTransaction(page, {
|
|
4502
|
+
write: {
|
|
4503
|
+
...(options.write ?? {}),
|
|
4504
|
+
target: options.target,
|
|
4505
|
+
value: payload,
|
|
4506
|
+
notify: options.write?.notify ?? true,
|
|
4507
|
+
},
|
|
4508
|
+
fieldKind: "name",
|
|
4509
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4510
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4511
|
+
afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
|
|
4512
|
+
...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
|
|
4513
|
+
});
|
|
4514
|
+
const dom = await waitForLiveFormNameState(page, {
|
|
4515
|
+
...options.target,
|
|
4516
|
+
expectedValue: payload,
|
|
4517
|
+
...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
|
|
4518
|
+
});
|
|
4519
|
+
const visibilityTargets = [
|
|
4520
|
+
...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
|
|
4521
|
+
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
4522
|
+
];
|
|
4523
|
+
const visibility = visibilityTargets.length > 0
|
|
4524
|
+
? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
|
|
4525
|
+
: undefined;
|
|
4526
|
+
return {
|
|
4527
|
+
payload,
|
|
4528
|
+
transaction,
|
|
4529
|
+
dom,
|
|
4530
|
+
...(visibility ? { visibility } : {}),
|
|
4531
|
+
ok: transaction.write.valueMatched &&
|
|
4532
|
+
(transaction.event?.matched ?? true) &&
|
|
4533
|
+
(transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
|
|
4534
|
+
(transaction.quiet?.quiet ?? true) &&
|
|
4535
|
+
((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
|
|
4536
|
+
dom.matched &&
|
|
4537
|
+
(visibility?.matched ?? true),
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4394
4540
|
export async function setLiveFormRatingValue(page, options) {
|
|
4395
4541
|
const payload = normalizeLiveFormRatingValue(options.value);
|
|
4396
4542
|
const value = payload.value;
|
|
@@ -4521,7 +4667,18 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4521
4667
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4522
4668
|
})
|
|
4523
4669
|
: undefined;
|
|
4524
|
-
const
|
|
4670
|
+
const nameTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && options.fieldKind === "name"
|
|
4671
|
+
? await setLiveFormNameValue(page, {
|
|
4672
|
+
target: options.target,
|
|
4673
|
+
value: options.value,
|
|
4674
|
+
...(options.write === undefined ? {} : { write: options.write }),
|
|
4675
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4676
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4677
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4678
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4679
|
+
})
|
|
4680
|
+
: undefined;
|
|
4681
|
+
const addressTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && options.fieldKind === "address"
|
|
4525
4682
|
? await setLiveFormAddressValue(page, {
|
|
4526
4683
|
target: options.target,
|
|
4527
4684
|
value: options.value,
|
|
@@ -4532,7 +4689,7 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4532
4689
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4533
4690
|
})
|
|
4534
4691
|
: undefined;
|
|
4535
|
-
const productTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && options.fieldKind === "product"
|
|
4692
|
+
const productTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && !addressTransaction && options.fieldKind === "product"
|
|
4536
4693
|
? await setLiveFormProductValue(page, {
|
|
4537
4694
|
target: options.target,
|
|
4538
4695
|
value: options.value,
|
|
@@ -4543,7 +4700,7 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4543
4700
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4544
4701
|
})
|
|
4545
4702
|
: undefined;
|
|
4546
|
-
const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && !productTransaction && options.fieldKind === "rating"
|
|
4703
|
+
const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && !addressTransaction && !productTransaction && options.fieldKind === "rating"
|
|
4547
4704
|
? await setLiveFormRatingValue(page, {
|
|
4548
4705
|
target: options.target,
|
|
4549
4706
|
value: options.value,
|
|
@@ -4573,27 +4730,29 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4573
4730
|
? selectTransaction.transaction
|
|
4574
4731
|
: matrixTransaction
|
|
4575
4732
|
? matrixTransaction.transaction
|
|
4576
|
-
:
|
|
4577
|
-
?
|
|
4578
|
-
:
|
|
4579
|
-
?
|
|
4580
|
-
:
|
|
4581
|
-
?
|
|
4582
|
-
:
|
|
4583
|
-
?
|
|
4584
|
-
:
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4733
|
+
: nameTransaction
|
|
4734
|
+
? nameTransaction.transaction
|
|
4735
|
+
: addressTransaction
|
|
4736
|
+
? addressTransaction.transaction
|
|
4737
|
+
: productTransaction
|
|
4738
|
+
? productTransaction.transaction
|
|
4739
|
+
: ratingTransaction
|
|
4740
|
+
? ratingTransaction.transaction
|
|
4741
|
+
: dateTimeTransaction
|
|
4742
|
+
? dateTimeTransaction.transaction
|
|
4743
|
+
: await performLiveFormFieldTransaction(page, {
|
|
4744
|
+
write: {
|
|
4745
|
+
...(options.write ?? {}),
|
|
4746
|
+
target: options.target,
|
|
4747
|
+
value: options.value,
|
|
4748
|
+
notify: options.write?.notify ?? true,
|
|
4749
|
+
},
|
|
4750
|
+
...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
|
|
4751
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4752
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4753
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4754
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4755
|
+
});
|
|
4597
4756
|
const visibilityTargets = [
|
|
4598
4757
|
...(options.expectVisible ?? []),
|
|
4599
4758
|
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
@@ -6297,7 +6456,19 @@ export const liveFormFieldRecipes = {
|
|
|
6297
6456
|
}
|
|
6298
6457
|
},
|
|
6299
6458
|
}),
|
|
6300
|
-
setNameApi: (name, internalLabel, value, options) =>
|
|
6459
|
+
setNameApi: (name, internalLabel, value, options = {}) => ({
|
|
6460
|
+
name,
|
|
6461
|
+
run: async (harness) => {
|
|
6462
|
+
await setLiveFormNameValue(harness.page, {
|
|
6463
|
+
target: { internalLabel },
|
|
6464
|
+
value,
|
|
6465
|
+
write: { notify: options.notify ?? true },
|
|
6466
|
+
waitForEvent: options.waitForEvent ?? (options.notify ?? true),
|
|
6467
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
6468
|
+
afterQuiet: options.waitForQuiet ?? true,
|
|
6469
|
+
});
|
|
6470
|
+
},
|
|
6471
|
+
}),
|
|
6301
6472
|
setAddressApi: (name, internalLabel, value, options = {}) => ({
|
|
6302
6473
|
name,
|
|
6303
6474
|
run: async (harness) => {
|