@globisnv/cypress-core 0.0.9 → 1.0.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 (37) hide show
  1. package/biome.json +11 -0
  2. package/dist/blueprints/GLO_dangerousGoods.d.ts +10 -0
  3. package/dist/blueprints/GLO_dangerousGoods.js +9 -0
  4. package/dist/blueprints/GLO_logisticContract.d.ts +14 -0
  5. package/dist/blueprints/GLO_logisticContract.js +53 -11
  6. package/dist/blueprints/GLO_logisticContractDetail.d.ts +11 -1
  7. package/dist/blueprints/GLO_logisticContractDetail.js +33 -8
  8. package/dist/blueprints/GLO_logisticContractDetailHandling.d.ts +1 -0
  9. package/dist/blueprints/GLO_logisticContractDetailHandling.js +1 -0
  10. package/dist/blueprints/GLO_logisticContractDetailItem.js +1 -1
  11. package/dist/blueprints/GLO_logisticContractItem.d.ts +9 -0
  12. package/dist/blueprints/GLO_logisticContractItem.js +31 -4
  13. package/dist/blueprints/GLO_logisticLocationType.d.ts +4 -0
  14. package/dist/blueprints/GLO_logisticLocationType.js +4 -0
  15. package/dist/blueprints/GLO_planBloc.d.ts +6 -0
  16. package/dist/blueprints/GLO_planBloc.js +6 -0
  17. package/dist/blueprints/GLO_priceEntry.d.ts +1 -0
  18. package/dist/blueprints/GLO_priceEntry.js +1 -0
  19. package/dist/blueprints/GLO_purchaseOrderLine.js +4 -0
  20. package/dist/blueprints/GLO_stopSchedule.d.ts +11 -0
  21. package/dist/blueprints/GLO_stopSchedule.js +11 -0
  22. package/dist/blueprints/GLO_transportScenario.d.ts +4 -0
  23. package/dist/blueprints/GLO_transportScenario.js +4 -0
  24. package/dist/blueprints/GLO_transportScenarioExecution.d.ts +6 -0
  25. package/dist/blueprints/GLO_transportScenarioExecution.js +5 -0
  26. package/dist/blueprints/GLO_transportScenarioRegion.d.ts +5 -0
  27. package/dist/blueprints/GLO_transportScenarioRegion.js +5 -0
  28. package/dist/blueprints/GLO_zoneTypeEntityType.d.ts +4 -0
  29. package/dist/blueprints/GLO_zoneTypeEntityType.js +4 -0
  30. package/dist/commands/GLO_commands.js +54 -22
  31. package/dist/config.js +4 -2
  32. package/dist/tsHelper.d.ts +4 -2
  33. package/dist/tsHelper.js +7 -6
  34. package/dist/websocketDetector.js +148 -114
  35. package/package.json +9 -6
  36. package/template/.gitattributes +12 -0
  37. package/template/package.json +5 -2
