@geometra/proxy 1.65.4 → 1.65.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"dom-actions.d.ts","sourceRoot":"","sources":["../src/dom-actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dom-actions.d.ts","sourceRoot":"","sources":["../src/dom-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAkC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAE/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEnE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;AAgDvE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;IACpC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;IACrC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAC,CAAA;CACvC;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAMvD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAIjE;AAi+FD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,YAAY,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CAkC/F;AA61CD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,CAAC,EAAE;IACL,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB,GACA,OAAO,CAAC,IAAI,CAAC,CAsGf;AAslCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAsNrD;AAeD,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IACL,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iFAAiF;IACjF,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB,GACA,OAAO,CAAC,IAAI,CAAC,CAoDf;AA8XD,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GAAG,OAAO,EACvB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvF,OAAO,CAAC,IAAI,CAAC,CA4Ef;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5J,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,CAAA;AAE3C,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,KAAK,kBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAsIpH;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqHlH;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAA;CAAE,GAC5J,OAAO,CAAC,IAAI,CAAC,CAwOf;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AA2MD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD1G;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4C/G"}
@@ -3314,11 +3314,81 @@ async function beginFileInputCommitGuard(handle) {
3314
3314
  }
3315
3315
  return null;
3316
3316
  }
3317
+ function explicitlyAssociatesFileInput(scope, exactInput) {
3318
+ const broadContainerSelector = 'main, nav, aside, article, section, header, footer, ' +
3319
+ '[role="main"], [role="application"], [role="document"], [role="navigation"]';
3320
+ if (scope.matches(broadContainerSelector) || scope.querySelector(broadContainerSelector)) {
3321
+ return false;
3322
+ }
3323
+ const labels = Array.from(exactInput.labels ?? []);
3324
+ const interactiveElements = Array.from(scope.querySelectorAll('input, select, textarea, button, [role="button"], a[href], [contenteditable="true"]'));
3325
+ const hasUnrelatedControl = interactiveElements.some((control) => {
3326
+ if (control === exactInput)
3327
+ return false;
3328
+ if (control instanceof HTMLLabelElement && labels.includes(control))
3329
+ return false;
3330
+ const style = getComputedStyle(control);
3331
+ const visible = style.display !== 'none' &&
3332
+ style.visibility !== 'hidden' &&
3333
+ style.visibility !== 'collapse' &&
3334
+ control.getClientRects().length > 0;
3335
+ if (!visible)
3336
+ return false;
3337
+ if (!control.matches('button, [role="button"], a[href]'))
3338
+ return true;
3339
+ const accessibleText = (control.getAttribute('aria-label') ??
3340
+ control.getAttribute('title') ??
3341
+ control.textContent ?? '').replace(/\s+/g, ' ').trim();
3342
+ const action = /^(attach|upload|browse|choose|select|add)\b/i.exec(accessibleText);
3343
+ if (!action)
3344
+ return true;
3345
+ const remainder = accessibleText.slice(action[0].length).trim();
3346
+ if (!remainder)
3347
+ return !/^(attach|upload|browse|choose)$/i.test(action[1] ?? '');
3348
+ return !/^(?:(?:a|an|the|this|new|your)\s+)?(?:files?|attachments?|upload|documents?|resume|cv|cover letter)\b/i.test(remainder);
3349
+ });
3350
+ if (hasUnrelatedControl)
3351
+ return false;
3352
+ if (scope instanceof HTMLLabelElement &&
3353
+ labels.includes(scope))
3354
+ return true;
3355
+ if (labels.some(label => scope.contains(label)))
3356
+ return true;
3357
+ if (scope instanceof HTMLFieldSetElement)
3358
+ return true;
3359
+ if (scope.getAttribute('role')?.trim().toLowerCase() !== 'group')
3360
+ return false;
3361
+ if (scope.getAttribute('aria-label')?.trim())
3362
+ return true;
3363
+ const labelledBy = scope.getAttribute('aria-labelledby')?.trim().split(/\s+/).filter(Boolean) ?? [];
3364
+ return labelledBy.some(id => {
3365
+ const label = exactInput.ownerDocument.getElementById(id);
3366
+ return Boolean(label && (scope.contains(label) || label === scope.previousElementSibling));
3367
+ });
3368
+ }
3317
3369
  const input = element instanceof HTMLInputElement && element.type === 'file' ? element : null;
3318
3370
  const guardedAncestors = new Set();
