@globisnv/cypress-core 0.0.10 → 1.1.0

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.
Files changed (52) hide show
  1. package/biome.json +11 -0
  2. package/dist/blueprints/GLO_consignment.js +1 -0
  3. package/dist/blueprints/GLO_consignmentCustoms.d.ts +24 -0
  4. package/dist/blueprints/GLO_consignmentCustoms.js +24 -0
  5. package/dist/blueprints/GLO_consignmentCustomsSettings.d.ts +11 -0
  6. package/dist/blueprints/GLO_consignmentCustomsSettings.js +11 -0
  7. package/dist/blueprints/GLO_customsDeclaration.d.ts +22 -0
  8. package/dist/blueprints/GLO_customsDeclaration.js +22 -0
  9. package/dist/blueprints/GLO_customsDeclarationDetail.d.ts +20 -0
  10. package/dist/blueprints/GLO_customsDeclarationDetail.js +20 -0
  11. package/dist/blueprints/GLO_customsDeclarationDocument.d.ts +9 -0
  12. package/dist/blueprints/GLO_customsDeclarationDocument.js +9 -0
  13. package/dist/blueprints/GLO_dangerousGoods.d.ts +10 -0
  14. package/dist/blueprints/GLO_dangerousGoods.js +9 -0
  15. package/dist/blueprints/GLO_logisticContract.d.ts +14 -0
  16. package/dist/blueprints/GLO_logisticContract.js +53 -11
  17. package/dist/blueprints/GLO_logisticContractCustomsDefinition.d.ts +22 -0
  18. package/dist/blueprints/GLO_logisticContractCustomsDefinition.js +22 -0
  19. package/dist/blueprints/GLO_logisticContractDetail.d.ts +11 -1
  20. package/dist/blueprints/GLO_logisticContractDetail.js +33 -8
  21. package/dist/blueprints/GLO_logisticContractDetailCustoms.d.ts +11 -0
  22. package/dist/blueprints/GLO_logisticContractDetailCustoms.js +11 -0
  23. package/dist/blueprints/GLO_logisticContractDetailHandling.d.ts +1 -0
  24. package/dist/blueprints/GLO_logisticContractDetailHandling.js +1 -0
  25. package/dist/blueprints/GLO_logisticContractDetailItem.js +1 -1
  26. package/dist/blueprints/GLO_logisticContractItem.d.ts +9 -0
  27. package/dist/blueprints/GLO_logisticContractItem.js +31 -4
  28. package/dist/blueprints/GLO_logisticLocationType.d.ts +4 -0
  29. package/dist/blueprints/GLO_logisticLocationType.js +4 -0
  30. package/dist/blueprints/GLO_planBloc.d.ts +6 -0
  31. package/dist/blueprints/GLO_planBloc.js +6 -0
  32. package/dist/blueprints/GLO_priceEntry.d.ts +1 -0
  33. package/dist/blueprints/GLO_priceEntry.js +1 -0
  34. package/dist/blueprints/GLO_purchaseOrderLine.js +4 -0
  35. package/dist/blueprints/GLO_relations.js +4 -4
  36. package/dist/blueprints/GLO_stopSchedule.d.ts +11 -0
  37. package/dist/blueprints/GLO_stopSchedule.js +11 -0
  38. package/dist/blueprints/GLO_transportScenario.d.ts +4 -0
  39. package/dist/blueprints/GLO_transportScenario.js +4 -0
  40. package/dist/blueprints/GLO_transportScenarioExecution.d.ts +6 -0
  41. package/dist/blueprints/GLO_transportScenarioExecution.js +5 -0
  42. package/dist/blueprints/GLO_transportScenarioRegion.d.ts +5 -0
  43. package/dist/blueprints/GLO_transportScenarioRegion.js +5 -0
  44. package/dist/blueprints/GLO_zoneTypeEntityType.d.ts +4 -0
  45. package/dist/blueprints/GLO_zoneTypeEntityType.js +4 -0
  46. package/dist/commands/GLO_commands.js +72 -39
  47. package/dist/commands/GLT_commands.js +2 -2
  48. package/dist/config.js +4 -2
  49. package/dist/websocketDetector.js +148 -114
  50. package/package.json +9 -6
  51. package/template/.gitattributes +12 -0
  52. package/template/package.json +5 -2
