@redrockswebdevelopment/formstack-form-testing 0.1.29 → 0.1.31
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 +46 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +253 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -969,6 +969,61 @@ export function buildLiveFormDateTimeValue(value, current = {}) {
|
|
|
969
969
|
...(value.ampm !== undefined ? { A: value.ampm } : {}),
|
|
970
970
|
};
|
|
971
971
|
}
|
|
972
|
+
export function normalizeLiveFormDateTimeValue(value) {
|
|
973
|
+
if (value instanceof Date) {
|
|
974
|
+
return {
|
|
975
|
+
M: String(value.getUTCMonth() + 1).padStart(2, "0"),
|
|
976
|
+
D: String(value.getUTCDate()).padStart(2, "0"),
|
|
977
|
+
Y: String(value.getUTCFullYear()),
|
|
978
|
+
H: String(value.getUTCHours()).padStart(2, "0"),
|
|
979
|
+
I: String(value.getUTCMinutes()).padStart(2, "0"),
|
|
980
|
+
S: String(value.getUTCSeconds()).padStart(2, "0"),
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (typeof value === "string") {
|
|
984
|
+
const iso = value.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{2}):(\d{2})(?::(\d{2}))?)?/);
|
|
985
|
+
if (iso) {
|
|
986
|
+
const [, year, month, day, hour, minutes, seconds] = iso;
|
|
987
|
+
return {
|
|
988
|
+
...(year === undefined ? {} : { Y: year }),
|
|
989
|
+
...(month === undefined ? {} : { M: month }),
|
|
990
|
+
...(day === undefined ? {} : { D: day }),
|
|
991
|
+
...(hour === undefined ? {} : { H: hour }),
|
|
992
|
+
...(minutes === undefined ? {} : { I: minutes }),
|
|
993
|
+
...(seconds === undefined ? {} : { S: seconds }),
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
const us = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM)?)?/i);
|
|
997
|
+
if (us) {
|
|
998
|
+
const [, month, day, year, hour, minutes, seconds, ampm] = us;
|
|
999
|
+
return {
|
|
1000
|
+
...(month === undefined ? {} : { M: month.padStart(2, "0") }),
|
|
1001
|
+
...(day === undefined ? {} : { D: day.padStart(2, "0") }),
|
|
1002
|
+
...(year === undefined ? {} : { Y: year }),
|
|
1003
|
+
...(hour === undefined ? {} : { H: hour.padStart(2, "0") }),
|
|
1004
|
+
...(minutes === undefined ? {} : { I: minutes }),
|
|
1005
|
+
...(seconds === undefined ? {} : { S: seconds }),
|
|
1006
|
+
...(ampm === undefined ? {} : { A: ampm.toUpperCase() }),
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
return { Y: value };
|
|
1010
|
+
}
|
|
1011
|
+
if ("month" in value || "day" in value || "year" in value || "hour" in value || "minutes" in value || "seconds" in value || "ampm" in value) {
|
|
1012
|
+
return buildLiveFormDateTimeValue(value);
|
|
1013
|
+
}
|
|
1014
|
+
if ("M" in value || "D" in value || "Y" in value || "H" in value || "I" in value || "S" in value || "A" in value) {
|
|
1015
|
+
return {
|
|
1016
|
+
...(value.M === undefined ? {} : { M: String(value.M) }),
|
|
1017
|
+
...(value.D === undefined ? {} : { D: String(value.D) }),
|
|
1018
|
+
...(value.Y === undefined ? {} : { Y: String(value.Y) }),
|
|
1019
|
+
...(value.H === undefined ? {} : { H: String(value.H) }),
|
|
1020
|
+
...(value.I === undefined ? {} : { I: String(value.I) }),
|
|
1021
|
+
...(value.S === undefined ? {} : { S: String(value.S) }),
|
|
1022
|
+
...(value.A === undefined ? {} : { A: String(value.A) }),
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return {};
|
|
1026
|
+
}
|
|
972
1027
|
export function buildLiveFormProductValue(value, current = {}) {
|
|
973
1028
|
return {
|
|
974
1029
|
...(current && typeof current === "object" ? current : {}),
|
|
@@ -2172,6 +2227,124 @@ export async function waitForLiveFormRatingState(page, options) {
|
|
|
2172
2227
|
}
|
|
2173
2228
|
return latest;
|
|
2174
2229
|
}
|
|
2230
|
+
export async function readLiveFormDateTimeState(page, options) {
|
|
2231
|
+
if (!hasEvaluate(page)) {
|
|
2232
|
+
throw new Error("readLiveFormDateTimeState requires a Playwright page with evaluate().");
|
|
2233
|
+
}
|
|
2234
|
+
return page.evaluate((stateOptions) => {
|
|
2235
|
+
const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
|
|
2236
|
+
const inferPart = (control) => {
|
|
2237
|
+
const label = control.id ? document.querySelector(`label[for="${CSS.escape(control.id)}"]`) : null;
|
|
2238
|
+
const haystack = [
|
|
2239
|
+
control.name,
|
|
2240
|
+
control.id,
|
|
2241
|
+
control.className,
|
|
2242
|
+
control.getAttribute("placeholder"),
|
|
2243
|
+
control.getAttribute("aria-label"),
|
|
2244
|
+
label?.textContent,
|
|
2245
|
+
].map(normalizeText).join(" ").toLowerCase();
|
|
2246
|
+
if (/\b(month|mm|\[m\]|-m$|_m$)/i.test(haystack))
|
|
2247
|
+
return "M";
|
|
2248
|
+
if (/\b(day|dd|\[d\]|-d$|_d$)/i.test(haystack))
|
|
2249
|
+
return "D";
|
|
2250
|
+
if (/\b(year|yyyy|\[y\]|-y$|_y$)/i.test(haystack))
|
|
2251
|
+
return "Y";
|
|
2252
|
+
if (/\b(hour|hh|\[h\]|-h$|_h$)/i.test(haystack))
|
|
2253
|
+
return "H";
|
|
2254
|
+
if (/\b(minute|minutes|mmn|\[i\]|-i$|_i$)/i.test(haystack))
|
|
2255
|
+
return "I";
|
|
2256
|
+
if (/\b(second|seconds|\[s\]|-s$|_s$)/i.test(haystack))
|
|
2257
|
+
return "S";
|
|
2258
|
+
if (/\b(am\/pm|ampm|am pm|\[a\]|-a$|_a$)/i.test(haystack))
|
|
2259
|
+
return "A";
|
|
2260
|
+
return null;
|
|
2261
|
+
};
|
|
2262
|
+
const root = stateOptions.selector != null
|
|
2263
|
+
? document.querySelector(stateOptions.selector)
|
|
2264
|
+
: stateOptions.internalLabel != null
|
|
2265
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
2266
|
+
: stateOptions.fieldId == null
|
|
2267
|
+
? null
|
|
2268
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
2269
|
+
const nativeControls = root
|
|
2270
|
+
? Array.from(root.querySelectorAll("input, textarea, select"))
|
|
2271
|
+
: [];
|
|
2272
|
+
const controls = nativeControls.map((control) => {
|
|
2273
|
+
const currentValue = "value" in control ? control.value : "";
|
|
2274
|
+
return {
|
|
2275
|
+
tagName: control.tagName.toLowerCase(),
|
|
2276
|
+
type: control.getAttribute("type"),
|
|
2277
|
+
id: control.getAttribute("id"),
|
|
2278
|
+
name: control.getAttribute("name"),
|
|
2279
|
+
value: control.getAttribute("value"),
|
|
2280
|
+
currentValue,
|
|
2281
|
+
checked: control instanceof HTMLInputElement && ["checkbox", "radio"].includes(control.type) ? control.checked : null,
|
|
2282
|
+
part: inferPart(control),
|
|
2283
|
+
text: control instanceof HTMLSelectElement
|
|
2284
|
+
? normalizeText(control.selectedOptions.item(0)?.textContent)
|
|
2285
|
+
: normalizeText(control.textContent),
|
|
2286
|
+
};
|
|
2287
|
+
});
|
|
2288
|
+
const selectedParts = {};
|
|
2289
|
+
for (const control of controls) {
|
|
2290
|
+
if (control.part && typeof control.currentValue === "string" && control.currentValue.length > 0) {
|
|
2291
|
+
selectedParts[control.part] = control.currentValue;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const unparted = controls.filter((control) => control.part === null && typeof control.currentValue === "string" && control.currentValue.length > 0);
|
|
2295
|
+
if (Object.keys(selectedParts).length === 0 && unparted.length === 1) {
|
|
2296
|
+
const parsed = unparted[0]?.currentValue?.match(/^(\d{4})-(\d{2})-(\d{2})/) ?? unparted[0]?.currentValue?.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
|
|
2297
|
+
if (parsed) {
|
|
2298
|
+
if (parsed[0].includes("-")) {
|
|
2299
|
+
selectedParts.Y = parsed[1] ?? "";
|
|
2300
|
+
selectedParts.M = parsed[2] ?? "";
|
|
2301
|
+
selectedParts.D = parsed[3] ?? "";
|
|
2302
|
+
}
|
|
2303
|
+
else {
|
|
2304
|
+
selectedParts.M = (parsed[1] ?? "").padStart(2, "0");
|
|
2305
|
+
selectedParts.D = (parsed[2] ?? "").padStart(2, "0");
|
|
2306
|
+
selectedParts.Y = parsed[3] ?? "";
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
const expected = stateOptions.expectedValue ?? {};
|
|
2311
|
+
const expectedEntries = Object.entries(expected).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0);
|
|
2312
|
+
const renderedValueAvailable = Object.keys(selectedParts).length > 0;
|
|
2313
|
+
const unavailableParts = renderedValueAvailable ? [] : expectedEntries.map(([part]) => part);
|
|
2314
|
+
const missingParts = renderedValueAvailable
|
|
2315
|
+
? expectedEntries
|
|
2316
|
+
.filter(([part]) => selectedParts[part] === undefined || selectedParts[part] === "")
|
|
2317
|
+
.map(([part]) => part)
|
|
2318
|
+
: [];
|
|
2319
|
+
const mismatchedParts = renderedValueAvailable
|
|
2320
|
+
? expectedEntries
|
|
2321
|
+
.filter(([part, value]) => selectedParts[part] !== undefined && selectedParts[part] !== "" && selectedParts[part] !== value)
|
|
2322
|
+
.map(([part]) => part)
|
|
2323
|
+
: [];
|
|
2324
|
+
return {
|
|
2325
|
+
fieldMatched: Boolean(root),
|
|
2326
|
+
selectedParts,
|
|
2327
|
+
...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
|
|
2328
|
+
renderedValueAvailable,
|
|
2329
|
+
unavailableParts,
|
|
2330
|
+
missingParts,
|
|
2331
|
+
mismatchedParts,
|
|
2332
|
+
matched: Boolean(root) && missingParts.length === 0 && mismatchedParts.length === 0,
|
|
2333
|
+
controls,
|
|
2334
|
+
};
|
|
2335
|
+
}, options);
|
|
2336
|
+
}
|
|
2337
|
+
export async function waitForLiveFormDateTimeState(page, options) {
|
|
2338
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
2339
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
2340
|
+
const startedAt = Date.now();
|
|
2341
|
+
let latest = await readLiveFormDateTimeState(page, options);
|
|
2342
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
2343
|
+
await delay(intervalMs);
|
|
2344
|
+
latest = await readLiveFormDateTimeState(page, options);
|
|
2345
|
+
}
|
|
2346
|
+
return latest;
|
|
2347
|
+
}
|
|
2175
2348
|
export async function dispatchLiveFormFieldDomEvents(page, options) {
|
|
2176
2349
|
if (!hasEvaluate(page)) {
|
|
2177
2350
|
throw new Error("dispatchLiveFormFieldDomEvents requires a Playwright page with evaluate().");
|
|
@@ -3951,6 +4124,47 @@ export async function setLiveFormRatingValue(page, options) {
|
|
|
3951
4124
|
(visibility?.matched ?? true),
|
|
3952
4125
|
};
|
|
3953
4126
|
}
|
|
4127
|
+
export async function setLiveFormDateTimeValue(page, options) {
|
|
4128
|
+
const payload = normalizeLiveFormDateTimeValue(options.value);
|
|
4129
|
+
const transaction = await performLiveFormFieldTransaction(page, {
|
|
4130
|
+
write: {
|
|
4131
|
+
...(options.write ?? {}),
|
|
4132
|
+
target: options.target,
|
|
4133
|
+
value: payload,
|
|
4134
|
+
notify: options.write?.notify ?? true,
|
|
4135
|
+
},
|
|
4136
|
+
fieldKind: "datetime",
|
|
4137
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4138
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4139
|
+
afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
|
|
4140
|
+
...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
|
|
4141
|
+
});
|
|
4142
|
+
const dom = await waitForLiveFormDateTimeState(page, {
|
|
4143
|
+
...options.target,
|
|
4144
|
+
expectedValue: payload,
|
|
4145
|
+
...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
|
|
4146
|
+
});
|
|
4147
|
+
const visibilityTargets = [
|
|
4148
|
+
...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
|
|
4149
|
+
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
4150
|
+
];
|
|
4151
|
+
const visibility = visibilityTargets.length > 0
|
|
4152
|
+
? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
|
|
4153
|
+
: undefined;
|
|
4154
|
+
return {
|
|
4155
|
+
payload,
|
|
4156
|
+
transaction,
|
|
4157
|
+
dom,
|
|
4158
|
+
...(visibility ? { visibility } : {}),
|
|
4159
|
+
ok: transaction.write.valueMatched &&
|
|
4160
|
+
(transaction.event?.matched ?? true) &&
|
|
4161
|
+
(transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
|
|
4162
|
+
(transaction.quiet?.quiet ?? true) &&
|
|
4163
|
+
((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
|
|
4164
|
+
dom.matched &&
|
|
4165
|
+
(visibility?.matched ?? true),
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
3954
4168
|
export async function answerProgressiveLiveFormField(page, options) {
|
|
3955
4169
|
const validationOptions = options.afterValidation === undefined ? undefined : typeof options.afterValidation === "object" ? options.afterValidation : {};
|
|
3956
4170
|
const checkboxTransaction = options.fieldKind === "checkbox"
|
|
@@ -4008,6 +4222,17 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4008
4222
|
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4009
4223
|
})
|
|
4010
4224
|
: undefined;
|
|
4225
|
+
const dateTimeTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !ratingTransaction && ["date", "datetime"].includes(options.fieldKind ?? "")
|
|
4226
|
+
? await setLiveFormDateTimeValue(page, {
|
|
4227
|
+
target: options.target,
|
|
4228
|
+
value: options.value,
|
|
4229
|
+
...(options.write === undefined ? {} : { write: options.write }),
|
|
4230
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4231
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4232
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4233
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4234
|
+
})
|
|
4235
|
+
: undefined;
|
|
4011
4236
|
const transaction = checkboxTransaction
|
|
4012
4237
|
? checkboxTransaction.transaction
|
|
4013
4238
|
: choiceTransaction
|
|
@@ -4018,19 +4243,21 @@ export async function answerProgressiveLiveFormField(page, options) {
|
|
|
4018
4243
|
? matrixTransaction.transaction
|
|
4019
4244
|
: ratingTransaction
|
|
4020
4245
|
? ratingTransaction.transaction
|
|
4021
|
-
:
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4246
|
+
: dateTimeTransaction
|
|
4247
|
+
? dateTimeTransaction.transaction
|
|
4248
|
+
: await performLiveFormFieldTransaction(page, {
|
|
4249
|
+
write: {
|
|
4250
|
+
...(options.write ?? {}),
|
|
4251
|
+
target: options.target,
|
|
4252
|
+
value: options.value,
|
|
4253
|
+
notify: options.write?.notify ?? true,
|
|
4254
|
+
},
|
|
4255
|
+
...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
|
|
4256
|
+
waitForEvent: options.waitForEvent ?? true,
|
|
4257
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
4258
|
+
afterQuiet: options.afterQuiet ?? true,
|
|
4259
|
+
...(validationOptions ? { afterValidation: validationOptions } : {}),
|
|
4260
|
+
});
|
|
4034
4261
|
const visibilityTargets = [
|
|
4035
4262
|
...(options.expectVisible ?? []),
|
|
4036
4263
|
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
@@ -5491,7 +5718,19 @@ export const liveFormFieldRecipes = {
|
|
|
5491
5718
|
});
|
|
5492
5719
|
},
|
|
5493
5720
|
}),
|
|
5494
|
-
setDateTimeApi: (name, internalLabel, value, options) =>
|
|
5721
|
+
setDateTimeApi: (name, internalLabel, value, options = {}) => ({
|
|
5722
|
+
name,
|
|
5723
|
+
run: async (harness) => {
|
|
5724
|
+
await setLiveFormDateTimeValue(harness.page, {
|
|
5725
|
+
target: { internalLabel },
|
|
5726
|
+
value,
|
|
5727
|
+
write: { notify: options.notify ?? true },
|
|
5728
|
+
waitForEvent: options.waitForEvent ?? (options.notify ?? true),
|
|
5729
|
+
dispatchDomEvents: options.dispatchDomEvents ?? "auto",
|
|
5730
|
+
afterQuiet: options.waitForQuiet ?? true,
|
|
5731
|
+
});
|
|
5732
|
+
},
|
|
5733
|
+
}),
|
|
5495
5734
|
setMatrixApi: (name, internalLabel, selections, options = {}) => ({
|
|
5496
5735
|
name,
|
|
5497
5736
|
run: async (harness) => {
|