3371
+ const receiptScopes = [];
3372
+ let capturedReceiptScope = false;
3319
3373
  let current = input;
3320
3374
  while (current) {
3321
3375
  guardedAncestors.add(current);
3376
+ if (!capturedReceiptScope &&
3377
+ current !== input &&
3378
+ !(current instanceof HTMLFormElement) &&
3379
+ !(current instanceof HTMLBodyElement) &&
3380
+ !(current instanceof HTMLHtmlElement) &&
3381
+ input !== null &&
3382
+ explicitlyAssociatesFileInput(current, input) &&
3383
+ current.querySelectorAll('input[type="file"]').length === 1) {
3384
+ const initialExactTexts = [current, ...Array.from(current.querySelectorAll('*'))]
3385
+ .map(candidate => candidate.textContent?.replace(/\s+/g, ' ').trim() ?? '')
3386
+ .filter(Boolean);
3387
+ receiptScopes.push({ element: current, initialExactTexts });
3388
+ capturedReceiptScope = true;
3389
+ }
3390
+ if (current instanceof HTMLFormElement)
3391
+ break;
3322
3392
  current = composedParent(current);
3323
3393
  }
3324
3394
  const initial = {
@@ -3331,6 +3401,7 @@ async function beginFileInputCommitGuard(handle) {
3331
3401
  const state = {
3332
3402
  input,
3333
3403
  initial,
3404
+ receiptScopes,
3334
3405
  invalidReason: null,
3335
3406
  observer: null,
3336
3407
  record: () => { },
@@ -3413,6 +3484,113 @@ async function stopFileInputCommitGuard(guard) {
3413
3484
  value.observer.disconnect();
3414
3485
  }).catch(() => { });
3415
3486
  }
3487
+ async function readReactiveFileReceipt(guard, expectedNames) {
3488
+ return await guard.evaluate((value, names) => {
3489
+ function visible(element) {
3490
+ const style = getComputedStyle(element);
3491
+ return style.display !== 'none' &&
3492
+ style.visibility !== 'hidden' &&
3493
+ style.visibility !== 'collapse' &&
3494
+ element.getClientRects().length > 0;
3495
+ }
3496
+ const normalize = (text) => (text ?? '').replace(/\s+/g, ' ').trim();
3497
+ // The guard snapshots at most the exact input's nearest local wrapper.
3498
+ // Never combine its evidence with controls or filenames from an ancestor.
3499
+ const candidate = value.receiptScopes.find((entry) => entry.element.isConnected);
3500
+ if (!candidate) {
3501
+ return { hasScope: false, hasReceipt: false, hasProgress: false, hasError: false };
3502
+ }
3503
+ if (names.some(name => candidate.initialExactTexts.includes(name))) {
3504
+ return { hasScope: true, hasReceipt: false, hasProgress: false, hasError: false };
3505
+ }
3506
+ const scope = candidate.element;
3507
+ const descendants = [scope, ...Array.from(scope.querySelectorAll('*'))];
3508
+ const expectedCounts = new Map();
3509
+ for (const name of names)
3510
+ expectedCounts.set(name, (expectedCounts.get(name) ?? 0) + 1);
3511
+ const observedCounts = new Map();
3512
+ for (const element of descendants) {
3513
+ if (!visible(element))
3514
+ continue;
3515
+ const text = normalize(element.textContent);
3516
+ if (!expectedCounts.has(text))
3517
+ continue;
3518
+ // Count only the innermost exact-text element so nested wrappers cannot
3519
+ // manufacture duplicate filename evidence for one rendered receipt.
3520
+ const nestedExactText = Array.from(element.querySelectorAll('*')).some(descendant => visible(descendant) && normalize(descendant.textContent) === text);
3521
+ if (!nestedExactText)
3522
+ observedCounts.set(text, (observedCounts.get(text) ?? 0) + 1);
3523
+ }
3524
+ const hasExactNames = Array.from(expectedCounts).every(([name, count]) => observedCounts.get(name) === count);
3525
+ const hasRemovalControl = descendants.some((element) => {
3526
+ if (!visible(element) || !element.matches('button, [role="button"], a'))
3527
+ return false;
3528
+ const accessibleText = normalize(element.getAttribute('aria-label') ??
3529
+ element.getAttribute('title') ??
3530
+ element.textContent);
3531
+ const action = /^(remove|delete|clear|replace|change)\b/i.exec(accessibleText);
3532
+ if (!action)
3533
+ return false;
3534
+ const remainder = accessibleText.slice(action[0].length).trim();
3535
+ if (!remainder)
3536
+ return true;
3537
+ if (names.some(name => remainder.toLowerCase().includes(name.toLowerCase())))
3538
+ return true;
3539
+ return /^(?:(?:this|the|selected|uploaded|current)\s+)?(?:file|attachment|upload|document|resume|cv|cover letter)\b/i.test(remainder);
3540
+ });
3541
+ const hasProgress = descendants.some(element => visible(element) && element.matches('[role="progressbar"], progress, [aria-busy="true"]'));
3542
+ const hasError = descendants.some((element) => {
3543
+ if (!visible(element))
3544
+ return false;
3545
+ const role = normalize(element.getAttribute('role')).toLowerCase();
3546
+ if (role === 'alert' || role === 'alertdialog')
3547
+ return true;
3548
+ if (element.getAttribute('aria-invalid')?.trim().toLowerCase() === 'true')
3549
+ return true;
3550
+ const dataError = element.getAttribute('data-error');
3551
+ if (dataError !== null &&
3552
+ !/^(?:false|0|off|none)$/i.test(dataError.trim()))
3553
+ return true;
3554
+ for (const attribute of ['data-state', 'data-status', 'data-upload-state', 'data-file-state']) {
3555
+ const state = element.getAttribute(attribute)?.trim().toLowerCase();
3556
+ if (state && /^(?:error|invalid|failed|failure|rejected)$/.test(state))
3557
+ return true;
3558
+ }
3559
+ return Array.from(element.classList).some(className => /(?:^|[-_:])(?:error|errors|invalid|failed|failure)(?:$|[-_:])/i.test(className));
3560
+ });
3561
+ if (hasExactNames && hasRemovalControl && !hasProgress && !hasError) {
3562
+ return { hasScope: true, hasReceipt: true, hasProgress: false, hasError: false };
3563
+ }
3564
+ return { hasScope: true, hasReceipt: false, hasProgress, hasError };
3565
+ }, expectedNames);
3566
+ }
3567
+ /**
3568
+ * Some reactive uploaders detach the native input while streaming the file,
3569
+ * then render a filename receipt in the same field wrapper. Treat that as
3570
+ * success only when the exact new filenames and a removal control remain
3571
+ * stable after progress completes. A filename in an error message is not a
3572
+ * receipt, and an unbounded form-level match is never considered.
3573
+ */
3574
+ async function confirmStableReactiveFileReceipt(guard, paths) {
3575
+ const expectedNames = expectedUploadNames(paths);
3576
+ const signalDeadline = Date.now() + 500;
3577
+ let completionDeadline = null;
3578
+ while (Date.now() < (completionDeadline ?? signalDeadline)) {
3579
+ const state = await readReactiveFileReceipt(guard, expectedNames).catch(() => null);
3580
+ if (!state?.hasScope || state.hasError)
3581
+ return false;
3582
+ if (state.hasReceipt) {
3583
+ await delay(100);
3584
+ const stable = await readReactiveFileReceipt(guard, expectedNames).catch(() => null);
3585
+ return Boolean(stable?.hasReceipt && !stable.hasProgress && !stable.hasError);
3586
+ }
3587
+ if (state.hasProgress && completionDeadline === null) {
3588
+ completionDeadline = Date.now() + 15_000;
3589
+ }
3590
+ await delay(50);
3591
+ }
3592
+ return false;
3593
+ }
3416
3594
  async function clearAmbiguousRetainedFiles(handle, actualNames, initialNames) {
3417
3595
  const selectionChanged = !uploadedNamesMatch(actualNames, initialNames);
3418
3596
  if (!selectionChanged) {
@@ -3554,6 +3732,10 @@ async function commitFilesToExactInput(handle, paths, targetName, opts) {
3554
3732
  opts?.successor &&
3555
3733
  await confirmSameKeyFileInputSuccessor(opts.successor.page, handle, opts.successor.fieldKey, paths, initial))
3556
3734
  return;
3735
+ if (!operationError &&
3736
+ identityChanged &&
3737
+ await confirmStableReactiveFileReceipt(guard, paths))
3738
+ return;
3557
3739
  const cleanupOutcome = await clearAmbiguousRetainedFiles(handle, final.names, initial.names);
3558
3740
  const operationReason = operationError instanceof Error
3559
3741
  ? `Playwright could not confirm the exact-target commit: ${operationError.message}`