@@ -497,66 +497,86 @@ Cypress.Commands.add('WS_loadingCheck', (options) => {
497
497
  });
498
498
  return cy.wrap('locked', { log: false });
499
499
  }
500
- // ═══════════════════════════════════════════════════════════════════════════
501
- // Main polling loop
502
- // ═══════════════════════════════════════════════════════════════════════════
503
- /** Entry point for the resolution loop. Polls every 100 ms until resolved or timed out. */
500
+ /**
501
+ * Entry point for the resolution loop.
502
+ *
503
+ * The poll body only *reads* synchronous state (`win.wsMessages`, `state`, the AUT DOM via
504
+ * `Cypress.$`), so it runs in a plain `setInterval` inside a single command-queue entry — stack
505
+ * depth stays constant however long we wait. The old loop recursed through `cy.wait().then(checkAndWait)`,
506
+ * nesting one chainer per 100 ms; under the 20-min ceiling that exhausted V8's stack before
507
+ * handleTimeout() could ever fire. Only the dialog auto-click needs queued cy commands, so it is
508
+ * returned as a 'dialog' outcome and performed in a follow-up .then() before re-entering the poll.
509
+ */
504
510
  function waitForResolution() {
505
511
  const startTime = Date.now();
506
- return checkAndWait();
507
- function checkAndWait() {
512
+ // The .then() awaits the poll promise, so its command timeout must outlast the longest
513
+ // possible poll — otherwise Cypress aborts before pollOnce() reaches its own timeout branch.
514
+ const pollCommandTimeout = (options?.timeout ? config.timeout : Math.max(1200000, config.timeout)) + 60000;
515
+ return runPoll();
516
+ function runPoll() {
508
517
  return cy
509
518
  .window({ log: false })
510
- .then((win) => {
511
- let hasWsMessages;
512
- try {
513
- hasWsMessages = !!win.wsMessages;
514
- }
515
- catch {
516
- hasWsMessages = false;
517
- }
518
- // If wsMessages is gone the page navigated away — nothing more to wait for
519
- if (!hasWsMessages)
520
- return cy.wrap('navigated', { log: false });
521
- return checkForDialogs();
519
+ .then({ timeout: pollCommandTimeout }, (win) => {
520
+ return new Cypress.Promise((resolve) => {
521
+ let settled = false;
522
+ let intervalId = null;
523
+ const finish = (outcome) => {
524
+ if (settled)
525
+ return;
526
+ settled = true;
527
+ if (intervalId !== null)
528
+ clearInterval(intervalId);
529
+ resolve(outcome);
530
+ };
531
+ const tick = () => {
532
+ try {
533
+ const outcome = pollOnce(win);
534
+ if (outcome)
535
+ finish(outcome);
536
+ }
537
+ catch (error) {
538
+ finish({ type: 'error', error });
539
+ }
540
+ };
541
+ tick(); // check immediately, then every 100 ms
542
+ if (!settled)
543
+ intervalId = setInterval(tick, 100);
544
+ });
522
545
  })
523
- .then((dialogType) => {
524
- if (dialogType === 'navigated') {
525
- debugLog('🟢 Page navigated away — resolving loader');
526
- log.set({ displayName: '🟢 WSLoader', message: 'Resolved' }).end();
527
- return cy.wrap(undefined, { log: false });
528
- }
529
- if (dialogType === 'locked') {
530
- debugLog('🔓 Resolving loader due to locked record detection');
531
- log.set({ displayName: '🟡 WSLoader', message: 'Resolved (Locked Record)' }).end();
532
- return clearWebSocketMessages();
533
- }
534
- return performResolutionCheck();
535
- });
546
+ .then((outcome) => dispatch(outcome));
536
547
  }
537
- function performResolutionCheck() {
538
- return checkResolution().then((isResolved) => {
539
- if (isResolved)
540
- return handleResolvedState();
541
- const effectiveTimeout = !options?.timeout && domLoadingIndicatorEverDetected ? 1200000 : config.timeout;
542
- if (Date.now() - startTime > effectiveTimeout)
543
- return handleTimeout(effectiveTimeout);
544
- // biome-ignore plugin: internal polling interval for checkAndWait retry loop
545
- return cy.wait(100, { log: false }).then(() => checkAndWait());
546
- });
547
- }
548
- /**
549
- * Called once isStateResolved() returns true.
550
- * Enforces the quiet period — the state must stay resolved for QUIET_PERIOD_MS before
551
- * we actually commit to being done.
552
- */
553
- function handleResolvedState() {
554
- if (state.navigatingAway) {
555
- log.set({ displayName: '🟢 WSLoader', message: 'Resolved (navigation)' }).end();
556
- return clearWebSocketMessages();
548
+ /** One synchronous poll tick. Returns an outcome to act on, or null to keep polling. */
549
+ function pollOnce(win) {
550
+ let wsMessages;
551
+ try {
552
+ wsMessages = win.wsMessages;
553
+ }
554
+ catch {
555
+ wsMessages = undefined;
556
+ }
557
+ // wsMessages gone → the page navigated away, nothing more to wait for
558
+ if (!wsMessages)
559
+ return { type: 'navigated' };
560
+ const effectiveTimeout = !options?.timeout && domLoadingIndicatorEverDetected ? 1200000 : config.timeout;
561
+ const timedOut = Date.now() - startTime > effectiveTimeout;
562
+ // A dialog we auto-dismiss needs queued cy commands — hand back to the follow-up, unless
563
+ // we're already over budget (fall through so handleTimeout() gets to dump its diagnostics).
564
+ // Cross-origin redirects (e.g. Okta) can flip win.document's origin between ticks, so this
565
+ // read throws — treat that the same as the wsMessages check above: page navigated away.
566
+ try {
567
+ if (!timedOut && dialogNeedsHandling(win))
568
+ return { type: 'dialog' };
569
+ if (!checkDomReadySync(win.document))
570
+ return timedOut ? { type: 'timeout', timeout: effectiveTimeout } : null;
571
+ }
572
+ catch {
573
+ return state.navigatingAway ? { type: 'resolved', reason: 'navigation' } : { type: 'navigated' };
557
574
  }
558
- // Inside a popup that was opened before this loader started, use the longer quiet
559
- // period to bridge the gap between the loading indicator hiding and executeEvent firing
575
+ if (!checkWsResolvedSync(win))
576
+ return timedOut ? { type: 'timeout', timeout: effectiveTimeout } : null;
577
+ // Resolved — enforce the quiet period before committing.
578
+ if (state.navigatingAway)
579
+ return { type: 'resolved', reason: 'navigation' };
560
580
  const isInsideExternalPopup = !state.popupOpenedByThisAction && currentWin?.wsPopupFormOpen === true;
561
581
  let quietPeriodMs;
562
582
  if (isInsideExternalPopup || expectSearchPopup) {
@@ -568,69 +588,54 @@ Cypress.Commands.add('WS_loadingCheck', (options) => {
568
588
  else {
569
589
  quietPeriodMs = config.quietPeriodMs ?? QUIET_PERIOD_DEFAULT_MS;
570
590
  }
571
- // Only resolve if quiet period has fully elapsed since the last WS activity
572
- const now = Date.now();
573
- const quietTimeSoFar = now - state.lastWsActivityTs;
574
- const remaining = quietPeriodMs - quietTimeSoFar;
575
- if (remaining > 0) {
576
- return cy.wait(remaining, { log: false }).then(() => checkAndWait());
577
- }
591
+ // Keep ticking until the state has stayed resolved for the full quiet period since last WS activity
592
+ if (quietPeriodMs - (Date.now() - state.lastWsActivityTs) > 0)
593
+ return null;
578
594
  state.extendedQuietPeriod = false;
579
- log.set({ displayName: '🟢 WSLoader', message: 'Resolved' }).end();
580
- return clearWebSocketMessages();
595
+ return { type: 'resolved' };
581
596
  }
582
- /**
583
- * Checks whether the DOM loading indicator is gone first, then checks WS state.
584
- * The DOM indicator guards against Angular render lag that can appear after WS goes idle.
585
- */
586
- function checkResolution() {
587
- return checkDomLoadingIndicator().then((domReady) => {
588
- if (!domReady)
589
- return cy.wrap(false, { log: false });
590
- return checkWsResolution();
591
- });
597
+ /** Acts on a poll outcome, running any queued cy work, then finishes or re-enters the poll. */
598
+ function dispatch(outcome) {
599
+ switch (outcome.type) {
600
+ case 'navigated':
601
+ debugLog('🟢 Page navigated away — resolving loader');
602
+ log.set({ displayName: '🟢 WSLoader', message: 'Resolved' }).end();
603
+ return cy.wrap(undefined, { log: false });
604
+ case 'dialog':
605
+ return checkForDialogs().then((dialogType) => {
606
+ if (dialogType === 'locked') {
607
+ debugLog('🔓 Resolving loader due to locked record detection');
608
+ log.set({ displayName: '🟡 WSLoader', message: 'Resolved (Locked Record)' }).end();
609
+ return clearWebSocketMessages();
610
+ }
611
+ // Info/No-price OK-click enqueued (or the dialog vanished) — re-enter the flat poll
612
+ return runPoll();
613
+ });
614
+ case 'resolved':
615
+ log.set({ displayName: '🟢 WSLoader', message: outcome.reason === 'navigation' ? 'Resolved (navigation)' : 'Resolved' }).end();
616
+ return clearWebSocketMessages();
617
+ case 'timeout':
618
+ return handleTimeout(outcome.timeout);
619
+ case 'error':
620
+ throw outcome.error;
621
+ }
592
622
  }
593
- /** Processes new WS frames and evaluates isStateResolved() in a single cy.window() call. */
594
- function checkWsResolution() {
595
- return cy.window({ log: false }).then((win) => {
596
- currentWin = win;
597
- let wsMessages;
598
- try {
599
- wsMessages = win.wsMessages;
600
- }
601
- catch {
602
- wsMessages = undefined;
603
- }
604
- if (!wsMessages) {
605
- debugLog('🟢 No wsMessages found, resolving');
606
- return cy.wrap(true, { log: false });
607
- }
608
- processMessages(wsMessages);
609
- const isResolved = isStateResolved();
610
- let wsPopupFormOpen;
611
- try {
612
- wsPopupFormOpen = win.wsPopupFormOpen ?? false;
613
- }
614
- catch {
615
- wsPopupFormOpen = false;
616
- }
617
- if (!isResolved && !state.popupOpenedByThisAction && wsPopupFormOpen) {
618
- debugLog('🪟 Inside popup from previous action — using extended quiet period');
619
- }
620
- updateLog(isResolved, startTime);
621
- return cy.wrap(isResolved, { log: false });
622
- });
623
+ // ── Synchronous per-tick checks (no queued cy commands) ──────────────────────────────────
624
+ /** True when a dialog is present that the loader auto-dismisses (the dismiss needs queued cy commands). */
625
+ function dialogNeedsHandling(win) {
626
+ const dialog = findDialog(win.document);
627
+ if (!dialog.exists)
628
+ return false;
629
+ if (dialog.isLocked)
630
+ return !lockedRecordDialogDetected;
631
+ if (dialog.isNoPrice)
632
+ return true;
633
+ if (dialog.isInfo)
634
+ return !!(config.enableAutoInfoDialog ?? wsDialogHandlerEnabled);
635
+ return false;
623
636
  }
624
- }
625
- // ═══════════════════════════════════════════════════════════════════════════
626
- // DOM loading indicator check
627
- // ═══════════════════════════════════════════════════════════════════════════
628
- /**
629
- * Returns false (not ready) while a `<loading-indicator>` element is present in the DOM.
630
- * Updates a live Cypress log entry showing how long we have been waiting.
631
- */
632
- function checkDomLoadingIndicator() {
633
- return cy.document({ log: false }).then((doc) => {
637
+ /** Synchronous form of checkDomLoadingIndicator — returns false while a loading-indicator is in the DOM. */
638
+ function checkDomReadySync(doc) {
634
639
  const hasLoadingIndicator = Cypress.$(doc.body).find('loading-indicator').length > 0;
635
640
  if (hasLoadingIndicator) {
636
641
  if (state.domLoadingIndicatorStart === null) {
@@ -656,8 +661,37 @@ Cypress.Commands.add('WS_loadingCheck', (options) => {
656
661
  state.domLoadingIndicatorStart = null;
657
662
  domLoadingIndicatorLog = null;
658
663
  }
659
- return cy.wrap(!hasLoadingIndicator, { log: false });
660
- });
664
+ return !hasLoadingIndicator;
665
+ }
666
+ /** Synchronous form of checkWsResolution — processes new frames and returns isStateResolved(). */
667
+ function checkWsResolvedSync(win) {
668
+ currentWin = win;
669
+ let wsMessages;
670
+ try {
671
+ wsMessages = win.wsMessages;
672
+ }
673
+ catch {
674
+ wsMessages = undefined;
675
+ }
676
+ if (!wsMessages) {
677
+ debugLog('🟢 No wsMessages found, resolving');
678
+ return true;
679
+ }
680
+ processMessages(wsMessages);
681
+ const isResolved = isStateResolved();
682
+ let wsPopupFormOpen;
683
+ try {
684
+ wsPopupFormOpen = win.wsPopupFormOpen ?? false;
685
+ }
686
+ catch {
687
+ wsPopupFormOpen = false;
688
+ }
689
+ if (!isResolved && !state.popupOpenedByThisAction && wsPopupFormOpen) {
690
+ debugLog('🪟 Inside popup from previous action — using extended quiet period');
691
+ }
692
+ updateLog(isResolved, startTime);
693
+ return isResolved;
694
+ }
661
695
  }
662
696
  // ═══════════════════════════════════════════════════════════════════════════
663
697
  // Message processing
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@globisnv/cypress-core",
3
- "version": "0.0.10",
3
+ "version": "1.1.0",
4
4
  "description": "Core building blocks for Globis Cypress E2E tests",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
+ "engines": {
8
+ "node": "^22.0.0 || ^24.0.0 || >=26.0.0"
9
+ },
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "git+https://github.com/globisnv/globis-cypress-core.git"
@@ -43,8 +46,8 @@
43
46
  "typescript": "tsc --noEmit --skipLibCheck"
44
47
  },
45
48
  "peerDependencies": {
46
- "cypress": "^15.20.1",
47
- "cypress-real-events": "^1.15.0"
49
+ "cypress": "^16.0.0",
50
+ "cypress-real-events": "^1.15.1"
48
51
  },
49
52
  "dependencies": {
50
53
  "@aws-crypto/sha256-js": "^5.2.0",
@@ -56,11 +59,11 @@
56
59
  "devDependencies": {
57
60
  "@biomejs/biome": "^2.5.7",
58
61
  "@types/node": "^26.1.2",
59
- "cypress": "^15.20.1",
60
- "cypress-real-events": "^1.15.0",
62
+ "cypress": "^16.1.0",
63
+ "cypress-real-events": "^1.15.1",
61
64
  "typescript": "^7.0.2"
62
65
  },
63
66
  "allowScripts": {
64
- "cypress@15.20.1": true
67
+ "cypress@16.1.0": true
65
68
  }
66
69
  }
@@ -0,0 +1,12 @@
1
+ # Biome formats every file with LF (formatter.lineEnding in biome.json), so check
2
+ # them out with LF on every platform. Without this, core.autocrlf=true rewrites
3
+ # them to CRLF on checkout and each Biome run leaves the whole repo flagged as
4
+ # modified with an empty diff.
5
+ * text=auto eol=lf
6
+
7
+ # Binary assets - never convert or diff their bytes
8
+ *.xlsx binary
9
+ *.jasper binary
10
+ *.pdf binary
11
+ *.png binary
12
+ *.jpg binary
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "__PACKAGE_NAME__",
3
+ "engines": {
4
+ "node": "^22.0.0 || ^24.0.0 || >=26.0.0"
5
+ },
3
6
  "dependencies": {
4
7
  "@globisnv/cypress-core": "^__CORE_VERSION__",
5
- "cypress-real-events": "^1.15.0",
8
+ "cypress-real-events": "^1.15.1",
6
9
  "typescript": "^7.0.2"
7
10
  },
8
11
  "devDependencies": {
9
12
  "@biomejs/biome": "^2.5.7",
10
13
  "@types/node": "^26.1.2",
11
- "cypress": "^15.20.1"
14
+ "cypress": "^16.0.0"
12
15
  },
13
16
  "scripts": {
14
17
  "lint": "biome check . && node node_modules/@globisnv/cypress-core/scripts/check-unnecessary-waiting.mjs",