@redrockswebdevelopment/formstack-form-testing 0.1.34 → 0.1.35
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 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,8 +23,8 @@ export const liveFormFieldTypeCoverage = {
|
|
|
23
23
|
apiWrite: true,
|
|
24
24
|
domInteraction: true,
|
|
25
25
|
domAssertion: true,
|
|
26
|
-
reason: "Compound address values can be written through Live Form API, while
|
|
27
|
-
recommendedHelpers: ["buildLiveFormAddressValue", "
|
|
26
|
+
reason: "Compound address values can be written through Live Form API, while subfield UX should be asserted for address line, city, state, ZIP, and country controls.",
|
|
27
|
+
recommendedHelpers: ["buildLiveFormAddressValue", "setLiveFormAddressValue", "readLiveFormAddressState", "fillAddress"],
|
|
28
28
|
},
|
|
29
29
|
checkbox: {
|
|
30
30
|
kind: "checkbox",
|
|
@@ -901,6 +901,16 @@ export function buildLiveFormAddressValue(value, current = {}) {
|
|
|
901
901
|
...value,
|
|
902
902
|
};
|
|
903
903
|
}
|
|
904
|
+
export function normalizeLiveFormAddressValue(value, current = {}) {
|
|
905
|
+
return buildLiveFormAddressValue({
|
|
906
|
+
...(value.address === undefined ? {} : { address: String(value.address) }),
|
|
907
|
+
...(value.address2 === undefined ? {} : { address2: String(value.address2) }),
|
|
908
|
+
...(value.city === undefined ? {} : { city: String(value.city) }),
|
|
909
|
+
...(value.state === undefined ? {} : { state: String(value.state) }),
|
|
910
|
+
...(value.zip === undefined ? {} : { zip: String(value.zip) }),
|
|
911
|
+
...(value.country === undefined ? {} : { country: String(value.country) }),
|
|
912
|
+
}, current);
|
|
913
|
+
}
|
|
904
914
|
export function buildLiveFormChoiceValue(value, otherValue = "") {
|
|
905
915
|
return { value, otherValue };
|
|
906
916
|
}
|
|
@@ -1947,6 +1957,104 @@ export async function waitForLiveFormProductState(page, options) {
|
|
|
1947
1957
|
}
|
|
1948
1958
|
return latest;
|
|
1949
1959
|
}
|
|
1960
|
+
export async function readLiveFormAddressState(page, options) {
|
|
1961
|
+
if (!hasEvaluate(page)) {
|
|
1962
|
+
throw new Error("readLiveFormAddressState requires a Playwright page with evaluate().");
|
|
1963
|
+
}
|
|
1964
|
+
return page.evaluate((stateOptions) => {
|
|
1965
|
+
const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
|
|
1966
|
+
const inferPart = (control) => {
|
|
1967
|
+
const label = control.id ? document.querySelector(`label[for="${CSS.escape(control.id)}"]`) : null;
|
|
1968
|
+
const haystack = [
|
|
1969
|
+
control.name,
|
|
1970
|
+
control.id,
|
|
1971
|
+
control.className,
|
|
1972
|
+
control.getAttribute("placeholder"),
|
|
1973
|
+
control.getAttribute("aria-label"),
|
|
1974
|
+
label?.textContent,
|
|
1975
|
+
].map(normalizeText).join(" ").toLowerCase();
|
|
1976
|
+
if (/\b(address line 2|address 2|address2|line 2|street 2|\[address2\]|-address2$|_address2$)/i.test(haystack))
|
|
1977
|
+
return "address2";
|
|
1978
|
+
if (/\b(address line 1|address 1|address|street|line 1|\[address\]|-address$|_address$)/i.test(haystack))
|
|
1979
|
+
return "address";
|
|
1980
|
+
if (/\b(city|\[city\]|-city$|_city$)/i.test(haystack))
|
|
1981
|
+
return "city";
|
|
1982
|
+
if (/\b(state|province|region|\[state\]|-state$|_state$)/i.test(haystack))
|
|
1983
|
+
return "state";
|
|
1984
|
+
if (/\b(zip|postal|postcode|post code|\[zip\]|-zip$|_zip$)/i.test(haystack))
|
|
1985
|
+
return "zip";
|
|
1986
|
+
if (/\b(country|\[country\]|-country$|_country$)/i.test(haystack))
|
|
1987
|
+
return "country";
|
|
1988
|
+
return null;
|
|
1989
|
+
};
|
|
1990
|
+
const root = stateOptions.selector != null
|
|
1991
|
+
? document.querySelector(stateOptions.selector)
|
|
1992
|
+
: stateOptions.internalLabel != null
|
|
1993
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
1994
|
+
: stateOptions.fieldId == null
|
|
1995
|
+
? null
|
|
1996
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
1997
|
+
const nativeControls = root
|
|
1998
|
+
? Array.from(root.querySelectorAll("input, textarea, select"))
|
|
1999
|
+
: [];
|
|
2000
|
+
const controls = nativeControls.map((control) => ({
|
|
2001
|
+
tagName: control.tagName.toLowerCase(),
|
|
2002
|
+
type: control.getAttribute("type"),
|
|
2003
|
+
id: control.getAttribute("id"),
|
|
2004
|
+
name: control.getAttribute("name"),
|
|
2005
|
+
value: control.getAttribute("value"),
|
|
2006
|
+
currentValue: "value" in control ? control.value : "",
|
|
2007
|
+
checked: control instanceof HTMLInputElement && ["checkbox", "radio"].includes(control.type) ? control.checked : null,
|
|
2008
|
+
part: inferPart(control),
|
|
2009
|
+
text: control instanceof HTMLSelectElement
|
|
2010
|
+
? normalizeText(control.selectedOptions.item(0)?.textContent)
|
|
2011
|
+
: normalizeText(control.textContent),
|
|
2012
|
+
disabled: "disabled" in control ? Boolean(control.disabled) : null,
|
|
2013
|
+
}));
|
|
2014
|
+
const selectedParts = {};
|
|
2015
|
+
for (const control of controls) {
|
|
2016
|
+
if (control.part && control.disabled !== true && typeof control.currentValue === "string" && control.currentValue.length > 0) {
|
|
2017
|
+
selectedParts[control.part] = control.currentValue;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
const expected = stateOptions.expectedValue ?? {};
|
|
2021
|
+
const expectedEntries = Object.entries(expected).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
|
|
2022
|
+
const availableParts = new Set(controls.filter((control) => control.part && control.disabled !== true).map((control) => control.part));
|
|
2023
|
+
const renderedExpectedEntries = expectedEntries.filter(([part]) => availableParts.has(part));
|
|
2024
|
+
const unavailableParts = expectedEntries
|
|
2025
|
+
.filter(([part]) => !availableParts.has(part))
|
|
2026
|
+
.map(([part]) => part);
|
|
2027
|
+
const missingParts = renderedExpectedEntries
|
|
2028
|
+
.filter(([part]) => selectedParts[part] === undefined || selectedParts[part] === "")
|
|
2029
|
+
.map(([part]) => part);
|
|
2030
|
+
const mismatchedParts = renderedExpectedEntries
|
|
2031
|
+
.filter(([part, value]) => selectedParts[part] !== undefined && selectedParts[part] !== "" && selectedParts[part] !== value)
|
|
2032
|
+
.map(([part]) => part);
|
|
2033
|
+
const renderedValueAvailable = Object.keys(selectedParts).length > 0;
|
|
2034
|
+
return {
|
|
2035
|
+
fieldMatched: Boolean(root),
|
|
2036
|
+
selectedParts,
|
|
2037
|
+
...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
|
|
2038
|
+
renderedValueAvailable,
|
|
2039
|
+
unavailableParts,
|
|
2040
|
+
missingParts,
|
|
2041
|
+
mismatchedParts,
|
|
2042
|
+
matched: Boolean(root) && missingParts.length === 0 && mismatchedParts.length === 0,
|
|
2043
|
+
controls,
|
|
2044
|
+
};
|
|
2045
|
+
}, options);
|
|
2046
|
+
}
|
|
2047
|
+
export async function waitForLiveFormAddressState(page, options) {
|
|
2048
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
2049
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
2050
|
+
const startedAt = Date.now();
|
|
2051
|
+
let latest = await readLiveFormAddressState(page, options);
|
|
2052
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
2053
|
+
await delay(intervalMs);
|
|
2054
|
+
latest = await readLiveFormAddressState(page, options);
|
|
2055
|
+
}
|
|
2056
|
+
return latest;
|
|
2057
|
+
}
|
|
1950
2058
|
export async function readLiveFormCheckboxState(page, options) {
|
|
1951
2059
|
if (!hasEvaluate(page)) {
|
|
1952
2060
|
throw new Error("readLiveFormCheckboxState requires a Playwright page with evaluate().");
|
|
@@ -4242,6 +4350,47 @@ export async function setLiveFormProductValue(page, options) {
|
|
|
4242
4350
|
(visibility?.matched ?? true),
|
|
4243
4351
|
};
|
|
4244
4352
|
}
|
|
4353
|
+
export async function setLiveFormAddressValue(page, options) {
|
|
4354
|
+
const payload = normalizeLiveFormAddressValue(options.value, options.current);
|
|
4355
|
+
const transaction = await performLiveFormFieldTransaction(page, {
|
|
4356
|
+
write: {
|
|
4357
|
+
...(options.write ?? {}),
|
|
4358
|
+
target: options.target,
|
|
4359
|
+
value: payload,
|
|
4360
|
+
notify: options.write?.notify ?? true,
|
|
4361
|
+
},
|
|
4362
|
+
fieldKind: "address",
|
|
4363
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4364
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4365
|
+
afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
|
|
4366
|
+
...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
|
|
4367
|
+
});
|
|
4368
|
+
const dom = await waitForLiveFormAddressState(page, {
|
|
4369
|
+
...options.target,
|
|
4370
|
+
expectedValue: payload,
|
|
4371
|
+
...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
|
|
4372
|
+
});
|
|
4373
|
+
const visibilityTargets = [
|
|
4374
|
+
...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
|
|
4375
|
+
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
4376
|
+
];
|
|
4377
|
+
const visibility = visibilityTargets.length > 0
|
|
4378
|
+
? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
|
|
4379
|
+
: undefined;
|
|
4380
|
+
return {
|
|
4381
|
+
payload,
|
|
4382
|
+
transaction,
|
|
4383
|
+
dom,
|
|
4384
|
+
...(visibility ? { visibility } : {}),
|
|
4385
|
+
ok: transaction.write.valueMatched &&
|
|
4386
|
+
(transaction.event?.matched ?? true) &&
|
|
4387
|
+
(transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
|
|
4388
|
+
(transaction.quiet?.quiet ?? true) &&
|
|
4389
|
+
((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
|
|
4390
|
+
dom.matched &&
|
|
4391
|
+
(visibility?.matched ?? true),
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4245
4394
|
export async function setLiveFormRatingValue(page, options) {
|
|
4246
4395
|
const payload = normalizeLiveFormRatingValue(options.value);
|
|
4247
4396
|
const value = payload.value;
|
|
@@ -4372,7 +4521,18 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4372
4521
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4373
4522
|
})
|
|
4374
4523
|
: undefined;
|
|
4375
|
-
const
|
|
4524
|
+
const addressTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && options.fieldKind === "address"
|
|
4525
|
+
? await setLiveFormAddressValue(page, {
|
|
4526
|
+
target: options.target,
|
|
4527
|
+
value: options.value,
|
|
4528
|
+
...(options.write === undefined ? {} : { write: options.write }),
|
|
4529
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4530
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4531
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4532
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4533
|
+
})
|
|
4534
|
+
: undefined;
|
|
4535
|
+
const productTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && options.fieldKind === "product"
|
|
4376
4536
|
? await setLiveFormProductValue(page, {
|
|
4377
4537
|
target: options.target,
|
|
4378
4538
|
value: options.value,
|
|
@@ -4383,7 +4543,7 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4383
4543
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4384
4544
|
})
|
|
4385
4545
|
: undefined;
|
|
4386
|
-
const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !productTransaction && options.fieldKind === "rating"
|
|
4546
|
+
const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && !productTransaction && options.fieldKind === "rating"
|
|
4387
4547
|
? await setLiveFormRatingValue(page, {
|
|
4388
4548
|
target: options.target,
|
|
4389
4549
|
value: options.value,
|
|
@@ -4413,25 +4573,27 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4413
4573
|
? selectTransaction.transaction
|
|
4414
4574
|
: matrixTransaction
|
|
4415
4575
|
? matrixTransaction.transaction
|
|
4416
|
-
:
|
|
4417
|
-
?
|
|
4418
|
-
:
|
|
4419
|
-
?
|
|
4420
|
-
:
|
|
4421
|
-
?
|
|
4422
|
-
:
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4576
|
+
: addressTransaction
|
|
4577
|
+
? addressTransaction.transaction
|
|
4578
|
+
: productTransaction
|
|
4579
|
+
? productTransaction.transaction
|
|
4580
|
+
: ratingTransaction
|
|
4581
|
+
? ratingTransaction.transaction
|
|
4582
|
+
: dateTimeTransaction
|
|
4583
|
+
? dateTimeTransaction.transaction
|
|
4584
|
+
: await performLiveFormFieldTransaction(page, {
|
|
4585
|
+
write: {
|
|
4586
|
+
...(options.write ?? {}),
|
|
4587
|
+
target: options.target,
|
|
4588
|
+
value: options.value,
|
|
4589
|
+
notify: options.write?.notify ?? true,
|
|
4590
|
+
},
|
|
4591
|
+
...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
|
|
4592
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4593
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4594
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4595
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4596
|
+
});
|
|
4435
4597
|
const visibilityTargets = [
|
|
4436
4598
|
...(options.expectVisible ?? []),
|
|
4437
4599
|
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
@@ -6136,7 +6298,19 @@ export const liveFormFieldRecipes = {
|
|
|
6136
6298
|
},
|
|
6137
6299
|
}),
|
|
6138
6300
|
setNameApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormNameValue(value), options),
|
|
6139
|
-
setAddressApi: (name, internalLabel, value, options) =>
|
|
6301
|
+
setAddressApi: (name, internalLabel, value, options = {}) => ({
|
|
6302
|
+
name,
|
|
6303
|
+
run: async (harness) => {
|
|
6304
|
+
await setLiveFormAddressValue(harness.page, {
|
|
6305
|
+
target: { internalLabel },
|
|
6306
|
+
value,
|
|
6307
|
+
write: { notify: options.notify ?? true },
|
|
6308
|
+
waitForEvent: options.waitForEvent ?? (options.notify ?? true),
|
|
6309
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
6310
|
+
afterQuiet: options.waitForQuiet ?? true,
|
|
6311
|
+
});
|
|
6312
|
+
},
|
|
6313
|
+
}),
|
|
6140
6314
|
setChoiceApi: (name, internalLabel, value, options = {}) => ({
|
|
6141
6315
|
name,
|
|
6142
6316
|
run: async (harness) => {
|