@globisnv/cypress-core 0.0.10 → 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.
- package/biome.json +11 -0
- package/dist/blueprints/GLO_dangerousGoods.d.ts +10 -0
- package/dist/blueprints/GLO_dangerousGoods.js +9 -0
- package/dist/blueprints/GLO_logisticContract.d.ts +14 -0
- package/dist/blueprints/GLO_logisticContract.js +53 -11
- package/dist/blueprints/GLO_logisticContractDetail.d.ts +11 -1
- package/dist/blueprints/GLO_logisticContractDetail.js +33 -8
- package/dist/blueprints/GLO_logisticContractDetailHandling.d.ts +1 -0
- package/dist/blueprints/GLO_logisticContractDetailHandling.js +1 -0
- package/dist/blueprints/GLO_logisticContractDetailItem.js +1 -1
- package/dist/blueprints/GLO_logisticContractItem.d.ts +9 -0
- package/dist/blueprints/GLO_logisticContractItem.js +31 -4
- package/dist/blueprints/GLO_logisticLocationType.d.ts +4 -0
- package/dist/blueprints/GLO_logisticLocationType.js +4 -0
- package/dist/blueprints/GLO_planBloc.d.ts +6 -0
- package/dist/blueprints/GLO_planBloc.js +6 -0
- package/dist/blueprints/GLO_priceEntry.d.ts +1 -0
- package/dist/blueprints/GLO_priceEntry.js +1 -0
- package/dist/blueprints/GLO_purchaseOrderLine.js +4 -0
- package/dist/blueprints/GLO_stopSchedule.d.ts +11 -0
- package/dist/blueprints/GLO_stopSchedule.js +11 -0
- package/dist/blueprints/GLO_transportScenario.d.ts +4 -0
- package/dist/blueprints/GLO_transportScenario.js +4 -0
- package/dist/blueprints/GLO_transportScenarioExecution.d.ts +6 -0
- package/dist/blueprints/GLO_transportScenarioExecution.js +5 -0
- package/dist/blueprints/GLO_transportScenarioRegion.d.ts +5 -0
- package/dist/blueprints/GLO_transportScenarioRegion.js +5 -0
- package/dist/blueprints/GLO_zoneTypeEntityType.d.ts +4 -0
- package/dist/blueprints/GLO_zoneTypeEntityType.js +4 -0
- package/dist/commands/GLO_commands.js +54 -22
- package/dist/config.js +4 -2
- package/dist/websocketDetector.js +148 -114
- package/package.json +9 -6
- package/template/.gitattributes +12 -0
- 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
|
-
//
|
|
39
|
-
if (obj.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 ||
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
cy.
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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 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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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,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
|
+
};
|
|
@@ -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
|
};
|
|
@@ -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.
|
|
231
|
-
|
|
232
|
-
.
|
|
233
|
-
|
|
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.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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()
|
|
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()
|
|
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 })
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
keystrokeDelay: 10,
|
|
41
43
|
numTestsKeptInMemory: Number(process.env.numTestsKeptInMemory ?? 0),
|
|
42
44
|
scrollBehavior: 'center',
|
|
43
45
|
...overrides.e2e,
|
|
@@ -497,66 +497,86 @@ Cypress.Commands.add('WS_loadingCheck', (options) => {
|
|
|
497
497
|
});
|
|
498
498
|
return cy.wrap('locked', { log: false });
|
|
499
499
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
-
|
|
507
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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((
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
559
|
-
|
|
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
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
580
|
-
return clearWebSocketMessages();
|
|
595
|
+
return { type: 'resolved' };
|
|
581
596
|
}
|
|
582
|
-
/**
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
|
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
|
|
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": "^
|
|
47
|
-
"cypress-real-events": "^1.15.
|
|
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": "^
|
|
60
|
-
"cypress-real-events": "^1.15.
|
|
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@
|
|
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
|
package/template/package.json
CHANGED
|
@@ -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.
|
|
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": "^
|
|
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",
|