package/biome.json CHANGED
@@ -1,5 +1,10 @@
1
1
  {
2
2
  "$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
3
8
  "files": {
4
9
  "includes": ["**", "!dist", "!template"]
5
10
  },
@@ -24,6 +29,12 @@
24
29
  "complexity": {
25
30
  "noUselessTernary": "error"
26
31
  },
32
+ "correctness": {
33
+ "noUnusedImports": {
34
+ "level": "error",
35
+ "fix": "safe"
36
+ }
37
+ },
27
38
  "style": {
28
39
  "useTemplate": "error",
29
40
  "noNonNullAssertion": "off",
@@ -0,0 +1,10 @@
1
+ import type { CheckState } from '../tsHelper.js';
2
+ export type objDangerousGoods = {
3
+ unNumber?: string;
4
+ flagAdr?: CheckState;
5
+ flagImdg?: CheckState;
6
+ flagRid?: CheckState;
7
+ flagAdnr?: CheckState;
8
+ flagLimitedQuantity?: CheckState;
9
+ };
10
+ export declare const GLO_enterDangerousGoods: (obj: objDangerousGoods) => void;
@@ -0,0 +1,9 @@
1
+ export const GLO_enterDangerousGoods = (obj) => {
2
+ const formName = 'ng$dgn_detail_dtl';
3
+ cy.GLO_popupWindow([formName, 'dgn_un_number'], obj.unNumber);
4
+ cy.GLO_checkbox([formName, 'flag_adr'], obj.flagAdr);
5
+ cy.GLO_checkbox([formName, 'flag_imdg'], obj.flagImdg);
6
+ cy.GLO_checkbox([formName, 'flag_rid'], obj.flagRid);
7
+ cy.GLO_checkbox([formName, 'flag_adnr'], obj.flagAdnr);
8
+ cy.GLO_checkbox([formName, 'flag_limited_qty'], obj.flagLimitedQuantity);
9
+ };
@@ -7,6 +7,7 @@ export type objLogisticContract = {
7
7
  validTill?: string;
8
8
  description?: string;
9
9
  supplierCode?: string;
10
+ orderByRelation?: string;
10
11
  keyAccountManager?: string;
11
12
  accountManager?: string;
12
13
  currency?: string;
@@ -24,5 +25,18 @@ export type objLogisticContract = {
24
25
  dayForAutomaticInvoicing?: number;
25
26
  flagDefWaitingHoursProofRequired?: CheckState;
26
27
  defWaitingHoursThreshold?: string;
28
+ flagPreBilling?: CheckState;
29
+ flagSummaryInvoiceCost?: CheckState;
30
+ flagAutomatedInvoiceCost?: CheckState;
31
+ invoicePeriodicity?: string;
32
+ invoicePeriodicityQuantity?: number;
33
+ dayOfMonthOfInvoice?: number;
34
+ transactionDateType?: string;
35
+ invoiceRelation?: string;
36
+ invoicePeriodicityTurnover?: string;
37
+ invoicePeriodicityQuantityTurnover?: number;
38
+ dayOfMonthOfInvoiceTurnover?: number;
39
+ transactionDateTypeTurnover?: string;
40
+ invoiceRelationTurnover?: string;
27
41
  };
28
42
  export declare const GLO_enterLogisticContract: (obj: objLogisticContract) => void;
@@ -24,7 +24,7 @@ export const GLO_enterLogisticContract = (obj) => {
24
24
  cy.GLO_checkLoadingIndicator();
25
25
  }
26
26
  // General Tab
27
- if (obj.supplierCode) {
27
+ if (obj.supplierCode || obj.orderByRelation) {
28
28
  cy.GLO_selectTab([formName, tabs], 'General', open);
29
29
  generalTab(obj);
30
30
  cy.GLO_selectTab([formName, tabs], 'General', close);
@@ -35,18 +35,41 @@ export const GLO_enterLogisticContract = (obj) => {
35
35
  pricingTab(obj);
36
36
  cy.GLO_selectTab([formName, tabs], 'Pricing', close);
37
37
  }
38
- // User role tab
39
- if (obj.keyAccountManager || obj.accountManager) {
40
- cy.GLO_selectTab([formName, tabs], 'User role', open);
41
- userRoleTab(obj);
42
- cy.GLO_selectTab([formName, tabs], 'User role', close);
38
+ // Billing (cost)
39
+ if (obj.flagPreBilling ||
40
+ obj.flagSummaryInvoiceCost ||
41
+ obj.flagAutomatedInvoiceCost ||
42
+ obj.invoicePeriodicity ||
43
+ obj.invoicePeriodicityQuantity ||
44
+ obj.dayOfMonthOfInvoice ||
45
+ obj.transactionDateType ||
46
+ obj.invoiceRelation) {
47
+ cy.GLO_selectTab([formName, tabs], 'Billing (cost)', open);
48
+ billingCostTab(obj);
49
+ cy.GLO_selectTab([formName, tabs], 'Billing (cost)', close);
43
50
  }
44
51
  // Billing (turnover)
45
- if (obj.flagAutomatedInvoice || obj.flagDirectInvoice || obj.flagElectronicInvoice || obj.flagMergeInvoiceAndPOD || obj.flagSummaryInvoice || obj.dayForAutomaticInvoicing) {
52
+ if (obj.flagAutomatedInvoice ||
53
+ obj.flagDirectInvoice ||
54
+ obj.flagElectronicInvoice ||
55
+ obj.flagMergeInvoiceAndPOD ||
56
+ obj.flagSummaryInvoice ||
57
+ obj.dayForAutomaticInvoicing ||
58
+ obj.invoicePeriodicityTurnover ||
59
+ obj.invoicePeriodicityQuantityTurnover ||
60
+ obj.dayOfMonthOfInvoiceTurnover ||
61
+ obj.transactionDateTypeTurnover ||
62
+ obj.invoiceRelationTurnover) {
46
63
  cy.GLO_selectTab([formName, 'tabs_70'], 'Billing (turnover)', open);
47
64
  billingTurnoverTab(obj);
48
65
  cy.GLO_selectTab([formName, tabs], 'Billing (turnover)', close);
49
66
  }
67
+ // User role tab
68
+ if (obj.keyAccountManager || obj.accountManager) {
69
+ cy.GLO_selectTab([formName, tabs], 'User role', open);
70
+ userRoleTab(obj);
71
+ cy.GLO_selectTab([formName, tabs], 'User role', close);
72
+ }
50
73
  };
51
74
  // Billing tab
52
75
  const billingTurnoverTab = (obj) => {
@@ -57,6 +80,23 @@ const billingTurnoverTab = (obj) => {
57
80
  cy.GLO_checkbox([formNameBilingTurnover, 'flag_electronic_invoice'], obj.flagElectronicInvoice);
58
81
  cy.GLO_checkbox([formNameBilingTurnover, 'flag_summary_invoice'], obj.flagSummaryInvoice);
59
82
  cy.GLO_addInput([formNameBilingTurnover, 'def_automated_invoice_days'], obj.dayForAutomaticInvoicing);
83
+ cy.GLO_addInput([formNameBilingTurnover, 'def_invoice_periodicity'], obj.invoicePeriodicityTurnover);
84
+ cy.GLO_addInput([formNameBilingTurnover, 'def_invoice_periodicity_quantity'], obj.invoicePeriodicityQuantityTurnover);
85
+ cy.GLO_addInput([formNameBilingTurnover, 'day_of_month_of_invoice'], obj.dayOfMonthOfInvoiceTurnover);
86
+ cy.GLO_addInput([formNameBilingTurnover, 'transaction_date_type'], obj.transactionDateTypeTurnover);
87
+ cy.GLO_popupWindow([formNameBilingTurnover, 'invoice_relation_code'], obj.invoiceRelationTurnover);
88
+ };
89
+ // Billing (cost) tab
90
+ const billingCostTab = (obj) => {
91
+ const formNameBillingCost = 'ng$logistic_contract_dtl_invoicing_cost';
92
+ cy.GLO_checkbox([formNameBillingCost, 'flag_prebilling'], obj.flagPreBilling);
93
+ cy.GLO_checkbox([formNameBillingCost, 'flag_summary_invoice'], obj.flagSummaryInvoiceCost);
94
+ cy.GLO_checkbox([formNameBillingCost, 'flag_automated_invoice'], obj.flagAutomatedInvoiceCost);
95
+ cy.GLO_addInput([formNameBillingCost, 'def_invoice_periodicity'], obj.invoicePeriodicity);
96
+ cy.GLO_addInput([formNameBillingCost, 'def_invoice_periodicity_quantity'], obj.invoicePeriodicityQuantity);
97
+ cy.GLO_addInput([formNameBillingCost, 'day_of_month_of_invoice'], obj.dayOfMonthOfInvoice);
98
+ cy.GLO_addInput([formNameBillingCost, 'transaction_date_type'], obj.transactionDateType);
99
+ cy.GLO_popupWindow([formNameBillingCost, 'invoice_relation_code'], obj.invoiceRelation);
60
100
  };
61
101
  // User role tab
62
102
  const userRoleTab = (obj) => {
@@ -67,10 +107,12 @@ const userRoleTab = (obj) => {
67
107
  // General tab
68
108
  const generalTab = (obj) => {
69
109
  const formNameGeneral = 'ng$logistic_contract_dtl_general';
70
- if (obj.supplierCode) {
71
- cy.GLO_popupWindow([formNameGeneral, 'relation_code'], obj.supplierCode);
72
- cy.GLO_checkLoadingIndicator();
73
- }
110
+ cy.GLO_popupWindow([formNameGeneral, 'relation_code'], obj.supplierCode);
111
+ if (obj.supplierCode)
112
+ cy.GLO_checkField([formNameGeneral, 'relation_code'], obj.supplierCode);
113
+ cy.GLO_popupWindow([formNameGeneral, 'local_order_by_relation_code'], obj.orderByRelation);
114
+ if (obj.orderByRelation)
115
+ cy.GLO_checkField([formNameGeneral, 'local_order_by_relation_code'], obj.orderByRelation);
74
116
  };
75
117
  // Pricing tab
76
118
  const pricingTab = (obj) => {
@@ -2,6 +2,8 @@ import { type CheckState } from '../tsHelper.js';
2
2
  import { type objTclSettings } from './GLO_tclSetting.js';
3
3
  export type objLogisticContractDetail = {
4
4
  detailType?: string;
5
+ validFrom?: string;
6
+ validTill?: string;
5
7
  from?: string | JQuery;
6
8
  to?: string | JQuery;
7
9
  fromCountry?: string;
@@ -34,9 +36,17 @@ export type objLogisticContractDetail = {
34
36
  tcl?: CheckState;
35
37
  theftSensitive?: CheckState;
36
38
  flagLoadSecuringEquipment?: CheckState;
37
- tclSettings?: [objTclSettings];
39
+ flagSelfLoadByDriver?: CheckState;
40
+ tclSettings?: objTclSettings[];
38
41
  collectionSupplier?: string;
39
42
  equipmentCategory?: string;
40
43
  article?: string;
44
+ csdResponsibleLoad?: string;
45
+ csdResponsibleUnload?: string;
46
+ tclVentilation?: number;
47
+ tclSetpoint?: number;
48
+ tclMinTemperature?: number;
49
+ tclMaxTemperature?: number;
50
+ tclSeasonal?: string;
41
51
  };
42
52
  export declare const GLO_enterLogisticContractDetail: (obj: objLogisticContractDetail) => void;
@@ -1,4 +1,4 @@
1
- import { check, close, open } from '../tsHelper.js';
1
+ import { check, close, input, open } from '../tsHelper.js';
2
2
  import { GLO_enterTclSettings } from './GLO_tclSetting.js';
3
3
  const formName = 'ng$logistic_contract_detail_dtl';
4
4
  const formNameAddress = 'ng$logistic_contract_detail_dtl_address';
@@ -6,9 +6,13 @@ const formNameBilling = 'ng$logistic_contract_detail_dtl_invoicing';
6
6
  const formNameOperational = 'ng$logistic_contract_detail_dtl_operational';
7
7
  const formNameLaneConditions = 'ng$logistic_contract_detail_dtl_lane_conditions';
8
8
  const formNamePricing = 'ng$logistic_contract_detail_dtl_billing';
9
+ const formNameUserRole = 'ng$logistic_contract_detail_dtl_user_role';
10
+ const formNameTclSettings = 'ng$logistic_contract_detail_dtl_tcl_settings';
9
11
  const tabs = 'tabs_70';
10
12
  export const GLO_enterLogisticContractDetail = (obj) => {
11
13
  cy.GLO_addInput([formName, 'logistic_contract_detail_type'], obj.detailType);
14
+ cy.GLO_addDate(input, [formName, 'valid_from'], obj.validFrom);
15
+ cy.GLO_addDate(input, [formName, 'valid_till'], obj.validTill);
12
16
  cy.GLO_popupWindow([formName, 'local_logistic_conn_code'], obj.logisticConnection);
13
17
  cy.GLO_addInput([formName, 'price_rule_logistic_contract_detail_id'], obj.priceRule);
14
18
  cy.GLO_addInput([formName, 'sec_branch_id'], obj.branch);
@@ -26,14 +30,16 @@ export const GLO_enterLogisticContractDetail = (obj) => {
26
30
  GLO_enterLogisticContractDetailAddressData(obj);
27
31
  // Tab Address
28
32
  GLO_enterLogisticContractDetailAddresses(obj);
29
- // Tab billing
30
- GLO_enterLogisticContractDetailBilling(obj);
31
33
  // Tab Operational
32
34
  GLO_enterLogisticContractDetailOperational(obj);
33
35
  // Tab Pricing
34
36
  GLO_enterLogisticContractDetailPricing(obj);
37
+ // Tab billing
38
+ GLO_enterLogisticContractDetailBilling(obj);
35
39
  // Tab Lane conditions
36
40
  GLO_enterLogisticContractDetailLaneCondition(obj);
41
+ // Tab User role
42
+ GLO_enterLogisticContractDetailUserRole(obj);
37
43
  // Tab TCL settings
38
44
  GLO_enterTclSettingsTab(obj);
39
45
  };
@@ -75,15 +81,25 @@ const GLO_enterLogisticContractDetailBilling = (obj) => {
75
81
  }
76
82
  };
77
83
  const GLO_enterLogisticContractDetailOperational = (obj) => {
78
- if (obj.leadTime || obj.leadTimePrefReq || obj.collectionSupplier || obj.flagLoadSecuringEquipment) {
84
+ if (obj.leadTime || obj.leadTimePrefReq || obj.collectionSupplier || obj.flagLoadSecuringEquipment || obj.flagSelfLoadByDriver) {
79
85
  cy.GLO_selectTab([formName, tabs], 'Operational', open);
80
86
  cy.GLO_addInput([formNameOperational, 'leadtime_id'], obj.leadTime);
81
87
  cy.GLO_addInput([formNameOperational, 'lead_time_pref_req'], obj.leadTimePrefReq);
82
88
  cy.GLO_popupWindow([formNameOperational, 'collection_supplier_id'], obj.collectionSupplier);
83
89
  cy.GLO_checkbox([formNameOperational, 'flag_load_securing_equipment'], obj.flagLoadSecuringEquipment);
90
+ cy.GLO_checkbox([formNameOperational, 'flag_self_load_by_driver'], obj.flagSelfLoadByDriver);
84
91
  cy.GLO_selectTab([formName, tabs], 'Operational', close);
85
92
  }
86
93
  };
94
+ // User role tab
95
+ const GLO_enterLogisticContractDetailUserRole = (obj) => {
96
+ if (obj.csdResponsibleLoad || obj.csdResponsibleUnload) {
97
+ cy.GLO_selectTab([formName, tabs], 'User role', open);
98
+ cy.GLO_addInput([formNameUserRole, 'CSD_responsible_load'], obj.csdResponsibleLoad);
99
+ cy.GLO_addInput([formNameUserRole, 'CSD_responsible_unload'], obj.csdResponsibleUnload);
100
+ cy.GLO_selectTab([formName, tabs], 'User role', close);
101
+ }
102
+ };
87
103
  const GLO_enterLogisticContractDetailPricing = (obj) => {
88
104
  if (obj.group1 || obj.priceBreakUnit || obj.priceBreakOptimization) {
89
105
  cy.GLO_selectTab([formName, tabs], 'Pricing', open);
@@ -108,12 +124,21 @@ const GLO_enterLogisticContractDetailLaneCondition = (obj) => {
108
124
  }
109
125
  };
110
126
  const GLO_enterTclSettingsTab = (obj) => {
111
- const formNameTclSettingTab = 'ng$logistic_contract_detail_dtl_tcl_settings';
112
127
  const formNameTclSettingDlg = 'ng$logistic_contract_detail_transport_method_tcl_dlg';
113
- if (obj.tclSettings) {
128
+ if (obj.tclSettings ||
129
+ obj.tclVentilation !== undefined ||
130
+ obj.tclSetpoint !== undefined ||
131
+ obj.tclMinTemperature !== undefined ||
132
+ obj.tclMaxTemperature !== undefined ||
133
+ obj.tclSeasonal) {
114
134
  cy.GLO_selectTab([formName, tabs], 'TCL settings', open);
115
- obj.tclSettings.forEach((tclSetting) => {
116
- cy.GLO_clickButton([formNameTclSettingTab, 'btn_add'], 'fa-plus');
135
+ cy.GLO_addInput([formNameTclSettings, 'tcl_ventilation'], obj.tclVentilation);
136
+ cy.GLO_addInput([formNameTclSettings, 'tcl_setpoint'], obj.tclSetpoint);
137
+ cy.GLO_addInput([formNameTclSettings, 'tcl_min_temperature'], obj.tclMinTemperature);
138
+ cy.GLO_addInput([formNameTclSettings, 'tcl_max_temperature'], obj.tclMaxTemperature);
139
+ cy.GLO_addInput([formNameTclSettings, 'tcl_seasonal'], obj.tclSeasonal);
140
+ obj.tclSettings?.forEach((tclSetting) => {
141
+ cy.GLO_clickButton([formNameTclSettings, 'btn_add'], 'fa-plus');
117
142
  GLO_enterTclSettings(tclSetting);
118
143
  cy.GLO_clickButton([formNameTclSettingDlg, 'btn_ok']);
119
144
  });
@@ -5,6 +5,7 @@ export type objLogisticContractDetailHandling = {
5
5
  countryCode?: string;
6
6
  singleAddress?: objAddressPopup;
7
7
  timeActivity?: string;
8
+ bookingType?: string;
8
9
  flagCollection?: CheckState;
9
10
  flagDelivery?: CheckState;
10
11
  };
@@ -5,6 +5,7 @@ export const GLO_enterLogisticContractDetailHandling = (obj) => {
5
5
  cy.GLO_addInput([formName, 'time_activity_id'], obj.timeActivity);
6
6
  cy.GLO_checkbox([formNameAddress, 'flag_collection'], obj.flagCollection);
7
7
  cy.GLO_checkbox([formNameAddress, 'flag_delivery'], obj.flagDelivery);
8
+ cy.GLO_addInput([formNameAddress, 'booking_type'], obj.bookingType);
8
9
  cy.GLO_radioListCData([formNameAddress, 'address_data_type'], obj.addressDataType);
9
10
  cy.GLO_addInput([formNameAddress, 'country_code'], obj.countryCode);
10
11
  if (obj.singleAddress) {
@@ -1,8 +1,8 @@
1
1
  export const GLO_enterLogisticContractDetailItem = (obj) => {
2
2
  const formName = 'ng$logistic_contract_detail_item_dtl';
3
- cy.GLO_popupWindow([formName, 'logistic_contract_item_id'], obj.logisticContractItemCode);
4
3
  cy.GLO_addInput([formName, 'invoice_relation_id'], obj.invoiceRelation);
5
4
  if (obj.invoiceRelation) {
6
5
  cy.GLO_checkLoadingIndicator();
7
6
  }
7
+ cy.GLO_popupWindow([formName, 'logistic_contract_item_id'], obj.logisticContractItemCode);
8
8
  };
@@ -5,6 +5,15 @@ export type objLogisticContractItem = {
5
5
  department?: string;
6
6
  businessUnit?: string;
7
7
  flagProformaInvoice?: CheckState;
8
+ flagDirectInvoice?: CheckState;
9
+ flagSummaryInvoice?: CheckState;
10
+ flagAutomatedInvoice?: CheckState;
11
+ automatedInvoiceDays?: number;
12
+ invoicePeriodicity?: string;
13
+ invoicePeriodicityQuantity?: number;
14
+ dayOfMonthOfInvoice?: number;
15
+ transactionDateType?: string;
16
+ invoiceGroup?: string;
8
17
  priceRule?: string;
9
18
  invoiceRelation?: string;
10
19
  priceLogisticContract?: string;
@@ -1,4 +1,4 @@
1
- import { open } from '../tsHelper.js';
1
+ import { close, open } from '../tsHelper.js';
2
2
  export const GLO_enterLogisticContractItem = (obj) => {
3
3
  const formName = 'ng$logistic_contract_item_dtl';
4
4
  const generalFormName = 'ng$logistic_contract_item_dtl_general';
@@ -33,8 +33,35 @@ export const GLO_enterLogisticContractItem = (obj) => {
33
33
  cy.GLO_checkLoadingIndicator();
34
34
  }
35
35
  }
36
- if (obj.flagProformaInvoice) {
37
- cy.GLO_selectTab([formName, 'tabs_70'], 'Billing', open);
38
- cy.GLO_checkbox(['ng$logistic_contract_item_dtl_invoicing', 'flag_proforma_invoice'], obj.flagProformaInvoice);
36
+ // Tab Billing
37
+ enterBillingTab(obj);
38
+ };
39
+ // Billing tab
40
+ const enterBillingTab = (obj) => {
41
+ const formName = 'ng$logistic_contract_item_dtl';
42
+ const invoicingFormName = 'ng$logistic_contract_item_dtl_invoicing';
43
+ if (obj.flagProformaInvoice === undefined &&
44
+ obj.flagDirectInvoice === undefined &&
45
+ obj.flagSummaryInvoice === undefined &&
46
+ obj.flagAutomatedInvoice === undefined &&
47
+ obj.automatedInvoiceDays === undefined &&
48
+ !obj.invoicePeriodicity &&
49
+ !obj.invoicePeriodicityQuantity &&
50
+ !obj.dayOfMonthOfInvoice &&
51
+ !obj.transactionDateType &&
52
+ !obj.invoiceGroup) {
53
+ return;
39
54
  }
55
+ cy.GLO_selectTab([formName, 'tabs_70'], 'Billing', open);
56
+ cy.GLO_checkbox([invoicingFormName, 'flag_proforma_invoice'], obj.flagProformaInvoice);
57
+ cy.GLO_checkbox([invoicingFormName, 'flag_summary_invoice'], obj.flagSummaryInvoice);
58
+ cy.GLO_checkbox([invoicingFormName, 'flag_direct_invoice'], obj.flagDirectInvoice);
59
+ cy.GLO_checkbox([invoicingFormName, 'flag_automated_invoice'], obj.flagAutomatedInvoice);
60
+ cy.GLO_addInput([invoicingFormName, 'automated_invoice_days'], obj.automatedInvoiceDays);
61
+ cy.GLO_addInput([invoicingFormName, 'invoice_periodicity'], obj.invoicePeriodicity);
62
+ cy.GLO_addInput([invoicingFormName, 'invoice_periodicity_quantity'], obj.invoicePeriodicityQuantity);
63
+ cy.GLO_addInput([invoicingFormName, 'day_of_month_of_invoice'], obj.dayOfMonthOfInvoice);
64
+ cy.GLO_addInput([invoicingFormName, 'transaction_date_type'], obj.transactionDateType);
65
+ cy.GLO_addInput([invoicingFormName, 'invoice_group'], obj.invoiceGroup);
66
+ cy.GLO_selectTab([formName, 'tabs_70'], 'Billing', close);
40
67
  };
@@ -0,0 +1,4 @@
1
+ export type objLogisticLocationType = {
2
+ logisticLocationTypeCode?: string;
3
+ };
4
+ export declare const GLO_enterLogisticLocationType: (obj: objLogisticLocationType) => void;
@@ -0,0 +1,4 @@
1
+ export const GLO_enterLogisticLocationType = (obj) => {
2
+ const formName = 'ng$logistic_location_type_dtl';
3
+ cy.GLO_addInput([formName, 'logistic_location_type_code'], obj.logisticLocationTypeCode);
4
+ };
@@ -0,0 +1,6 @@
1
+ export type objPlanBloc = {
2
+ planBlocCode?: string;
3
+ planBlocName?: string;
4
+ company?: string;
5
+ };
6
+ export declare const GLO_enterPlanBloc: (obj: objPlanBloc) => void;
@@ -0,0 +1,6 @@
1
+ export const GLO_enterPlanBloc = (obj) => {
2
+ const formName = 'ng$plan_bloc_dtl';
3
+ cy.GLO_addInput([formName, 'plan_bloc_code'], obj.planBlocCode);
4
+ cy.GLO_addInput([formName, 'plan_bloc_name'], obj.planBlocName);
5
+ cy.GLO_addInput([formName, 'sec_company_id'], obj.company);
6
+ };
@@ -4,6 +4,7 @@ export type objPriceEntry = {
4
4
  validTill?: string;
5
5
  currency?: string;
6
6
  result?: number;
7
+ resultPer?: number;
7
8
  perType?: string;
8
9
  priceAmount?: number;
9
10
  pricePerUnit?: string;
@@ -5,6 +5,7 @@ export const GLO_enterPriceEntry = (obj) => {
5
5
  cy.GLO_addDate(input, [formName, 'valid_till'], obj.validTill);
6
6
  cy.GLO_addInput([formName, 'currency_id'], obj.currency);
7
7
  cy.GLO_addInput([formName, 'result_value'], obj.result);
8
+ cy.GLO_addInput([formName, 'result_value_per'], obj.resultPer);
8
9
  cy.GLO_addInput(['result_value_per_unit_id'], obj.perType);
9
10
  cy.GLO_addInput([formName, 'min_amount'], obj.minAmount);
10
11
  cy.GLO_addInput([formName, 'max_amount'], obj.maxAmount);
@@ -9,7 +9,11 @@ export const GLO_enterPurchaseOrderLine = (obj) => {
9
9
  if (obj.quantity || obj.generalPrice || obj.inputQtyPerUnitId) {
10
10
  cy.GLO_selectTab([formName, detailTabsName], 'General', open);
11
11
  cy.GLO_addInput([`${formName}_general`, 'input_qty_per_unit_id'], obj.inputQtyPerUnitId);
12
+ if (obj.inputQtyPerUnitId)
13
+ cy.GLO_checkLoadingIndicator();
12
14
  cy.GLO_addInput([`${formName}_general`, 'input_qty'], obj.quantity);
15
+ if (obj.quantity)
16
+ cy.GLO_checkLoadingIndicator();
13
17
  cy.GLO_addInput([`${formName}_general`, 'quantity'], obj.quantityBase);
14
18
  cy.GLO_checkField([`${formName}_general`, 'quantity'], obj.quantityBase);
15
19
  cy.GLO_addInput([`${formName}_general`, 'price'], obj.generalPrice);
@@ -1,4 +1,15 @@
1
+ export type objStopScheduleDay = {
2
+ /** 1 = Monday, 7 = Sunday. */
3
+ day: number;
4
+ amFrom?: string;
5
+ amTill?: string;
6
+ pmFrom?: string;
7
+ pmTill?: string;
8
+ };
1
9
  export type objStopSchedule = {
2
10
  code?: string;
11
+ every?: number;
12
+ unit?: string;
13
+ days?: objStopScheduleDay[];
3
14
  };
4
15
  export declare const GLO_enterStopSchedule: (obj: objStopSchedule) => void;
@@ -1,4 +1,15 @@
1
+ import { check } from '../tsHelper.js';
1
2
  export const GLO_enterStopSchedule = (obj) => {
2
3
  const formName = 'ng$delivery_schedule$logistic_contract_detail_stop_dtl';
3
4
  cy.GLO_addInput([formName, 'schedule_code'], obj.code);
5
+ cy.GLO_addInput([formName, 'every'], obj.every);
6
+ cy.GLO_addInput([formName, 'unit'], obj.unit);
7
+ // The times are named per weekday, the checkbox drops the flag prefix
8
+ obj.days?.forEach((day) => {
9
+ cy.GLO_checkbox([formName, `day${day.day}`], check);
10
+ cy.GLO_addInput([formName, `amfrom${day.day}`], day.amFrom);
11
+ cy.GLO_addInput([formName, `amto${day.day}`], day.amTill);
12
+ cy.GLO_addInput([formName, `pmfrom${day.day}`], day.pmFrom);
13
+ cy.GLO_addInput([formName, `pmto${day.day}`], day.pmTill);
14
+ });
4
15
  };
@@ -0,0 +1,4 @@
1
+ export type objTransportScenario = {
2
+ transportScenarioCode?: string;
3
+ };
4
+ export declare const GLO_enterTransportScenario: (obj: objTransportScenario) => void;
@@ -0,0 +1,4 @@
1
+ export const GLO_enterTransportScenario = (obj) => {
2
+ const formName = 'ng$tpt_scenario_dtl';
3
+ cy.GLO_addInput([formName, 'tpt_scenario_code'], obj.transportScenarioCode);
4
+ };
@@ -0,0 +1,6 @@
1
+ import type { CheckState } from '../tsHelper.js';
2
+ export type objTransportScenarioExecution = {
3
+ isActive?: CheckState;
4
+ description?: string;
5
+ };
6
+ export declare const GLO_enterTransportScenarioExecution: (obj: objTransportScenarioExecution) => void;
@@ -0,0 +1,5 @@
1
+ export const GLO_enterTransportScenarioExecution = (obj) => {
2
+ const formName = 'ng$tpt_scenario_execution_dtl';
3
+ cy.GLO_checkbox([formName, 'is_active'], obj.isActive);
4
+ cy.GLO_addInput([formName, 'description'], obj.description);
5
+ };
@@ -0,0 +1,5 @@
1
+ export type objTransportScenarioRegion = {
2
+ fromCountry?: string;
3
+ toCountry?: string;
4
+ };
5
+ export declare const GLO_enterTransportScenarioRegion: (obj: objTransportScenarioRegion) => void;
@@ -0,0 +1,5 @@
1
+ export const GLO_enterTransportScenarioRegion = (obj) => {
2
+ const formName = 'ng$tpt_scenario_region_region_dtl';
3
+ cy.GLO_addInput([formName, 'from_country_id'], obj.fromCountry);
4
+ cy.GLO_addInput([formName, 'to_country_id'], obj.toCountry);
5
+ };
@@ -0,0 +1,4 @@
1
+ export type objZoneTypeEntityType = {
2
+ entityType?: string;
3
+ };
4
+ export declare const GLO_enterZoneTypeEntityType: (obj: objZoneTypeEntityType) => void;
@@ -0,0 +1,4 @@
1
+ export const GLO_enterZoneTypeEntityType = (obj) => {
2
+ const formName = 'ng$zone_type_entity_type_dtl';
3
+ cy.GLO_addInput([formName, 'entity_type'], obj.entityType);
4
+ };
@@ -33,13 +33,13 @@ const isGridRowSelector = (baseSelector, index) => {
33
33
  // rendered-but-scrolled-past rows as hidden (measured: 14 of 21 rendered rows drop out of `:visible`
34
34
  // after scrolling a grid to the bottom). Filter on the container, which is what both were after.
35
35
  const isInOpenContainer = (_, el) => !el.closest('[hidden]');
36
- const pickByRowIndex = (chain, index, log = false) => {
36
+ const pickByRowIndex = (chain, index, log = false, timeout = defaultTimeout) => {
37
37
  if (log)
38
38
  Cypress.log({ name: 'idx', message: String(index) });
39
39
  return chain.filter((i, el) => {
40
40
  const rowIndexEl = el.closest('[row-index]');
41
41
  return rowIndexEl ? rowIndexEl.getAttribute('row-index') === String(index) : i === index;
42
- });
42
+ }, { timeout });
43
43
  };
44
44
  Cypress.Commands.add('GLO_getElement', (selectorArg, options = {}) => {
45
45
  const arr = selectorArg;
@@ -60,12 +60,12 @@ Cypress.Commands.add('GLO_getElement', (selectorArg, options = {}) => {
60
60
  const elementInCrumb = Cypress.$(`div[formslist] ${selector}`).length > 0;
61
61
  if (elementInCrumb) {
62
62
  const result = cy.get(`div[formslist]:not([hidden]) ${selector}`, getOpts);
63
- return pickByRowIndex(result, index ?? 0, hasIndex);
63
+ return pickByRowIndex(result, index ?? 0, hasIndex, getOpts.timeout);
64
64
  }
65
65
  }
66
66
  const result = cy.get(selector, getOpts);
67
67
  // Ponytail: sync DOM check — if element absent, skip filter so cy.get + should('not.exist') chain works
68
- return hasIndex || Cypress.$(selector).length > 0 ? pickByRowIndex(result, index ?? 0, hasIndex) : result;
68
+ return hasIndex || Cypress.$(selector).length > 0 ? pickByRowIndex(result, index ?? 0, hasIndex, getOpts.timeout) : result;
69
69
  });
70
70
  Cypress.Commands.add('GLO_getVisible', (elementName, timeOut = defaultTimeout) => {
71
71
  return cy.get(elementName, { timeout: timeOut }).filter(':visible', { timeout: timeOut });
@@ -136,7 +136,7 @@ const fillInUserAndPasswordDefault = (usernameENV, passwordENV) => {
136
136
  .type(Cypress.expose(usernameENV));
137
137
  cy.env([passwordENV]).then((envVars) => {
138
138
  cy.GLO_getElement(['ng2$svy_nav_login', 'txtPass'])
139
- .find('input')
139
+ .find('input:visible', { timeout: 30000 })
140
140
  .eq(0)
141
141
  .clear()
142
142
  .type(envVars[passwordENV], { log: false });
@@ -227,10 +227,21 @@ Cypress.Commands.add('GLO_oktaLogin', (company, urlType, skipvNavCheck) => {
227
227
  cy.task('log', 'End navbar check');
228
228
  });
229
229
  Cypress.Commands.add('GLO_logout', () => {
230
- cy.GLO_getElement(['ng$main_form2', 'navbar_top']).find('.lucide-LogOut').click();
231
- cy.GLO_getVisible('.window-footer button.svy-btn-primary', 30000)
232
- .contains(/^(Yes|Ja)$/)
233
- .click();
230
+ cy.document({ log: false }).then((doc) => {
231
+ cy.GLO_getElement(['ng$main_form2', 'navbar_top']).find('.lucide-LogOut').click();
232
+ cy.GLO_getVisible('.window-footer button.svy-btn-primary', 30000)
233
+ .contains(/^(Yes|Ja)$/)
234
+ .click();
235
+ // The confirm click triggers the app's own client-side redirect (not a cy.visit); a full
236
+ // page reload swaps the Document object as soon as navigation starts, so also wait for
237
+ // readyState to reach 'complete' — otherwise this resolves the instant the reload begins,
238
+ // not once it's actually finished. Checking readyState (not the destination URL) survives
239
+ // moving to a stateless auth flow with no login screen.
240
+ cy.document({ log: false, timeout: 30000 }).should((newDoc) => {
241
+ expect(newDoc).not.to.equal(doc);
242
+ expect(newDoc.readyState).to.equal('complete');
243
+ });
244
+ });
234
245
  });
235
246
  Cypress.Commands.add('GLO_servoyLogin', (company) => {
236
247
  if (cypressWebsocketLoader) {
@@ -241,13 +252,13 @@ Cypress.Commands.add('GLO_servoyLogin', (company) => {
241
252
  }
242
253
  cy.GLO_checkLoadingIndicator();
243
254
  cy.GLO_getElement(['ng2$svy_nav_login', 'txtUser'], { timeout: 30000 })
244
- .find('input')
255
+ .find('input:visible', { timeout: 30000 })
245
256
  .eq(0)
246
257
  .clear()
247
258
  .type(Cypress.expose('username'));
248
259
  cy.env(['password']).then(({ password }) => {
249
260
  cy.GLO_getElement(['ng2$svy_nav_login', 'txtPass'])
250
- .find('input')
261
+ .find('input:visible', { timeout: 30000 })
251
262
  .eq(0)
252
263
  .clear()
253
264
  .type(password, { log: false });
@@ -272,13 +283,13 @@ Cypress.Commands.add('GLO_clearData', (formulaName) => {
272
283
  cy.GLO_log('Delete records completed');
273
284
  });
274
285
  Cypress.Commands.add('GLO_clickButton', (selector, buttonType) => {
275
- cy.GLO_getElement(selector)
276
- .then(($element) => {
277
- buttonType ? cy.wrap($element).find(`.${buttonType}`).click() : cy.wrap($element).click();
278
- })
279
- .then(() => {
280
- cy.GLO_checkLoadingIndicator();
281
- });
286
+ // Chain .click() directly onto the query (no .then()/cy.wrap() snapshot) so Cypress's
287
+ // retry-ability re-runs GLO_getElement fresh on every attempt. A snapshotted jQuery handle
288
+ // goes stale and never recovers if Angular destroys/recreates the node once real data loads
289
+ // (e.g. a dialog opened via keyboard shortcut, before its websocket round-trip completes).
290
+ const target = buttonType ? cy.GLO_getElement(selector).find(`.${buttonType}`) : cy.GLO_getElement(selector);
291
+ target.click({ timeout: 10000 });
292
+ cy.GLO_checkLoadingIndicator();
282
293
  });
283
294
  Cypress.Commands.add('GLO_clickButtonText', (textButton) => {
284
295
  clickButtonTextHelper(textButton, '.dx-button-content');
@@ -1680,7 +1691,12 @@ Cypress.Commands.add('GLO_readFile', (fileName, fileType) => {
1680
1691
  cy.readFile(`${Cypress.config('downloadsFolder')}/${fileName}.${fileType}`);
1681
1692
  });
1682
1693
  Cypress.Commands.add('GLO_dialogRequiredCheck', (confirmation, title, content, contentMatchType = 'exact') => {
1683
- cy.GLO_getVisible('.svy-message-dialog, .svy-dialog', 120000).within(($dialog) => {
1694
+ // Cypress 16's :visible no longer excludes a dialog covered by another one stacked on top
1695
+ // of it (native checkVisibility() doesn't check occlusion), so when two dialogs are open
1696
+ // both can match here. The framework itself already marks the topmost/interactive one with
1697
+ // an "active" class (moved off the covered dialog onto whichever opens on top of it), so
1698
+ // scope to that instead of relying on :visible alone to disambiguate.
1699
+ cy.GLO_getVisible('.svy-message-dialog.active, .svy-dialog.active', 120000).within(($dialog) => {
1684
1700
  if ($dialog.find('.window-title.type-error:visible').length > 0) {
1685
1701
  if (typeof title === 'undefined' && typeof content === 'undefined') {
1686
1702
  cy.GLO_log('Error dialog, failing test. Explicit definition of the title or content required.');
@@ -1770,9 +1786,21 @@ Cypress.Commands.add('GLO_fillInfieldInGrid', (selector, rowFilter, columnId, va
1770
1786
  if (!value)
1771
1787
  return;
1772
1788
  if (typeof rowFilter === 'string') {
1773
- cy.GLO_getAllRowsInGrid().filter(`:contains("${rowFilter}")`).find(`[col-id="${columnId}"]`).click();
1789
+ cy.GLO_getAllRowsInGrid()
1790
+ .filter((_, el) => Cypress.$(el)
1791
+ .find('[role="gridcell"]')
1792
+ .toArray()
1793
+ .some((cell) => cell.textContent?.trim() === rowFilter))
1794
+ .find(`[col-id="${columnId}"]`)
1795
+ .click();
1774
1796
  cy.wait(500); // Else both click and dblclick fire too fast
1775
- cy.GLO_getAllRowsInGrid().filter(`:contains("${rowFilter}")`).find(`[col-id="${columnId}"]`).dblclick();
1797
+ cy.GLO_getAllRowsInGrid()
1798
+ .filter((_, el) => Cypress.$(el)
1799
+ .find('[role="gridcell"]')
1800
+ .toArray()
1801
+ .some((cell) => cell.textContent?.trim() === rowFilter))
1802
+ .find(`[col-id="${columnId}"]`)
1803
+ .dblclick();
1776
1804
  }
1777
1805
  else {
1778
1806
  cy.GLO_getElement(selector).within(() => {
@@ -2177,7 +2205,11 @@ Cypress.Commands.add('GLO_extendedFilter', (filter, clearExistingFilters) => {
2177
2205
  const id = $el[0].getAttribute('aria-controls');
2178
2206
  if (!id)
2179
2207
  throw new Error('Missing aria-controls');
2180
- cy.get(`[id="${id}"]`, { timeout: 30000 }).should('exist').should('not.have.class', 'dx-empty-collection').find('.dx-item').should('have.length.greaterThan', 0);
2208
+ cy.get(`[id="${id}"]`, { timeout: 30000 })
2209
+ .should('be.visible')
2210
+ .should('not.have.class', 'dx-empty-collection')
2211
+ .find('.dx-item')
2212
+ .should('have.length.greaterThan', 0);
2181
2213
  cy.get(`[id="${id}"] .dx-scrollview-content`).children().contains(value).click();
2182
2214
  });
2183
2215
  switch (dropDownType) {
package/dist/config.js CHANGED
@@ -27,7 +27,9 @@ const browserSettings = (browser, launchOptions) => {
27
27
  export function defineGlobisConfig(overrides = {}) {
28
28
  const wsMetricsBuffer = [];
29
29
  return defineConfig({
30
- allowCypressEnv: false,
30
+ // Cypress 16's HTTP/2 support is still an unfinished epic (1/14 done), causing ETIMEDOUT
31
+ // hangs against our Servoy backend. Remove once resolved: https://github.com/cypress-io/cypress/issues/33846
32
+ forceHttp1: true,
31
33
  e2e: {
32
34
  videosFolder: 'cypress/videos',
33
35
  screenshotsFolder: 'cypress/screenshots',
@@ -37,7 +39,7 @@ export function defineGlobisConfig(overrides = {}) {
37
39
  videoCompression: false,
38
40
  responseTimeout: 120000,
39
41
  modifyObstructiveCode: false,
40
- experimentalMemoryManagement: true,
42
+ keystrokeDelay: 10,
41
43
  numTestsKeptInMemory: Number(process.env.numTestsKeptInMemory ?? 0),
42
44
  scrollBehavior: 'center',
43
45
  ...overrides.e2e,
@@ -141,9 +141,11 @@ export declare function parseLocalizedNumber(value: string | number | undefined)
141
141
  *
142
142
  * Gets the jenkins build number to use as a UUID,
143
143
  * will fall back to a random generated 4-character UUID if not found.
144
- * @returns {string} CYP + build number or random 4-character UUID
144
+ * Each suffix keeps its own UUID: the first call for a suffix creates it, later calls reuse it.
145
+ * @param {string} [suffix] Optional suffix to get a UUID separate from the default one.
146
+ * @returns {string} CYP + build number or random 4-character UUID, followed by the suffix if given
145
147
  */
146
- export declare const getOrCreateUuid: (createNew?: 'createNew') => string;
148
+ export declare const getOrCreateUuid: (suffix?: string) => string;
147
149
  /**
148
150
  * Returns the ISO 8601 week number for the given date.
149
151
  *
package/dist/tsHelper.js CHANGED
@@ -151,16 +151,17 @@ export function parseLocalizedNumber(value) {
151
151
  *
152
152
  * Gets the jenkins build number to use as a UUID,
153
153
  * will fall back to a random generated 4-character UUID if not found.
154
- * @returns {string} CYP + build number or random 4-character UUID
154
+ * Each suffix keeps its own UUID: the first call for a suffix creates it, later calls reuse it.
155
+ * @param {string} [suffix] Optional suffix to get a UUID separate from the default one.
156
+ * @returns {string} CYP + build number or random 4-character UUID, followed by the suffix if given
155
157
  */
156
- export const getOrCreateUuid = (createNew) => {
157
- const id = createNew === 'createNew' ? getUUID(3) : undefined;
158
- const envKey = id !== undefined ? `BUILD_UUID_${id}` : 'BUILD_UUID';
158
+ export const getOrCreateUuid = (suffix) => {
159
+ const envKey = suffix ? `BUILD_UUID_${suffix}` : 'BUILD_UUID';
159
160
  let uuid = Cypress.expose(envKey);
160
161
  if (!uuid) {
161
162
  const buildNumber = Cypress.expose('BUILD_NUMBER');
162
- const suffix = id !== undefined ? `-${id}` : '';
163
- uuid = buildNumber ? `CYP${buildNumber}${suffix}` : `CYP${getUUID(4)}${suffix}`;
163
+ const postfix = suffix ? `-${suffix}` : '';
164
+ uuid = buildNumber ? `CYP${buildNumber}${postfix}` : `CYP${getUUID(4)}${postfix}`;
164
165
  Cypress.expose({ [envKey]: uuid });
165
166
  }
166
167
  return uuid;
@@ -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.9",
3
+ "version": "1.0.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",