@redrockswebdevelopment/formstack-form-testing 0.1.32 → 0.1.33
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 +54 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -195,8 +195,8 @@ export const liveFormFieldTypeCoverage = {
|
|
|
195
195
|
apiWrite: false,
|
|
196
196
|
domInteraction: true,
|
|
197
197
|
domAssertion: true,
|
|
198
|
-
reason: "Signature fields require specialized canvas/user-event automation;
|
|
199
|
-
recommendedHelpers: ["
|
|
198
|
+
reason: "Signature fields require specialized canvas/user-event automation; strict helpers verify hidden signature values or canvas ink after draw/clear.",
|
|
199
|
+
recommendedHelpers: ["drawLiveFormSignature", "clearLiveFormSignature", "readLiveFormSignatureState", "liveFormFieldRecipes.drawSignatureAndAssertDom"],
|
|
200
200
|
},
|
|
201
201
|
textarea: {
|
|
202
202
|
kind: "textarea",
|
|
@@ -5269,6 +5269,12 @@ function fileInputLocator(page, target) {
|
|
|
5269
5269
|
}
|
|
5270
5270
|
throw new Error("File upload target requires internalLabel or fieldId.");
|
|
5271
5271
|
}
|
|
5272
|
+
function liveFormSignatureInternalLabel(target) {
|
|
5273
|
+
if (target.internalLabel == null) {
|
|
5274
|
+
throw new Error("Signature target requires internalLabel.");
|
|
5275
|
+
}
|
|
5276
|
+
return target.internalLabel;
|
|
5277
|
+
}
|
|
5272
5278
|
export async function readLiveFormFileState(page, options) {
|
|
5273
5279
|
if (!hasEvaluate(page)) {
|
|
5274
5280
|
throw new Error("readLiveFormFileState requires a Playwright page with evaluate().");
|
|
@@ -5356,6 +5362,101 @@ export async function uploadLiveFormFile(page, options) {
|
|
|
5356
5362
|
((validation?.inlineErrors.length ?? 0) === 0),
|
|
5357
5363
|
};
|
|
5358
5364
|
}
|
|
5365
|
+
export async function readLiveFormSignatureState(page, options) {
|
|
5366
|
+
if (!hasEvaluate(page)) {
|
|
5367
|
+
throw new Error("readLiveFormSignatureState requires a Playwright page with evaluate().");
|
|
5368
|
+
}
|
|
5369
|
+
return page.evaluate((stateOptions) => {
|
|
5370
|
+
const root = stateOptions.selector != null
|
|
5371
|
+
? document.querySelector(stateOptions.selector)
|
|
5372
|
+
: stateOptions.internalLabel != null
|
|
5373
|
+
? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
|
|
5374
|
+
: stateOptions.fieldId == null
|
|
5375
|
+
? null
|
|
5376
|
+
: document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
|
|
5377
|
+
const hiddenValues = root
|
|
5378
|
+
? Array.from(root.querySelectorAll("input[type=\"hidden\"], textarea"))
|
|
5379
|
+
.map((control) => control.value)
|
|
5380
|
+
.filter((value) => value.trim().length > 0)
|
|
5381
|
+
: [];
|
|
5382
|
+
const canvases = root
|
|
5383
|
+
? Array.from(root.querySelectorAll("canvas")).map((canvas) => {
|
|
5384
|
+
try {
|
|
5385
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
5386
|
+
if (!context || canvas.width <= 0 || canvas.height <= 0) {
|
|
5387
|
+
return {
|
|
5388
|
+
width: canvas.width,
|
|
5389
|
+
height: canvas.height,
|
|
5390
|
+
readable: false,
|
|
5391
|
+
inkPixelCount: null,
|
|
5392
|
+
dataUrlLength: null,
|
|
5393
|
+
error: context ? "empty-canvas" : "missing-2d-context",
|
|
5394
|
+
};
|
|
5395
|
+
}
|
|
5396
|
+
const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
5397
|
+
let inkPixelCount = 0;
|
|
5398
|
+
for (let index = 0; index < pixels.length; index += 4) {
|
|
5399
|
+
const alpha = pixels[index + 3] ?? 0;
|
|
5400
|
+
const red = pixels[index] ?? 255;
|
|
5401
|
+
const green = pixels[index + 1] ?? 255;
|
|
5402
|
+
const blue = pixels[index + 2] ?? 255;
|
|
5403
|
+
if (alpha > 0 && (red < 245 || green < 245 || blue < 245))
|
|
5404
|
+
inkPixelCount += 1;
|
|
5405
|
+
}
|
|
5406
|
+
let dataUrlLength = null;
|
|
5407
|
+
try {
|
|
5408
|
+
dataUrlLength = canvas.toDataURL("image/png").length;
|
|
5409
|
+
}
|
|
5410
|
+
catch {
|
|
5411
|
+
dataUrlLength = null;
|
|
5412
|
+
}
|
|
5413
|
+
return {
|
|
5414
|
+
width: canvas.width,
|
|
5415
|
+
height: canvas.height,
|
|
5416
|
+
readable: true,
|
|
5417
|
+
inkPixelCount,
|
|
5418
|
+
dataUrlLength,
|
|
5419
|
+
error: null,
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
catch (error) {
|
|
5423
|
+
return {
|
|
5424
|
+
width: canvas.width,
|
|
5425
|
+
height: canvas.height,
|
|
5426
|
+
readable: false,
|
|
5427
|
+
inkPixelCount: null,
|
|
5428
|
+
dataUrlLength: null,
|
|
5429
|
+
error: error instanceof Error ? error.message : String(error),
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
})
|
|
5433
|
+
: [];
|
|
5434
|
+
const surfaceMatched = Boolean(root?.querySelector(".konvajs-content, canvas"));
|
|
5435
|
+
const canvasMatched = canvases.length > 0;
|
|
5436
|
+
const signed = hiddenValues.length > 0 || canvases.some((canvas) => (canvas.inkPixelCount ?? 0) > 0);
|
|
5437
|
+
return {
|
|
5438
|
+
fieldMatched: Boolean(root),
|
|
5439
|
+
surfaceMatched,
|
|
5440
|
+
canvasMatched,
|
|
5441
|
+
signed,
|
|
5442
|
+
...(stateOptions.expectSigned === undefined ? {} : { expectSigned: stateOptions.expectSigned }),
|
|
5443
|
+
matched: Boolean(root) && surfaceMatched && (stateOptions.expectSigned === undefined || signed === stateOptions.expectSigned),
|
|
5444
|
+
hiddenValues,
|
|
5445
|
+
canvases,
|
|
5446
|
+
};
|
|
5447
|
+
}, options);
|
|
5448
|
+
}
|
|
5449
|
+
export async function waitForLiveFormSignatureState(page, options) {
|
|
5450
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
5451
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
5452
|
+
const startedAt = Date.now();
|
|
5453
|
+
let latest = await readLiveFormSignatureState(page, options);
|
|
5454
|
+
while (!latest.matched && Date.now() - startedAt < timeoutMs) {
|
|
5455
|
+
await delay(intervalMs);
|
|
5456
|
+
latest = await readLiveFormSignatureState(page, options);
|
|
5457
|
+
}
|
|
5458
|
+
return latest;
|
|
5459
|
+
}
|
|
5359
5460
|
export async function drawSignature(page, internalLabel, options = {}) {
|
|
5360
5461
|
if (!page.mouse) {
|
|
5361
5462
|
throw new Error("drawSignature requires a Playwright page with mouse support.");
|
|
@@ -5393,6 +5494,49 @@ export async function drawSignature(page, internalLabel, options = {}) {
|
|
|
5393
5494
|
}
|
|
5394
5495
|
await page.mouse.up();
|
|
5395
5496
|
}
|
|
5497
|
+
export async function drawLiveFormSignature(page, options) {
|
|
5498
|
+
await drawSignature(page, liveFormSignatureInternalLabel(options.target), options);
|
|
5499
|
+
const quiet = options.afterQuiet === false
|
|
5500
|
+
? undefined
|
|
5501
|
+
: await waitForLiveFormQuiet(page, typeof options.afterQuiet === "object" ? options.afterQuiet : {});
|
|
5502
|
+
const dom = await waitForLiveFormSignatureState(page, {
|
|
5503
|
+
...options.target,
|
|
5504
|
+
expectSigned: true,
|
|
5505
|
+
});
|
|
5506
|
+
const validation = options.afterValidation === undefined
|
|
5507
|
+
? undefined
|
|
5508
|
+
: await waitForLiveFormValidationState(page, typeof options.afterValidation === "object" ? options.afterValidation : {});
|
|
5509
|
+
return {
|
|
5510
|
+
dom,
|
|
5511
|
+
...(quiet ? { quiet } : {}),
|
|
5512
|
+
...(validation ? { validation } : {}),
|
|
5513
|
+
ok: dom.matched &&
|
|
5514
|
+
(quiet?.quiet ?? true) &&
|
|
5515
|
+
((validation?.inlineErrors.length ?? 0) === 0),
|
|
5516
|
+
};
|
|
5517
|
+
}
|
|
5518
|
+
export async function clearLiveFormSignature(page, options) {
|
|
5519
|
+
const internalLabel = liveFormSignatureInternalLabel(options.target);
|
|
5520
|
+
await page.locator(formstackSelectors.signatureClearButtonByInternalLabel(internalLabel)).first().click();
|
|
5521
|
+
const quiet = options.afterQuiet === false
|
|
5522
|
+
? undefined
|
|
5523
|
+
: await waitForLiveFormQuiet(page, typeof options.afterQuiet === "object" ? options.afterQuiet : {});
|
|
5524
|
+
const dom = await waitForLiveFormSignatureState(page, {
|
|
5525
|
+
...options.target,
|
|
5526
|
+
expectSigned: false,
|
|
5527
|
+
});
|
|
5528
|
+
const validation = options.afterValidation === undefined
|
|
5529
|
+
? undefined
|
|
5530
|
+
: await waitForLiveFormValidationState(page, typeof options.afterValidation === "object" ? options.afterValidation : {});
|
|
5531
|
+
return {
|
|
5532
|
+
dom,
|
|
5533
|
+
...(quiet ? { quiet } : {}),
|
|
5534
|
+
...(validation ? { validation } : {}),
|
|
5535
|
+
ok: dom.matched &&
|
|
5536
|
+
(quiet?.quiet ?? true) &&
|
|
5537
|
+
((validation?.inlineErrors.length ?? 0) === 0),
|
|
5538
|
+
};
|
|
5539
|
+
}
|
|
5396
5540
|
export function createFormTestHarness(page, options = {}) {
|
|
5397
5541
|
return {
|
|
5398
5542
|
page,
|
|
@@ -5793,6 +5937,30 @@ export const liveFormFieldRecipes = {
|
|
|
5793
5937
|
name,
|
|
5794
5938
|
run: (harness) => harness.drawSignature(internalLabel, options),
|
|
5795
5939
|
}),
|
|
5940
|
+
drawSignatureAndAssertDom: (name, internalLabel, options = {}) => ({
|
|
5941
|
+
name,
|
|
5942
|
+
run: async (harness) => {
|
|
5943
|
+
const result = await drawLiveFormSignature(harness.page, {
|
|
5944
|
+
...options,
|
|
5945
|
+
target: { internalLabel },
|
|
5946
|
+
});
|
|
5947
|
+
if (!result.ok) {
|
|
5948
|
+
throw new Error(`Signature "${internalLabel}" did not produce a signed state.`);
|
|
5949
|
+
}
|
|
5950
|
+
},
|
|
5951
|
+
}),
|
|
5952
|
+
clearSignatureAndAssertDom: (name, internalLabel, options = {}) => ({
|
|
5953
|
+
name,
|
|
5954
|
+
run: async (harness) => {
|
|
5955
|
+
const result = await clearLiveFormSignature(harness.page, {
|
|
5956
|
+
...options,
|
|
5957
|
+
target: { internalLabel },
|
|
5958
|
+
});
|
|
5959
|
+
if (!result.ok) {
|
|
5960
|
+
throw new Error(`Signature "${internalLabel}" did not clear to an unsigned state.`);
|
|
5961
|
+
}
|
|
5962
|
+
},
|
|
5963
|
+
}),
|
|
5796
5964
|
setNameApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormNameValue(value), options),
|
|
5797
5965
|
setAddressApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormAddressValue(value), options),
|
|
5798
5966
|
setChoiceApi: (name, internalLabel, value, options = {}) => ({
|