@redrockswebdevelopment/formstack-form-testing 0.1.35 → 0.1.37

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
@@ -78,7 +78,7 @@ export const liveFormFieldTypeCoverage = {
78
78
  domInteraction: true,
79
79
  domAssertion: true,
80
80
  reason: "Email fields behave like text fields with validation-specific assertions.",
81
- recommendedHelpers: ["setLiveFormFieldValueTransaction", "fillEmail", "waitForLiveFormValidationState"],
81
+ recommendedHelpers: ["setLiveFormTextValue", "fillEmail", "waitForLiveFormValidationState"],
82
82
  },
83
83
  file: {
84
84
  kind: "file",
@@ -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 stable subfield DOM automation.",
108
- recommendedHelpers: ["buildLiveFormNameValue", "setLiveFormFieldValueTransaction", "fillName", "readLiveFormReadiness"],
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",
@@ -114,7 +114,7 @@ export const liveFormFieldTypeCoverage = {
114
114
  domInteraction: true,
115
115
  domAssertion: true,
116
116
  reason: "Number fields use simple value payloads; use validation assertions for numeric constraints.",
117
- recommendedHelpers: ["setLiveFormFieldValueTransaction", "fillNumber", "waitForLiveFormValidationState"],
117
+ recommendedHelpers: ["setLiveFormTextValue", "fillNumber", "waitForLiveFormValidationState"],
118
118
  },
119
119
  pagebreak: {
120
120
  kind: "pagebreak",
@@ -142,7 +142,7 @@ export const liveFormFieldTypeCoverage = {
142
142
  domInteraction: true,
143
143
  domAssertion: true,
144
144
  reason: "Phone fields use simple value payloads; use DOM and validation assertions for formatting behavior.",
145
- recommendedHelpers: ["setLiveFormFieldValueTransaction", "fillPhone", "waitForLiveFormValidationState"],
145
+ recommendedHelpers: ["setLiveFormTextValue", "fillPhone", "waitForLiveFormValidationState"],
146
146
  },
147
147
  product: {
148
148
  kind: "product",
@@ -205,7 +205,7 @@ export const liveFormFieldTypeCoverage = {
205
205
  domInteraction: true,
206
206
  domAssertion: true,
207
207
  reason: "Textarea fields use simple value payloads and stable DOM fill operations.",
208
- recommendedHelpers: ["setLiveFormFieldValueTransaction", "fillTextarea", "readLiveFormDomFieldState"],
208
+ recommendedHelpers: ["setLiveFormTextValue", "fillTextarea", "readLiveFormDomFieldState"],
209
209
  },
210
210
  text: {
211
211
  kind: "text",
@@ -214,7 +214,7 @@ export const liveFormFieldTypeCoverage = {
214
214
  domInteraction: true,
215
215
  domAssertion: true,
216
216
  reason: "Text fields use simple value payloads and stable DOM fill operations.",
217
- recommendedHelpers: ["setLiveFormFieldValueTransaction", "fillText", "readLiveFormDomFieldState"],
217
+ recommendedHelpers: ["setLiveFormTextValue", "fillText", "readLiveFormDomFieldState"],
218
218
  },
219
219
  unknown: {
220
220
  kind: "unknown",
@@ -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().");
@@ -4179,6 +4284,49 @@ export async function setLiveFormCheckboxValues(page, options) {
4179
4284
  (commitControl?.ready ?? true),
4180
4285
  };
4181
4286
  }
4287
+ export async function setLiveFormTextValue(page, options) {
4288
+ const value = String(options.value);
4289
+ const fieldKind = options.fieldKind ?? "text";
4290
+ const transaction = await performLiveFormFieldTransaction(page, {
4291
+ write: {
4292
+ ...(options.write ?? {}),
4293
+ target: options.target,
4294
+ value,
4295
+ notify: options.write?.notify ?? true,
4296
+ },
4297
+ fieldKind,
4298
+ waitForEvent: options.waitForEvent ?? true,
4299
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4300
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
4301
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
4302
+ });
4303
+ const dom = await waitForLiveFormDomFieldState(page, {
4304
+ ...options.target,
4305
+ expectedValue: value,
4306
+ ...(options.controlSelector === undefined ? {} : { controlSelector: options.controlSelector }),
4307
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
4308
+ });
4309
+ const visibilityTargets = [
4310
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
4311
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
4312
+ ];
4313
+ const visibility = visibilityTargets.length > 0
4314
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
4315
+ : undefined;
4316
+ return {
4317
+ value,
4318
+ transaction,
4319
+ dom,
4320
+ ...(visibility ? { visibility } : {}),
4321
+ ok: transaction.write.valueMatched &&
4322
+ (transaction.event?.matched ?? true) &&
4323
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
4324
+ (transaction.quiet?.quiet ?? true) &&
4325
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
4326
+ dom.matched &&
4327
+ (visibility?.matched ?? true),
4328
+ };
4329
+ }
4182
4330
  export async function setLiveFormChoiceValue(page, options) {
4183
4331
  const payload = normalizeLiveFormChoiceValue(options.value);
4184
4332
  const value = payload.value;
@@ -4391,6 +4539,47 @@ export async function setLiveFormAddressValue(page, options) {
4391
4539
  (visibility?.matched ?? true),
4392
4540
  };
4393
4541
  }
4542
+ export async function setLiveFormNameValue(page, options) {
4543
+ const payload = normalizeLiveFormNameValue(options.value, options.current);
4544
+ const transaction = await performLiveFormFieldTransaction(page, {
4545
+ write: {
4546
+ ...(options.write ?? {}),
4547
+ target: options.target,
4548
+ value: payload,
4549
+ notify: options.write?.notify ?? true,
4550
+ },
4551
+ fieldKind: "name",
4552
+ waitForEvent: options.waitForEvent ?? true,
4553
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4554
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
4555
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
4556
+ });
4557
+ const dom = await waitForLiveFormNameState(page, {
4558
+ ...options.target,
4559
+ expectedValue: payload,
4560
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
4561
+ });
4562
+ const visibilityTargets = [
4563
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
4564
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
4565
+ ];
4566
+ const visibility = visibilityTargets.length > 0
4567
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
4568
+ : undefined;
4569
+ return {
4570
+ payload,
4571
+ transaction,
4572
+ dom,
4573
+ ...(visibility ? { visibility } : {}),
4574
+ ok: transaction.write.valueMatched &&
4575
+ (transaction.event?.matched ?? true) &&
4576
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
4577
+ (transaction.quiet?.quiet ?? true) &&
4578
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
4579
+ dom.matched &&
4580
+ (visibility?.matched ?? true),
4581
+ };
4582
+ }
4394
4583
  export async function setLiveFormRatingValue(page, options) {
4395
4584
  const payload = normalizeLiveFormRatingValue(options.value);
4396
4585
  const value = payload.value;
@@ -4521,7 +4710,18 @@ export async function answerProgressiveLiveFormField(page, options) {
4521
4710
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
4522
4711
  })
4523
4712
  : undefined;
4524
- const addressTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && options.fieldKind === "address"
4713
+ const nameTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && options.fieldKind === "name"
4714
+ ? await setLiveFormNameValue(page, {
4715
+ target: options.target,
4716
+ value: options.value,
4717
+ ...(options.write === undefined ? {} : { write: options.write }),
4718
+ waitForEvent: options.waitForEvent ?? true,
4719
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4720
+ afterQuiet: options.afterQuiet ?? true,
4721
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
4722
+ })
4723
+ : undefined;
4724
+ const addressTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && options.fieldKind === "address"
4525
4725
  ? await setLiveFormAddressValue(page, {
4526
4726
  target: options.target,
4527
4727
  value: options.value,
@@ -4532,7 +4732,7 @@ export async function answerProgressiveLiveFormField(page, options) {
4532
4732
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
4533
4733
  })
4534
4734
  : undefined;
4535
- const productTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && options.fieldKind === "product"
4735
+ const productTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && !addressTransaction && options.fieldKind === "product"
4536
4736
  ? await setLiveFormProductValue(page, {
4537
4737
  target: options.target,
4538
4738
  value: options.value,
@@ -4543,7 +4743,7 @@ export async function answerProgressiveLiveFormField(page, options) {
4543
4743
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
4544
4744
  })
4545
4745
  : undefined;
4546
- const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !addressTransaction && !productTransaction && options.fieldKind === "rating"
4746
+ const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && !addressTransaction && !productTransaction && options.fieldKind === "rating"
4547
4747
  ? await setLiveFormRatingValue(page, {
4548
4748
  target: options.target,
4549
4749
  value: options.value,
@@ -4565,6 +4765,18 @@ export async function answerProgressiveLiveFormField(page, options) {
4565
4765
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
4566
4766
  })
4567
4767
  : undefined;
4768
+ const textTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && !nameTransaction && !addressTransaction && !productTransaction && !ratingTransaction && !dateTimeTransaction && ["text", "textarea", "email", "phone", "number"].includes(options.fieldKind ?? "")
4769
+ ? await setLiveFormTextValue(page, {
4770
+ target: options.target,
4771
+ value: options.value,
4772
+ fieldKind: options.fieldKind,
4773
+ ...(options.write === undefined ? {} : { write: options.write }),
4774
+ waitForEvent: options.waitForEvent ?? true,
4775
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4776
+ afterQuiet: options.afterQuiet ?? true,
4777
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
4778
+ })
4779
+ : undefined;
4568
4780
  const transaction = checkboxTransaction
4569
4781
  ? checkboxTransaction.transaction
4570
4782
  : choiceTransaction
@@ -4573,27 +4785,31 @@ export async function answerProgressiveLiveFormField(page, options) {
4573
4785
  ? selectTransaction.transaction
4574
4786
  : matrixTransaction
4575
4787
  ? matrixTransaction.transaction
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
- });
4788
+ : nameTransaction
4789
+ ? nameTransaction.transaction
4790
+ : addressTransaction
4791
+ ? addressTransaction.transaction
4792
+ : productTransaction
4793
+ ? productTransaction.transaction
4794
+ : ratingTransaction
4795
+ ? ratingTransaction.transaction
4796
+ : dateTimeTransaction
4797
+ ? dateTimeTransaction.transaction
4798
+ : textTransaction
4799
+ ? textTransaction.transaction
4800
+ : await performLiveFormFieldTransaction(page, {
4801
+ write: {
4802
+ ...(options.write ?? {}),
4803
+ target: options.target,
4804
+ value: options.value,
4805
+ notify: options.write?.notify ?? true,
4806
+ },
4807
+ ...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
4808
+ waitForEvent: options.waitForEvent ?? true,
4809
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4810
+ afterQuiet: options.afterQuiet ?? true,
4811
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
4812
+ });
4597
4813
  const visibilityTargets = [
4598
4814
  ...(options.expectVisible ?? []),
4599
4815
  ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
@@ -6297,7 +6513,19 @@ export const liveFormFieldRecipes = {
6297
6513
  }
6298
6514
  },
6299
6515
  }),
6300
- setNameApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormNameValue(value), options),
6516
+ setNameApi: (name, internalLabel, value, options = {}) => ({
6517
+ name,
6518
+ run: async (harness) => {
6519
+ await setLiveFormNameValue(harness.page, {
6520
+ target: { internalLabel },
6521
+ value,
6522
+ write: { notify: options.notify ?? true },
6523
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
6524
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
6525
+ afterQuiet: options.waitForQuiet ?? true,
6526
+ });
6527
+ },
6528
+ }),
6301
6529
  setAddressApi: (name, internalLabel, value, options = {}) => ({
6302
6530
  name,
6303
6531
  run: async (harness) => {
@@ -6311,6 +6539,26 @@ export const liveFormFieldRecipes = {
6311
6539
  });
6312
6540
  },
6313
6541
  }),
6542
+ setTextApi: (name, internalLabel, value, options = {}) => ({
6543
+ name,
6544
+ run: async (harness) => {
6545
+ await setLiveFormTextValue(harness.page, {
6546
+ target: { internalLabel },
6547
+ value,
6548
+ fieldKind: options.fieldKind && ["text", "textarea", "email", "phone", "number"].includes(options.fieldKind)
6549
+ ? options.fieldKind
6550
+ : "text",
6551
+ write: { notify: options.notify ?? true },
6552
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
6553
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
6554
+ afterQuiet: options.waitForQuiet ?? true,
6555
+ });
6556
+ },
6557
+ }),
6558
+ setTextareaApi: (name, internalLabel, value, options = {}) => liveFormFieldRecipes.setTextApi(name, internalLabel, value, { ...options, fieldKind: "textarea" }),
6559
+ setEmailApi: (name, internalLabel, value, options = {}) => liveFormFieldRecipes.setTextApi(name, internalLabel, value, { ...options, fieldKind: "email" }),
6560
+ setPhoneApi: (name, internalLabel, value, options = {}) => liveFormFieldRecipes.setTextApi(name, internalLabel, value, { ...options, fieldKind: "phone" }),
6561
+ setNumberApi: (name, internalLabel, value, options = {}) => liveFormFieldRecipes.setTextApi(name, internalLabel, value, { ...options, fieldKind: "number" }),
6314
6562
  setChoiceApi: (name, internalLabel, value, options = {}) => ({
6315
6563
  name,
6316
6564
  run: async (harness) => {