@globisnv/cypress-core 0.0.2 → 0.0.4
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/README.md +12 -7
- package/dist/apiMethods/IGS_request.d.ts +98 -0
- package/dist/apiMethods/IGS_request.js +161 -0
- package/dist/apiMethods/IGS_response.d.ts +47 -0
- package/dist/apiMethods/IGS_response.js +1 -0
- package/dist/blueprints/GLO_addressPopup.d.ts +45 -0
- package/dist/blueprints/GLO_addressPopup.js +96 -0
- package/dist/blueprints/GLO_article.d.ts +45 -0
- package/dist/blueprints/GLO_article.js +110 -0
- package/dist/blueprints/GLO_articleLot.d.ts +12 -0
- package/dist/blueprints/GLO_articleLot.js +23 -0
- package/dist/blueprints/GLO_articleVersion.d.ts +4 -0
- package/dist/blueprints/GLO_articleVersion.js +4 -0
- package/dist/blueprints/GLO_attachmentPopup.d.ts +9 -0
- package/dist/blueprints/GLO_attachmentPopup.js +10 -0
- package/dist/blueprints/GLO_bankAccount.d.ts +7 -0
- package/dist/blueprints/GLO_bankAccount.js +7 -0
- package/dist/blueprints/GLO_billingProcess.d.ts +4 -0
- package/dist/blueprints/GLO_billingProcess.js +4 -0
- package/dist/blueprints/GLO_billingProposal.d.ts +9 -0
- package/dist/blueprints/GLO_billingProposal.js +9 -0
- package/dist/blueprints/GLO_cancel.d.ts +7 -0
- package/dist/blueprints/GLO_cancel.js +7 -0
- package/dist/blueprints/GLO_commandlist.d.ts +10 -0
- package/dist/blueprints/GLO_commandlist.js +10 -0
- package/dist/blueprints/GLO_complaint.d.ts +27 -0
- package/dist/blueprints/GLO_complaint.js +51 -0
- package/dist/blueprints/GLO_composition.d.ts +14 -0
- package/dist/blueprints/GLO_composition.js +20 -0
- package/dist/blueprints/GLO_consignment.d.ts +61 -0
- package/dist/blueprints/GLO_consignment.js +104 -0
- package/dist/blueprints/GLO_consignmentContainerGroup.d.ts +6 -0
- package/dist/blueprints/GLO_consignmentContainerGroup.js +6 -0
- package/dist/blueprints/GLO_consignmentGoods.d.ts +40 -0
- package/dist/blueprints/GLO_consignmentGoods.js +62 -0
- package/dist/blueprints/GLO_consignmentHandling.d.ts +4 -0
- package/dist/blueprints/GLO_consignmentHandling.js +4 -0
- package/dist/blueprints/GLO_consignmentOrder.d.ts +31 -0
- package/dist/blueprints/GLO_consignmentOrder.js +37 -0
- package/dist/blueprints/GLO_consignmentPart.d.ts +13 -0
- package/dist/blueprints/GLO_consignmentPart.js +13 -0
- package/dist/blueprints/GLO_consignmentParts.d.ts +5 -0
- package/dist/blueprints/GLO_consignmentParts.js +12 -0
- package/dist/blueprints/GLO_consignmentPrice.d.ts +11 -0
- package/dist/blueprints/GLO_consignmentPrice.js +19 -0
- package/dist/blueprints/GLO_consignmentSupplier.d.ts +6 -0
- package/dist/blueprints/GLO_consignmentSupplier.js +11 -0
- package/dist/blueprints/GLO_contacts.d.ts +10 -0
- package/dist/blueprints/GLO_contacts.js +14 -0
- package/dist/blueprints/GLO_container.d.ts +6 -0
- package/dist/blueprints/GLO_container.js +4 -0
- package/dist/blueprints/GLO_contract.d.ts +22 -0
- package/dist/blueprints/GLO_contract.js +30 -0
- package/dist/blueprints/GLO_contractArticle.d.ts +17 -0
- package/dist/blueprints/GLO_contractArticle.js +35 -0
- package/dist/blueprints/GLO_contractArticleCost.d.ts +5 -0
- package/dist/blueprints/GLO_contractArticleCost.js +8 -0
- package/dist/blueprints/GLO_countryVatZone.d.ts +8 -0
- package/dist/blueprints/GLO_countryVatZone.js +9 -0
- package/dist/blueprints/GLO_currencyDeviation.d.ts +14 -0
- package/dist/blueprints/GLO_currencyDeviation.js +15 -0
- package/dist/blueprints/GLO_customerArticle.d.ts +12 -0
- package/dist/blueprints/GLO_customerArticle.js +15 -0
- package/dist/blueprints/GLO_customsFlow.d.ts +8 -0
- package/dist/blueprints/GLO_customsFlow.js +19 -0
- package/dist/blueprints/GLO_customsFlowDefinition.d.ts +15 -0
- package/dist/blueprints/GLO_customsFlowDefinition.js +14 -0
- package/dist/blueprints/GLO_customsUnion.d.ts +4 -0
- package/dist/blueprints/GLO_customsUnion.js +4 -0
- package/dist/blueprints/GLO_deliveryReceipt.d.ts +10 -0
- package/dist/blueprints/GLO_deliveryReceipt.js +22 -0
- package/dist/blueprints/GLO_departureSchedule.d.ts +27 -0
- package/dist/blueprints/GLO_departureSchedule.js +57 -0
- package/dist/blueprints/GLO_departureScheduleTimes.d.ts +49 -0
- package/dist/blueprints/GLO_departureScheduleTimes.js +61 -0
- package/dist/blueprints/GLO_document.d.ts +7 -0
- package/dist/blueprints/GLO_document.js +7 -0
- package/dist/blueprints/GLO_documentAttachment.d.ts +4 -0
- package/dist/blueprints/GLO_documentAttachment.js +4 -0
- package/dist/blueprints/GLO_documentDefinitionEntity.d.ts +5 -0
- package/dist/blueprints/GLO_documentDefinitionEntity.js +5 -0
- package/dist/blueprints/GLO_documentGroup.d.ts +4 -0
- package/dist/blueprints/GLO_documentGroup.js +4 -0
- package/dist/blueprints/GLO_documentType.d.ts +12 -0
- package/dist/blueprints/GLO_documentType.js +11 -0
- package/dist/blueprints/GLO_enterCountry.d.ts +21 -0
- package/dist/blueprints/GLO_enterCountry.js +47 -0
- package/dist/blueprints/GLO_enterDiscount.d.ts +8 -0
- package/dist/blueprints/GLO_enterDiscount.js +9 -0
- package/dist/blueprints/GLO_equipment.d.ts +34 -0
- package/dist/blueprints/GLO_equipment.js +55 -0
- package/dist/blueprints/GLO_equipmentCategory.d.ts +15 -0
- package/dist/blueprints/GLO_equipmentCategory.js +14 -0
- package/dist/blueprints/GLO_equipmentEntity.d.ts +5 -0
- package/dist/blueprints/GLO_equipmentEntity.js +5 -0
- package/dist/blueprints/GLO_equipmentServiceCategory.d.ts +13 -0
- package/dist/blueprints/GLO_equipmentServiceCategory.js +13 -0
- package/dist/blueprints/GLO_equipmentType.d.ts +21 -0
- package/dist/blueprints/GLO_equipmentType.js +20 -0
- package/dist/blueprints/GLO_equipmentTypeServiceCategory.d.ts +13 -0
- package/dist/blueprints/GLO_equipmentTypeServiceCategory.js +12 -0
- package/dist/blueprints/GLO_fieldInputValidation.d.ts +46 -0
- package/dist/blueprints/GLO_fieldInputValidation.js +73 -0
- package/dist/blueprints/GLO_formula.d.ts +9 -0
- package/dist/blueprints/GLO_formula.js +9 -0
- package/dist/blueprints/GLO_gang.d.ts +12 -0
- package/dist/blueprints/GLO_gang.js +12 -0
- package/dist/blueprints/GLO_holiday.d.ts +7 -0
- package/dist/blueprints/GLO_holiday.js +8 -0
- package/dist/blueprints/GLO_journey.d.ts +18 -0
- package/dist/blueprints/GLO_journey.js +24 -0
- package/dist/blueprints/GLO_journeyStop.d.ts +32 -0
- package/dist/blueprints/GLO_journeyStop.js +44 -0
- package/dist/blueprints/GLO_layout.d.ts +6 -0
- package/dist/blueprints/GLO_layout.js +6 -0
- package/dist/blueprints/GLO_logging.d.ts +9 -0
- package/dist/blueprints/GLO_logging.js +12 -0
- package/dist/blueprints/GLO_logisticConnection.d.ts +10 -0
- package/dist/blueprints/GLO_logisticConnection.js +11 -0
- package/dist/blueprints/GLO_logisticConnectionType.d.ts +5 -0
- package/dist/blueprints/GLO_logisticConnectionType.js +5 -0
- package/dist/blueprints/GLO_logisticContract.d.ts +28 -0
- package/dist/blueprints/GLO_logisticContract.js +80 -0
- package/dist/blueprints/GLO_logisticContractDetail.d.ts +42 -0
- package/dist/blueprints/GLO_logisticContractDetail.js +122 -0
- package/dist/blueprints/GLO_logisticContractDetailHandling.d.ts +11 -0
- package/dist/blueprints/GLO_logisticContractDetailHandling.js +14 -0
- package/dist/blueprints/GLO_logisticContractDetailItem.d.ts +5 -0
- package/dist/blueprints/GLO_logisticContractDetailItem.js +8 -0
- package/dist/blueprints/GLO_logisticContractItem.d.ts +14 -0
- package/dist/blueprints/GLO_logisticContractItem.js +40 -0
- package/dist/blueprints/GLO_logisticContractItemActivity.d.ts +4 -0
- package/dist/blueprints/GLO_logisticContractItemActivity.js +4 -0
- package/dist/blueprints/GLO_logisticContractItemSurcharge.d.ts +7 -0
- package/dist/blueprints/GLO_logisticContractItemSurcharge.js +7 -0
- package/dist/blueprints/GLO_logisticContractItemSurchargeItem.d.ts +4 -0
- package/dist/blueprints/GLO_logisticContractItemSurchargeItem.js +4 -0
- package/dist/blueprints/GLO_logisticContractReference.d.ts +4 -0
- package/dist/blueprints/GLO_logisticContractReference.js +4 -0
- package/dist/blueprints/GLO_logisticContractVolumeConstraint.d.ts +9 -0
- package/dist/blueprints/GLO_logisticContractVolumeConstraint.js +8 -0
- package/dist/blueprints/GLO_logisticLocation.d.ts +7 -0
- package/dist/blueprints/GLO_logisticLocation.js +7 -0
- package/dist/blueprints/GLO_logisticPriceRequestDetailExternalPrice.d.ts +19 -0
- package/dist/blueprints/GLO_logisticPriceRequestDetailExternalPrice.js +30 -0
- package/dist/blueprints/GLO_logisticPurchaseOrder.d.ts +4 -0
- package/dist/blueprints/GLO_logisticPurchaseOrder.js +4 -0
- package/dist/blueprints/GLO_logistic_waiting_hours.d.ts +6 -0
- package/dist/blueprints/GLO_logistic_waiting_hours.js +6 -0
- package/dist/blueprints/GLO_operationalDefault.d.ts +23 -0
- package/dist/blueprints/GLO_operationalDefault.js +25 -0
- package/dist/blueprints/GLO_outputDetailMail.d.ts +14 -0
- package/dist/blueprints/GLO_outputDetailMail.js +13 -0
- package/dist/blueprints/GLO_postalcode.d.ts +6 -0
- package/dist/blueprints/GLO_postalcode.js +6 -0
- package/dist/blueprints/GLO_priceDependency.d.ts +5 -0
- package/dist/blueprints/GLO_priceDependency.js +4 -0
- package/dist/blueprints/GLO_priceEntry.d.ts +17 -0
- package/dist/blueprints/GLO_priceEntry.js +17 -0
- package/dist/blueprints/GLO_priceRequest.d.ts +8 -0
- package/dist/blueprints/GLO_priceRequest.js +20 -0
- package/dist/blueprints/GLO_priceRequestLine.d.ts +12 -0
- package/dist/blueprints/GLO_priceRequestLine.js +17 -0
- package/dist/blueprints/GLO_principal.d.ts +9 -0
- package/dist/blueprints/GLO_principal.js +13 -0
- package/dist/blueprints/GLO_printPopup.d.ts +10 -0
- package/dist/blueprints/GLO_printPopup.js +62 -0
- package/dist/blueprints/GLO_production.d.ts +9 -0
- package/dist/blueprints/GLO_production.js +9 -0
- package/dist/blueprints/GLO_productionLineMaterial.d.ts +8 -0
- package/dist/blueprints/GLO_productionLineMaterial.js +13 -0
- package/dist/blueprints/GLO_productionMaterial.d.ts +5 -0
- package/dist/blueprints/GLO_productionMaterial.js +5 -0
- package/dist/blueprints/GLO_purchaseCosts.d.ts +6 -0
- package/dist/blueprints/GLO_purchaseCosts.js +9 -0
- package/dist/blueprints/GLO_purchaseInvoice.d.ts +12 -0
- package/dist/blueprints/GLO_purchaseInvoice.js +25 -0
- package/dist/blueprints/GLO_purchaseInvoiceLine.d.ts +8 -0
- package/dist/blueprints/GLO_purchaseInvoiceLine.js +13 -0
- package/dist/blueprints/GLO_purchaseOrder.d.ts +14 -0
- package/dist/blueprints/GLO_purchaseOrder.js +25 -0
- package/dist/blueprints/GLO_purchaseOrderLine.d.ts +10 -0
- package/dist/blueprints/GLO_purchaseOrderLine.js +24 -0
- package/dist/blueprints/GLO_purchasePrices.d.ts +10 -0
- package/dist/blueprints/GLO_purchasePrices.js +10 -0
- package/dist/blueprints/GLO_quotation.d.ts +12 -0
- package/dist/blueprints/GLO_quotation.js +32 -0
- package/dist/blueprints/GLO_quotationLine.d.ts +12 -0
- package/dist/blueprints/GLO_quotationLine.js +28 -0
- package/dist/blueprints/GLO_receipt.d.ts +13 -0
- package/dist/blueprints/GLO_receipt.js +17 -0
- package/dist/blueprints/GLO_receiptFromTempReceipt.d.ts +8 -0
- package/dist/blueprints/GLO_receiptFromTempReceipt.js +8 -0
- package/dist/blueprints/GLO_receiptLine.d.ts +22 -0
- package/dist/blueprints/GLO_receiptLine.js +28 -0
- package/dist/blueprints/GLO_relationAddress.d.ts +26 -0
- package/dist/blueprints/GLO_relationAddress.js +58 -0
- package/dist/blueprints/GLO_relationBankAccount.d.ts +9 -0
- package/dist/blueprints/GLO_relationBankAccount.js +9 -0
- package/dist/blueprints/GLO_relationContact.d.ts +23 -0
- package/dist/blueprints/GLO_relationContact.js +45 -0
- package/dist/blueprints/GLO_relationGroupType.d.ts +7 -0
- package/dist/blueprints/GLO_relationGroupType.js +6 -0
- package/dist/blueprints/GLO_relations.d.ts +67 -0
- package/dist/blueprints/GLO_relations.js +186 -0
- package/dist/blueprints/GLO_relationtype.d.ts +15 -0
- package/dist/blueprints/GLO_relationtype.js +31 -0
- package/dist/blueprints/GLO_remarkType.d.ts +10 -0
- package/dist/blueprints/GLO_remarkType.js +9 -0
- package/dist/blueprints/GLO_remarkTypeDefaultValue.d.ts +4 -0
- package/dist/blueprints/GLO_remarkTypeDefaultValue.js +14 -0
- package/dist/blueprints/GLO_remarkTypeEntityType.d.ts +10 -0
- package/dist/blueprints/GLO_remarkTypeEntityType.js +9 -0
- package/dist/blueprints/GLO_representative.d.ts +7 -0
- package/dist/blueprints/GLO_representative.js +6 -0
- package/dist/blueprints/GLO_salesInvoice.d.ts +9 -0
- package/dist/blueprints/GLO_salesInvoice.js +15 -0
- package/dist/blueprints/GLO_salesInvoiceLine.d.ts +9 -0
- package/dist/blueprints/GLO_salesInvoiceLine.js +13 -0
- package/dist/blueprints/GLO_salesOrder.d.ts +13 -0
- package/dist/blueprints/GLO_salesOrder.js +25 -0
- package/dist/blueprints/GLO_salesOrderLine.d.ts +13 -0
- package/dist/blueprints/GLO_salesOrderLine.js +34 -0
- package/dist/blueprints/GLO_stockCorrection.d.ts +14 -0
- package/dist/blueprints/GLO_stockCorrection.js +24 -0
- package/dist/blueprints/GLO_stopSchedule.d.ts +4 -0
- package/dist/blueprints/GLO_stopSchedule.js +4 -0
- package/dist/blueprints/GLO_supplierArticle.d.ts +8 -0
- package/dist/blueprints/GLO_supplierArticle.js +8 -0
- package/dist/blueprints/GLO_tclSetting.d.ts +6 -0
- package/dist/blueprints/GLO_tclSetting.js +6 -0
- package/dist/blueprints/GLO_todo.d.ts +10 -0
- package/dist/blueprints/GLO_todo.js +21 -0
- package/dist/blueprints/GLO_todoSchedule.d.ts +23 -0
- package/dist/blueprints/GLO_todoSchedule.js +79 -0
- package/dist/blueprints/GLO_tractionDistance.d.ts +6 -0
- package/dist/blueprints/GLO_tractionDistance.js +6 -0
- package/dist/blueprints/GLO_transportOrder.d.ts +4 -0
- package/dist/blueprints/GLO_transportOrder.js +4 -0
- package/dist/blueprints/GLO_transportPrice.d.ts +18 -0
- package/dist/blueprints/GLO_transportPrice.js +34 -0
- package/dist/blueprints/GLO_transportRequirement.d.ts +18 -0
- package/dist/blueprints/GLO_transportRequirement.js +32 -0
- package/dist/blueprints/GLO_transportRequirementContainer.d.ts +8 -0
- package/dist/blueprints/GLO_transportRequirementContainer.js +8 -0
- package/dist/blueprints/GLO_transportRequirementGoods.d.ts +33 -0
- package/dist/blueprints/GLO_transportRequirementGoods.js +51 -0
- package/dist/blueprints/GLO_transportScenarioExecutionLeg.d.ts +6 -0
- package/dist/blueprints/GLO_transportScenarioExecutionLeg.js +6 -0
- package/dist/blueprints/GLO_transportStock.d.ts +4 -0
- package/dist/blueprints/GLO_transportStock.js +4 -0
- package/dist/blueprints/GLO_visit.d.ts +9 -0
- package/dist/blueprints/GLO_visit.js +10 -0
- package/dist/blueprints/GLO_warehouseMove.d.ts +9 -0
- package/dist/blueprints/GLO_warehouseMove.js +9 -0
- package/dist/blueprints/GLO_warehouseOrderSubline.d.ts +6 -0
- package/dist/blueprints/GLO_warehouseOrderSubline.js +11 -0
- package/dist/blueprints/GLO_workflowLoggingTable.d.ts +20 -0
- package/dist/blueprints/GLO_workflowLoggingTable.js +20 -0
- package/dist/blueprints/GLO_workflowVerification.d.ts +10 -0
- package/dist/blueprints/GLO_workflowVerification.js +19 -0
- package/dist/blueprints/GLO_zone.d.ts +9 -0
- package/dist/blueprints/GLO_zone.js +9 -0
- package/dist/blueprints/GLO_zone_postalcode.d.ts +12 -0
- package/dist/blueprints/GLO_zone_postalcode.js +10 -0
- package/dist/blueprints/GLO_zone_type.d.ts +8 -0
- package/dist/blueprints/GLO_zone_type.js +7 -0
- package/dist/blueprints/GLT_goods.d.ts +2 -0
- package/dist/blueprints/GLT_goods.js +12 -0
- package/dist/commands/FOB_commands.d.ts +124 -0
- package/dist/commands/FOB_commands.js +211 -0
- package/dist/commands/GLJ_commands.d.ts +28 -0
- package/dist/commands/GLJ_commands.js +40 -0
- package/dist/commands/GLO_commands.d.ts +685 -0
- package/dist/commands/GLO_commands.js +2319 -0
- package/dist/commands/GLS_commands.d.ts +63 -0
- package/dist/commands/GLS_commands.js +103 -0
- package/dist/commands/GLT_commands.d.ts +78 -0
- package/dist/commands/GLT_commands.js +148 -0
- package/dist/commands/POR_commands.d.ts +85 -0
- package/dist/commands/POR_commands.js +122 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/plugins/prometheusWriter.d.ts +15 -0
- package/dist/plugins/prometheusWriter.js +111 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.js +23 -0
- package/dist/tsHelper.d.ts +161 -0
- package/dist/tsHelper.js +294 -0
- package/dist/types.d.ts +363 -0
- package/dist/types.js +4 -0
- package/dist/websocketDetector.d.ts +71 -0
- package/dist/websocketDetector.js +1593 -0
- package/package.json +10 -2
- package/template/package.json +1 -1
|
@@ -0,0 +1,2319 @@
|
|
|
1
|
+
import { bottom, buttonSearch, check, contains, cypressWebsocketLoader, defaultTimeout, enabled, equal, greaterEqual, gridDropDown, gridInput, gridInputWithoutEnter, gridSelect, hideInCommandLog, input, isInvalid, noEqualBool, noSkipCheck, notEqual, parseLocalizedNumber, row, select, skipCheck, span, top, unCheck } from '../tsHelper.js';
|
|
2
|
+
import { attachWebSocketSpy } from '../websocketDetector.js';
|
|
3
|
+
//! File-specific helpers
|
|
4
|
+
const monthNames = ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'];
|
|
5
|
+
const monthMap = new Map(monthNames.map((month, index) => [month, index + 1]));
|
|
6
|
+
const monthNamesEN = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
7
|
+
monthNamesEN.forEach((month, index) => {
|
|
8
|
+
monthMap.set(month, index + 1);
|
|
9
|
+
});
|
|
10
|
+
const globisUrlTypes = ['globtake', 'globis_external_portal'];
|
|
11
|
+
const viewModeButtons = ['btnList', 'btnDetail'];
|
|
12
|
+
const extractSelectorParts = (selector) => {
|
|
13
|
+
const last = selector[selector.length - 1];
|
|
14
|
+
if (typeof last === 'number')
|
|
15
|
+
return { baseSelector: selector.slice(0, -1), index: last };
|
|
16
|
+
return { baseSelector: selector, index: 0 };
|
|
17
|
+
};
|
|
18
|
+
// When index refers to a grid row (element repeated per row), GLO_getElement handles row filtering via the full selector.
|
|
19
|
+
// When index refers to a sub-input within a single element (e.g. a field with two inputs), the caller uses eq(index).
|
|
20
|
+
// This helper distinguishes the two cases synchronously.
|
|
21
|
+
const isGridRowSelector = (baseSelector, index) => {
|
|
22
|
+
if (index === 0)
|
|
23
|
+
return false;
|
|
24
|
+
const selectorStr = baseSelector.length === 2 ? `[data-cy$="${baseSelector[0]}.${baseSelector[1]}"]` : `[name="${baseSelector[0]}"]`;
|
|
25
|
+
const el = Cypress.$(`div[formslist]:not([hidden]) ${selectorStr}`)[0];
|
|
26
|
+
return !!el?.closest('[row-index]');
|
|
27
|
+
};
|
|
28
|
+
// Row queries must ignore grids that are not on screen, but neither obvious filter does that job.
|
|
29
|
+
// Servoy marks the *containers* hidden - `div[formslist][hidden]` for background programs,
|
|
30
|
+
// `[tabpane][hidden]` for closed tabs (see GLO_getElement) - so `[row-index]:not([hidden])` filters
|
|
31
|
+
// nothing at all and matches rows of every grid in the DOM. `:visible` overshoots the other way:
|
|
32
|
+
// AG Grid's ag-root and ag-viewport use `overflow: hidden`, so Cypress' out-of-bounds check reports
|
|
33
|
+
// rendered-but-scrolled-past rows as hidden (measured: 14 of 21 rendered rows drop out of `:visible`
|
|
34
|
+
// after scrolling a grid to the bottom). Filter on the container, which is what both were after.
|
|
35
|
+
const isInOpenContainer = (_, el) => !el.closest('[hidden]');
|
|
36
|
+
const pickByRowIndex = (chain, index, log = false) => {
|
|
37
|
+
if (log)
|
|
38
|
+
Cypress.log({ name: 'idx', message: String(index) });
|
|
39
|
+
return chain.filter((i, el) => {
|
|
40
|
+
const rowIndexEl = el.closest('[row-index]');
|
|
41
|
+
return rowIndexEl ? rowIndexEl.getAttribute('row-index') === String(index) : i === index;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Cypress.Commands.add('GLO_getElement', (selectorArg, options = {}) => {
|
|
45
|
+
const arr = selectorArg;
|
|
46
|
+
const hasIndex = typeof arr[arr.length - 1] === 'number';
|
|
47
|
+
const index = hasIndex ? arr[arr.length - 1] : undefined;
|
|
48
|
+
const strParts = (hasIndex ? arr.slice(0, -1) : arr);
|
|
49
|
+
const selector = strParts.length === 2 ? `[data-cy$="${strParts[0]}.${strParts[1]}"]` : `[name="${strParts[0]}"]`;
|
|
50
|
+
const safeCallerOpts = Object.fromEntries(Object.entries(options).filter(([, v]) => v !== undefined));
|
|
51
|
+
const getOpts = { timeout: defaultTimeout, log: true, ...safeCallerOpts };
|
|
52
|
+
const withinChain = cy.state('withinSubjectChain');
|
|
53
|
+
const withinSubject = withinChain ? cy.getSubjectFromChain(withinChain) : undefined;
|
|
54
|
+
if (!withinSubject) {
|
|
55
|
+
cy.wrap(null, { log: false, timeout: getOpts.timeout }).should(() => {
|
|
56
|
+
if (Cypress.$(`div[formslist]:not([hidden]) [tabpane][hidden] ${selector}`).length > 0) {
|
|
57
|
+
throw new Error(`GLO_getElement: element "${selector}" found in crumb but its tab is closed`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
const elementInCrumb = Cypress.$(`div[formslist] ${selector}`).length > 0;
|
|
61
|
+
if (elementInCrumb) {
|
|
62
|
+
const result = cy.get(`div[formslist]:not([hidden]) ${selector}`, getOpts);
|
|
63
|
+
return pickByRowIndex(result, index ?? 0, hasIndex);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const result = cy.get(selector, getOpts);
|
|
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;
|
|
69
|
+
});
|
|
70
|
+
Cypress.Commands.add('GLO_getVisible', (elementName, timeOut = defaultTimeout) => {
|
|
71
|
+
return cy.get(elementName, { timeout: timeOut }).filter(':visible', { timeout: timeOut });
|
|
72
|
+
});
|
|
73
|
+
Cypress.Commands.add('GLO_checkUser', () => {
|
|
74
|
+
const username = String(Cypress.expose('username'));
|
|
75
|
+
if (/globisadmin/.exec(username.toLowerCase())) {
|
|
76
|
+
throw new Error('Deletion of created data not allowed when globisadmin. Please log in using cypress user to proceed testing.'); // Stop the test by throwing an error
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
Cypress.Commands.add('GLO_login', (company, urlType) => {
|
|
80
|
+
const loginType = String(Cypress.expose('loginType'));
|
|
81
|
+
if (!Cypress.expose('globisUrl')) {
|
|
82
|
+
throw new Error('No Globis URL defined as environment parameter!');
|
|
83
|
+
}
|
|
84
|
+
switch (loginType.toLocaleLowerCase()) {
|
|
85
|
+
case 'servoy':
|
|
86
|
+
cy.GLO_servoyLogin(company);
|
|
87
|
+
break;
|
|
88
|
+
case 'okta':
|
|
89
|
+
cy.GLO_oktaLogin(company, urlType);
|
|
90
|
+
break;
|
|
91
|
+
case 'default':
|
|
92
|
+
cy.GLO_defaultlogin(company, undefined, undefined, urlType);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
throw new Error('Incorrect type for GLO_login. Available types: servoy, okta, default');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
Cypress.Commands.add('GLO_defaultlogin', (company, usernameENV = 'username', passwordENV = 'password', urlType) => {
|
|
99
|
+
// Change the Url if necessary
|
|
100
|
+
let url = Cypress.expose('globisUrl');
|
|
101
|
+
if (urlType) {
|
|
102
|
+
const target = globisUrlTypes.includes(urlType) ? 'globis_online' : 'online';
|
|
103
|
+
url = url.replace(target, urlType);
|
|
104
|
+
}
|
|
105
|
+
if (cypressWebsocketLoader) {
|
|
106
|
+
cy.WS_visitWithSpy(url);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
cy.visit(url);
|
|
110
|
+
}
|
|
111
|
+
cy.GLO_checkLoadingIndicator();
|
|
112
|
+
urlType === 'globjob' ? fillInUserAndPasswordGlobjob(usernameENV) : fillInUserAndPasswordDefault(usernameENV, passwordENV);
|
|
113
|
+
if (company) {
|
|
114
|
+
cy.GLO_selectCompany(company);
|
|
115
|
+
}
|
|
116
|
+
cy.GLO_checkLoadingIndicator();
|
|
117
|
+
cy.get('body').then(($body) => {
|
|
118
|
+
if ($body.find('.svy-message-dialog .window-title').text() === 'Locked record') {
|
|
119
|
+
// biome-ignore plugin: wait for locked record dialog choice to register
|
|
120
|
+
cy.wait(15000);
|
|
121
|
+
cy.GLO_dialogChoice('!globis.label.yes!');
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (!urlType) {
|
|
125
|
+
cy.get('[data-cy="ng$main_form2.navbar_top"]', { timeout: 120000 }).should('exist');
|
|
126
|
+
cy.get('loading-indicator', { timeout: 1200000 }).should('not.exist');
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
const fillInUserAndPasswordDefault = (usernameENV, passwordENV) => {
|
|
130
|
+
cy.get('[data-cy="ng2$svy_nav_login.logoImage"]', { timeout: 120000 }).should('exist');
|
|
131
|
+
cy.GLO_checkLoadingIndicator(); // The logo might be visible but there can still be a loading indicator
|
|
132
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'txtUser'], { timeout: 30000 })
|
|
133
|
+
.find('input:visible', { timeout: 30000 })
|
|
134
|
+
.eq(0)
|
|
135
|
+
.clear()
|
|
136
|
+
.type(Cypress.expose(usernameENV));
|
|
137
|
+
cy.env([passwordENV]).then((envVars) => {
|
|
138
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'txtPass'])
|
|
139
|
+
.find('input')
|
|
140
|
+
.eq(0)
|
|
141
|
+
.clear()
|
|
142
|
+
.type(envVars[passwordENV], { log: false });
|
|
143
|
+
});
|
|
144
|
+
cy.get('[data-cy="ng2$svy_nav_login.btnLogin"]').click();
|
|
145
|
+
cy.GLO_checkLoadingIndicator();
|
|
146
|
+
};
|
|
147
|
+
const fillInUserAndPasswordGlobjob = (usernameENV) => {
|
|
148
|
+
cy.GLO_getElement(['mobile$svy_nav_login', 'txt_login'])
|
|
149
|
+
.find('input')
|
|
150
|
+
.eq(0)
|
|
151
|
+
.clear()
|
|
152
|
+
.type(Cypress.expose(usernameENV));
|
|
153
|
+
cy.get('[data-cy="mobile$svy_nav_login.btn_login"]').click();
|
|
154
|
+
cy.GLO_checkLoadingIndicator();
|
|
155
|
+
};
|
|
156
|
+
Cypress.Commands.add('GLO_defaultloginExtraUser', (usernameENV, passwordENV, company) => {
|
|
157
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'txtUser'], { timeout: 60000 }).should('exist');
|
|
158
|
+
fillInUserAndPasswordDefault(usernameENV, passwordENV);
|
|
159
|
+
if (company) {
|
|
160
|
+
cy.GLO_selectCompany(company);
|
|
161
|
+
}
|
|
162
|
+
cy.get('[data-cy="ng$main_form2.navbar_top"]', { timeout: 120000 }).should('exist');
|
|
163
|
+
});
|
|
164
|
+
const waitForWebSocketAfterOkta = () => {
|
|
165
|
+
cy.url({ timeout: 60000 }).should('not.include', new URL(Cypress.expose('oktaUrl')).hostname);
|
|
166
|
+
cy.window({ timeout: 60000 }).should((win) => {
|
|
167
|
+
const w = win;
|
|
168
|
+
if (!w.wsMessages)
|
|
169
|
+
attachWebSocketSpy(win);
|
|
170
|
+
assert.isTrue(w.wsMessages?.wsConnected === true);
|
|
171
|
+
});
|
|
172
|
+
cy.window().then((win) => {
|
|
173
|
+
cy.wrap(win.wsMessages).as('wsMessages');
|
|
174
|
+
cy.task('log', `WebSocket spy confirmed connected, ${win.wsMessages?.messages.length} messages captured so far`);
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
Cypress.Commands.add('GLO_oktaLogin', (company, urlType, skipvNavCheck) => {
|
|
178
|
+
// Change the Url if necessary
|
|
179
|
+
let url = Cypress.expose('globisUrl');
|
|
180
|
+
if (urlType) {
|
|
181
|
+
const target = globisUrlTypes.includes(urlType) ? 'globis_online' : 'online';
|
|
182
|
+
url = url.replace(target, urlType);
|
|
183
|
+
}
|
|
184
|
+
if (cypressWebsocketLoader) {
|
|
185
|
+
// Stateless Okta login redirects server side before any Servoy client boots, so there is
|
|
186
|
+
// no websocket to wait for on the first visit. Attach the spy to every document load and
|
|
187
|
+
// only assert the connection after the Okta round trip (see waitForWebSocketAfterOkta).
|
|
188
|
+
cy.on('window:before:load', (win) => {
|
|
189
|
+
attachWebSocketSpy(win);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
cy.visit(url);
|
|
193
|
+
cy.task('log', 'Redirect to okta url');
|
|
194
|
+
cy.origin(Cypress.expose('oktaUrl'), () => {
|
|
195
|
+
cy.get('.o-form-input', { timeout: 60000 })
|
|
196
|
+
.find('[autocomplete="username"]')
|
|
197
|
+
.clear()
|
|
198
|
+
.type(Cypress.expose('username'));
|
|
199
|
+
cy.task('log', 'Filled in username');
|
|
200
|
+
cy.get('[type="submit"]').click();
|
|
201
|
+
cy.env(['password']).then(({ password }) => {
|
|
202
|
+
cy.get('.o-form-input')
|
|
203
|
+
.find('[type="password"]', { timeout: 20000 })
|
|
204
|
+
.type(password, { log: false });
|
|
205
|
+
});
|
|
206
|
+
cy.task('log', 'Filled in password');
|
|
207
|
+
cy.get('[type="submit"]').click();
|
|
208
|
+
});
|
|
209
|
+
if (company) {
|
|
210
|
+
cy.GLO_selectCompany(company);
|
|
211
|
+
}
|
|
212
|
+
if (cypressWebsocketLoader)
|
|
213
|
+
waitForWebSocketAfterOkta();
|
|
214
|
+
cy.task('log', 'Start navbar check');
|
|
215
|
+
if (!skipvNavCheck) {
|
|
216
|
+
const navbarSelectors = {
|
|
217
|
+
globtake: '[data-cy="gt$main.form_view_sidebar"]',
|
|
218
|
+
globis_external_portal: '[data-cy$="ep$main.sidemenu"]'
|
|
219
|
+
};
|
|
220
|
+
const selector = urlType ? navbarSelectors[urlType] : '[data-cy="ng$main_form2.navbar_top"]';
|
|
221
|
+
if (selector)
|
|
222
|
+
cy.get(selector, { timeout: 120000 }).should('exist');
|
|
223
|
+
cy.get('loading-indicator', { timeout: 1200000 }).should('not.exist');
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
// This is in case the check is instable
|
|
227
|
+
// biome-ignore plugin: wait in case the check is instable
|
|
228
|
+
cy.wait(5000);
|
|
229
|
+
cy.GLO_checkLoadingIndicator();
|
|
230
|
+
}
|
|
231
|
+
cy.task('log', 'End navbar check');
|
|
232
|
+
});
|
|
233
|
+
Cypress.Commands.add('GLO_logout', () => {
|
|
234
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_top']).find('.lucide-LogOut').click();
|
|
235
|
+
cy.GLO_getVisible('.window-footer button.svy-btn-primary', 30000)
|
|
236
|
+
.contains(/^(Yes|Ja)$/)
|
|
237
|
+
.click();
|
|
238
|
+
});
|
|
239
|
+
Cypress.Commands.add('GLO_servoyLogin', (company) => {
|
|
240
|
+
if (cypressWebsocketLoader) {
|
|
241
|
+
cy.WS_visitWithSpy(Cypress.expose('globisUrl'));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
cy.visit(Cypress.expose('globisUrl'));
|
|
245
|
+
}
|
|
246
|
+
cy.GLO_checkLoadingIndicator();
|
|
247
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'txtUser'], { timeout: 30000 })
|
|
248
|
+
.find('input')
|
|
249
|
+
.eq(0)
|
|
250
|
+
.clear()
|
|
251
|
+
.type(Cypress.expose('username'));
|
|
252
|
+
cy.env(['password']).then(({ password }) => {
|
|
253
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'txtPass'])
|
|
254
|
+
.find('input')
|
|
255
|
+
.eq(0)
|
|
256
|
+
.clear()
|
|
257
|
+
.type(password, { log: false });
|
|
258
|
+
});
|
|
259
|
+
cy.GLO_addInput(['ng2$svy_nav_login', 'owner'], Cypress.expose('ownerName'));
|
|
260
|
+
cy.GLO_getElement(['ng2$svy_nav_login', 'btnLogin']).click();
|
|
261
|
+
cy.GLO_checkLoadingIndicator();
|
|
262
|
+
if (company) {
|
|
263
|
+
cy.GLO_selectCompany(company);
|
|
264
|
+
}
|
|
265
|
+
cy.get('[data-cy="ng$main_form2.navbar_top"]', { timeout: 120000 }).should('exist');
|
|
266
|
+
});
|
|
267
|
+
Cypress.Commands.add('GLO_clearData', (formulaName) => {
|
|
268
|
+
cy.GLO_searchHeader('Formulas', 'Formulas', 'Formulas');
|
|
269
|
+
cy.GLO_searchDataControl(formulaName);
|
|
270
|
+
cy.GLO_clickDatacontrolBrowserButton('btnEdit');
|
|
271
|
+
cy.GLO_clickButton(['ng$formula_dtl', 'btn_test_formula']);
|
|
272
|
+
cy.task('log', 'Delete records started');
|
|
273
|
+
cy.GLO_clickButton(['ng$test_formula_parameter', 'confirm']);
|
|
274
|
+
cy.GLO_clickDatacontrolBrowserButton('btnSave');
|
|
275
|
+
cy.GLO_closeDashboard();
|
|
276
|
+
cy.GLO_log('Delete records completed');
|
|
277
|
+
});
|
|
278
|
+
Cypress.Commands.add('GLO_clickButton', (selector, buttonType) => {
|
|
279
|
+
cy.GLO_getElement(selector)
|
|
280
|
+
.then(($element) => {
|
|
281
|
+
buttonType ? cy.wrap($element).find(`.${buttonType}`).click() : cy.wrap($element).click();
|
|
282
|
+
})
|
|
283
|
+
.then(() => {
|
|
284
|
+
cy.GLO_checkLoadingIndicator();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
Cypress.Commands.add('GLO_clickButtonText', (textButton) => {
|
|
288
|
+
clickButtonTextHelper(textButton, '.dx-button-content');
|
|
289
|
+
});
|
|
290
|
+
export const clickButtonTextHelper = (textButton, buttonContainer) => {
|
|
291
|
+
cy.get(buttonContainer)
|
|
292
|
+
.contains(textButton)
|
|
293
|
+
.should('not.be.disabled')
|
|
294
|
+
.click()
|
|
295
|
+
.then(() => {
|
|
296
|
+
cy.GLO_checkLoadingIndicator();
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
Cypress.Commands.add('GLO_checkField', (selector, value, operator = equal, timeOut = 10000) => {
|
|
300
|
+
if (value === undefined)
|
|
301
|
+
return;
|
|
302
|
+
const { baseSelector, index } = extractSelectorParts(selector);
|
|
303
|
+
const inGrid = isGridRowSelector(baseSelector, index);
|
|
304
|
+
const effectiveSelector = inGrid ? selector : baseSelector;
|
|
305
|
+
const effectiveIndex = inGrid ? 0 : index;
|
|
306
|
+
function assertFieldVal($input) {
|
|
307
|
+
const normalizedValue = typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : value;
|
|
308
|
+
let inputVal;
|
|
309
|
+
if (typeof value === 'number') {
|
|
310
|
+
inputVal = $input.find('input[value]').length
|
|
311
|
+
? Number($input.find('input[value]').eq(effectiveIndex).attr('value'))
|
|
312
|
+
: Number($input.find('.dx-texteditor-input').eq(effectiveIndex).val());
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
// Trim spaces before and after the value, including html space ( ) and other unicode spaces
|
|
316
|
+
inputVal = String($input.find('.dx-texteditor-input').eq(effectiveIndex).val()).replace(/\s+/g, ' ').trim();
|
|
317
|
+
}
|
|
318
|
+
if (operator === equal) {
|
|
319
|
+
expect(inputVal).to.equal(normalizedValue);
|
|
320
|
+
}
|
|
321
|
+
else if (operator === notEqual) {
|
|
322
|
+
expect(inputVal).to.not.equal(normalizedValue);
|
|
323
|
+
}
|
|
324
|
+
else if (operator === contains) {
|
|
325
|
+
expect(inputVal).to.contain(normalizedValue);
|
|
326
|
+
}
|
|
327
|
+
else if (operator === greaterEqual && typeof normalizedValue === 'number' && typeof inputVal === 'number') {
|
|
328
|
+
expect(inputVal).to.be.gte(normalizedValue);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
throw new Error('Incorrect operator for GLO_checkField. Available operators: equal, notEqual, contains');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
cy.GLO_getElement(effectiveSelector, { timeout: timeOut }).should(assertFieldVal);
|
|
335
|
+
});
|
|
336
|
+
Cypress.Commands.add('GLO_searchHeader', (solutionName, moduleName, programName) => {
|
|
337
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_top'], { timeout: 30000 }).find('.dx-texteditor-input').type(programName);
|
|
338
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_top'])
|
|
339
|
+
.find('.dx-texteditor-input')
|
|
340
|
+
.invoke('attr', 'aria-controls')
|
|
341
|
+
.then((dropDownId) => {
|
|
342
|
+
cy.contains(`#${dropDownId} .dx-scrollview-content .dx-item`, `${solutionName} - ${moduleName} - ${programName}`, { timeout: 30000 }).click();
|
|
343
|
+
});
|
|
344
|
+
cy.GLO_checkLoadingIndicator();
|
|
345
|
+
cy.GLO_log(` - Opened the following program: ${solutionName} - ${moduleName} - ${programName}`);
|
|
346
|
+
});
|
|
347
|
+
Cypress.Commands.add('GLO_searchDataControl', (elementName) => {
|
|
348
|
+
cy.GLO_getVisible('globiscomponents-data-control[name=datacontrol]')
|
|
349
|
+
.within(() => {
|
|
350
|
+
cy.get('.dx-texteditor-input').click();
|
|
351
|
+
cy.get('.dx-texteditor-input').type(`{selectall}{del}${elementName}{enter}`);
|
|
352
|
+
})
|
|
353
|
+
.then(() => {
|
|
354
|
+
cy.GLO_checkLoadingIndicator();
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
Cypress.Commands.add('GLO_checkLoadingIndicator', () => {
|
|
358
|
+
if (cypressWebsocketLoader) {
|
|
359
|
+
cy.WS_loadingCheck();
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// biome-ignore plugin: wait before checking loading indicator
|
|
363
|
+
cy.wait(1000);
|
|
364
|
+
cy.get('loading-indicator', { timeout: 1200000 }).should('not.exist');
|
|
365
|
+
cy.get('[data-cy="ng$main_form2.progress"] dx-progress-bar', { timeout: 1200000, log: false }).should('not.exist');
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
Cypress.Commands.add('GLO_clickDatacontrolBrowserButton', (buttonName, skipCheck = noSkipCheck) => {
|
|
369
|
+
const datacontrol = 'globiscomponents-data-control[name="datacontrol"]:visible';
|
|
370
|
+
const enabledButton = `${datacontrol} .item-container:has([disabled="false"]:has(#${buttonName}))`;
|
|
371
|
+
const clickAndCheck = () => {
|
|
372
|
+
cy.get(enabledButton, { timeout: 10000 }).click();
|
|
373
|
+
cy.GLO_checkLoadingIndicator();
|
|
374
|
+
// This is needed because loading indicator is unreliable
|
|
375
|
+
if (!skipCheck) {
|
|
376
|
+
switch (buttonName) {
|
|
377
|
+
case 'btnSave':
|
|
378
|
+
case 'btnCancel':
|
|
379
|
+
cy.get('.navigation-item[selected="true"] span.fa-circle.pane-changed', { timeout: 30000 }).should('not.exist');
|
|
380
|
+
break;
|
|
381
|
+
case 'btnAdd':
|
|
382
|
+
case 'btnEdit':
|
|
383
|
+
cy.get('.navigation-item[selected="true"] span.fa-circle.pane-changed', { timeout: 30000 }).should('exist');
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
if (!viewModeButtons.includes(buttonName)) {
|
|
389
|
+
clickAndCheck();
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// btnList/btnDetail are disabled once their own view is active, and absent in programs without a list/detail toggle: in both cases there is nothing to click.
|
|
393
|
+
cy.get(datacontrol, { timeout: 10000 }).then(($datacontrol) => {
|
|
394
|
+
if (!$datacontrol.find(`.item-container [disabled="false"]:has(#${buttonName})`).length) {
|
|
395
|
+
cy.GLO_log(`${buttonName} is not available or its view is already active: click skipped`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
clickAndCheck();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
Cypress.Commands.add('GLO_clickDatacontrolViewerButton', (pageName, buttonName, skipCheck = noSkipCheck) => {
|
|
402
|
+
const IgnoredButtons = ['btnMenu', 'btnDelete', 'btnEdit', 'btnSave', 'btnPrint', 'btnClearAllFilters'];
|
|
403
|
+
// Scroll Into view can't because of same page is opend twice
|
|
404
|
+
cy.GLO_getElement([`ng$${pageName}_tab`, 'datacontrol_viewer'])
|
|
405
|
+
.within(() => {
|
|
406
|
+
cy.get(`#${buttonName}`).closest('.item-container').find('[disabled="false"]', { timeout: 10000 }).click();
|
|
407
|
+
})
|
|
408
|
+
.then(() => {
|
|
409
|
+
if (!IgnoredButtons.includes(buttonName) && !skipCheck)
|
|
410
|
+
cy.get(`div[formslist][hidden] [data-cy$="ng$${pageName}_tab.datacontrol_viewer"]`, { timeout: 60000 }).should('exist');
|
|
411
|
+
cy.GLO_checkLoadingIndicator();
|
|
412
|
+
if (buttonName === 'btnSave' && !skipCheck)
|
|
413
|
+
cy.GLO_getElement([`ng$${pageName}_tab`, 'datacontrol_viewer']).find('[title="Save record"][disabled="true"]', { timeout: 60000 });
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
Cypress.Commands.add('GLO_selectCompany', (companyName) => {
|
|
417
|
+
cy.GLO_addInput(['ng2$svy_nav_login', 'company'], companyName);
|
|
418
|
+
cy.get('[data-cy="ng2$svy_nav_login.btnLogin"]').click();
|
|
419
|
+
});
|
|
420
|
+
Cypress.Commands.add('GLO_checkbox', (selector, bool) => {
|
|
421
|
+
if (bool === undefined)
|
|
422
|
+
return;
|
|
423
|
+
checkBoxHelper(selector, bool, 'dx-check-box', '.dx-checkbox-icon');
|
|
424
|
+
cy.GLO_checkLoadingIndicator();
|
|
425
|
+
});
|
|
426
|
+
Cypress.Commands.add('GLO_switch', (selector, bool) => {
|
|
427
|
+
if (bool === undefined)
|
|
428
|
+
return;
|
|
429
|
+
checkBoxHelper(selector, bool, 'dx-switch', '.dx-switch-container');
|
|
430
|
+
cy.GLO_checkLoadingIndicator();
|
|
431
|
+
});
|
|
432
|
+
const checkBoxHelper = (selector, bool, dxElement, dxElementContainer) => {
|
|
433
|
+
// Retry mechanism in case the checkbox is 3-state version and doesn't switch to the desired state on first click.
|
|
434
|
+
const retries = 3;
|
|
435
|
+
const clickElement = (remaining = retries) => {
|
|
436
|
+
return cy
|
|
437
|
+
.GLO_getElement(selector)
|
|
438
|
+
.find(dxElement)
|
|
439
|
+
.filter(':visible')
|
|
440
|
+
.then(($element) => {
|
|
441
|
+
const currentState = $element[0].getAttribute('aria-checked');
|
|
442
|
+
if (currentState === String(bool))
|
|
443
|
+
return;
|
|
444
|
+
if (remaining <= 0)
|
|
445
|
+
throw new Error('Failed to set checkbox/switch to the desired state after multiple attempts.');
|
|
446
|
+
cy.GLO_getElement(selector)
|
|
447
|
+
.find(dxElement)
|
|
448
|
+
.find(dxElementContainer)
|
|
449
|
+
.filter(':visible')
|
|
450
|
+
.click()
|
|
451
|
+
.then(() => {
|
|
452
|
+
cy.GLO_checkLoadingIndicator();
|
|
453
|
+
clickElement(remaining - 1);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
clickElement();
|
|
458
|
+
};
|
|
459
|
+
const GLO_inputValuelist = (selector, rawValue) => {
|
|
460
|
+
if (!rawValue)
|
|
461
|
+
return;
|
|
462
|
+
const selection = String(rawValue);
|
|
463
|
+
// Globis filters only the first part of a string — if '-' is present (compound valuelists) full-string filtering may fail.
|
|
464
|
+
const maxRetries = 3;
|
|
465
|
+
let attempts = 0;
|
|
466
|
+
let unsliced = false;
|
|
467
|
+
let filtered = false;
|
|
468
|
+
let dropdownValue = selection.includes('-') ? selection.split('-')[0].trim() : selection;
|
|
469
|
+
const { baseSelector: vlBaseSelector, index: vlIndex } = extractSelectorParts(selector);
|
|
470
|
+
const vlInGrid = isGridRowSelector(vlBaseSelector, vlIndex);
|
|
471
|
+
const vlEffectiveSelector = vlInGrid ? selector : vlBaseSelector;
|
|
472
|
+
const vlEffectiveIndex = vlInGrid ? 0 : vlIndex;
|
|
473
|
+
const getElement = () => cy.GLO_getElement(vlEffectiveSelector, { log: false });
|
|
474
|
+
// Helper uses cy.document() to escape any .within() scope
|
|
475
|
+
let dropDownId;
|
|
476
|
+
const getDropdown = (dropdownSelector = '', timeout) => {
|
|
477
|
+
return getElement()
|
|
478
|
+
.find('.dx-texteditor-input', { log: false })
|
|
479
|
+
.eq(vlEffectiveIndex)
|
|
480
|
+
.invoke({ log: false }, 'attr', 'aria-controls')
|
|
481
|
+
.then((newId) => {
|
|
482
|
+
if (newId)
|
|
483
|
+
dropDownId = newId;
|
|
484
|
+
const query = dropdownSelector ? `[id="${dropDownId}"] ${dropdownSelector}` : `[id="${dropDownId}"]`;
|
|
485
|
+
return cy
|
|
486
|
+
.document({ timeout: timeout ?? defaultTimeout, log: false })
|
|
487
|
+
.its('body', { log: false })
|
|
488
|
+
.find(query, { log: false });
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
cy.GLO_getElement(vlEffectiveSelector).find('.dx-texteditor-input:enabled', { log: false }).eq(vlEffectiveIndex).click();
|
|
492
|
+
getElement()
|
|
493
|
+
.find('.dx-texteditor-input:enabled', { log: false })
|
|
494
|
+
.eq(vlEffectiveIndex)
|
|
495
|
+
.should('have.attr', 'aria-controls')
|
|
496
|
+
.then((id) => {
|
|
497
|
+
dropDownId = id;
|
|
498
|
+
// Wait for dropdown to load its values.
|
|
499
|
+
getDropdown('.dx-scrollable-container')
|
|
500
|
+
.should('exist')
|
|
501
|
+
.then(($el) => {
|
|
502
|
+
if ($el[0].scrollHeight > $el[0].clientHeight) {
|
|
503
|
+
cy.wrap($el, { log: false }).invoke({ log: false }, 'scrollTop', 0);
|
|
504
|
+
cy.wrap($el, { log: false }).should(($e) => {
|
|
505
|
+
expect($e[0].scrollTop).to.eq(0);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
getDropdown(undefined, 30000).should(($el) => {
|
|
510
|
+
assert.exists($el[0]);
|
|
511
|
+
assert.notInclude($el[0].className, 'dx-empty-collection');
|
|
512
|
+
assert.isAbove($el
|
|
513
|
+
.find('.dx-item')
|
|
514
|
+
.toArray()
|
|
515
|
+
.filter((item) => (item.textContent || '').trim() !== '').length, 0);
|
|
516
|
+
});
|
|
517
|
+
getDropdown('.dx-scrollview-content .dx-item')
|
|
518
|
+
.should('exist')
|
|
519
|
+
.then(($list) => {
|
|
520
|
+
if (!clickExactMatch($list))
|
|
521
|
+
attemptDropdownSelect();
|
|
522
|
+
});
|
|
523
|
+
function attemptDropdownSelect() {
|
|
524
|
+
attempts++;
|
|
525
|
+
getDropdown('.dx-scrollview-content .dx-item').as('before', { type: 'static' });
|
|
526
|
+
cy.log(`Attempt __${attempts}__: Selecting dropdown value`);
|
|
527
|
+
getElement()
|
|
528
|
+
.find('.dx-texteditor-input:enabled', { log: false })
|
|
529
|
+
.eq(vlEffectiveIndex)
|
|
530
|
+
.then(($input) => {
|
|
531
|
+
if ($input.val() !== '') {
|
|
532
|
+
cy.GLO_clearField(selector);
|
|
533
|
+
if (filtered) {
|
|
534
|
+
// After clearing a filtered dropdown we check dropdown correctly clears and updates. We then store this cleared version as new baseline 'before'
|
|
535
|
+
cy.get('@before').then(($before) => {
|
|
536
|
+
getDropdown('.dx-scrollview-content .dx-item', 30000)
|
|
537
|
+
.should(($current) => {
|
|
538
|
+
const initialTexts = [...$before].map((el) => el.innerText.trim());
|
|
539
|
+
const currentTexts = [...$current].map((el) => el.innerText.trim());
|
|
540
|
+
const hasChanged = !Cypress._.isEqual(currentTexts, initialTexts);
|
|
541
|
+
// Also accept when the list is the same size or larger: branch-filtered valuelists
|
|
542
|
+
// with few options won't change after clearing because the full list IS the filtered list.
|
|
543
|
+
const listNotSmaller = currentTexts.length >= initialTexts.length;
|
|
544
|
+
return expect(hasChanged || listNotSmaller, 'Dropdown updated after clear').to.be.true;
|
|
545
|
+
})
|
|
546
|
+
.then(() => {
|
|
547
|
+
getDropdown('.dx-scrollview-content .dx-item').as('before', { type: 'static' });
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
cy.wrap($input, { log: false }).type(dropdownValue);
|
|
553
|
+
filtered = true;
|
|
554
|
+
});
|
|
555
|
+
// Check if the value is correctly set
|
|
556
|
+
getElement()
|
|
557
|
+
.find('.dx-texteditor-input:enabled', { log: false })
|
|
558
|
+
.eq(vlEffectiveIndex)
|
|
559
|
+
.invoke({ log: false }, 'val')
|
|
560
|
+
.then((value) => {
|
|
561
|
+
if (value !== dropdownValue) {
|
|
562
|
+
// Retry if the value is incorrect
|
|
563
|
+
if (attempts < maxRetries) {
|
|
564
|
+
filtered = false; // Wrong value check doesn't need to wait so we just retry before filtering even has to occur.
|
|
565
|
+
cy.log(`Retrying... (attempt __${attempts}__)`);
|
|
566
|
+
attemptDropdownSelect(); // Recursive call
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
throw new Error(`Failed to set dropdown value after ${maxRetries} attempts`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
// Wait for dropdown to update after typing.
|
|
574
|
+
cy.get('@before')
|
|
575
|
+
.then(($before) => {
|
|
576
|
+
getDropdown('.dx-scrollview-content .dx-item', 30000).should(($current) => {
|
|
577
|
+
const initialTexts = [...$before].map((el) => el.innerText.trim());
|
|
578
|
+
const currentTexts = [...$current].map((el) => el.innerText.trim());
|
|
579
|
+
const hasChanged = !Cypress._.isEqual(currentTexts, initialTexts);
|
|
580
|
+
const anyIncludes = currentTexts.every((text) => text.includes(dropdownValue));
|
|
581
|
+
return expect(hasChanged || anyIncludes, `Dropdown updated or contains "${dropdownValue}" already`).to.be.true;
|
|
582
|
+
});
|
|
583
|
+
})
|
|
584
|
+
.then(() => {
|
|
585
|
+
getDropdown('.dx-scrollview-content .dx-item:visible', 15000).then(($list) => {
|
|
586
|
+
// Try to click exact match; if that fails, re-run with full value instead of sliced value.
|
|
587
|
+
if (!clickExactMatch($list) && !unsliced) {
|
|
588
|
+
dropdownValue = selection;
|
|
589
|
+
attempts = 0;
|
|
590
|
+
unsliced = true;
|
|
591
|
+
attemptDropdownSelect();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
const clickExactMatch = ($list) => {
|
|
599
|
+
const normalizeText = (el) => Cypress.$(el).text().replace(/\s+/g, ' ').trim();
|
|
600
|
+
const normalizedTarget = selection.replace(/\s+/g, ' ').trim();
|
|
601
|
+
const $filteredList = $list.filter((_, el) => normalizeText(el) !== '');
|
|
602
|
+
const exactMatch = $filteredList.filter((_, el) => normalizeText(el) === normalizedTarget);
|
|
603
|
+
if ($filteredList.length === 0) {
|
|
604
|
+
cy.log(`__No options available in dropdown to select "${selection}"__`);
|
|
605
|
+
throw new Error(`No options available in dropdown to select "${selection}"`);
|
|
606
|
+
}
|
|
607
|
+
// If _all_ values of the dropdown currently already contain targetText, we can assume to click the partial match since no further filtering will happen.
|
|
608
|
+
const allContain = $filteredList.length > 0 && $filteredList.toArray().every((el) => normalizeText(el).toLowerCase().includes(normalizedTarget.toLowerCase()));
|
|
609
|
+
const hasMatch = exactMatch.length > 0 || allContain;
|
|
610
|
+
if (hasMatch) {
|
|
611
|
+
const matchedText = exactMatch.length > 0 ? normalizeText(exactMatch[0]) : normalizeText($filteredList[0]);
|
|
612
|
+
cy.log(`__Clicking ${exactMatch.length > 0 ? 'exact' : 'partial'} match: "${matchedText}"__`);
|
|
613
|
+
// Using cy.wrap(match[0]) can give issue that element updated, so we need to 're-find' it
|
|
614
|
+
getDropdown('.dx-scrollview-content .dx-item')
|
|
615
|
+
.filter((_, el) => (exactMatch.length > 0 ? normalizeText(el) === normalizedTarget : normalizeText(el).includes(normalizedTarget)))
|
|
616
|
+
.first()
|
|
617
|
+
.click({ log: false });
|
|
618
|
+
getDropdown('.dx-overlay-content.dx-popup-normal[role="dialog"]').should('not.exist'); // Ensure the dropdown is completely handled/closed after selection
|
|
619
|
+
getElement().then(($el) => {
|
|
620
|
+
if (!$el.find('.dx-tagbox').length)
|
|
621
|
+
cy.GLO_checkField(selector, selection, exactMatch.length === 0 ? contains : undefined);
|
|
622
|
+
});
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
cy.log(`__No match found for "${selection}"__`);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
};
|
|
632
|
+
Cypress.Commands.add('GLO_closeDashboard', () => {
|
|
633
|
+
cy.task('get', ['testFailed'], { log: false }).then(({ testFailed }) => {
|
|
634
|
+
if (testFailed)
|
|
635
|
+
return;
|
|
636
|
+
cy.get('.navigation-item-close', { timeout: 10000 }).click();
|
|
637
|
+
cy.GLO_getVisible('.svy-message-dialog', 30000).should('exist');
|
|
638
|
+
cy.get('.svy-message-dialog').should(($dialog) => {
|
|
639
|
+
expect($dialog.find('.window-title').text().trim()).to.match(/^(Confirm|Bevestigen)$/);
|
|
640
|
+
expect($dialog.find('.window-body').text().trim()).to.match(/^(Close all programs\?|Sluit alle programma's\?)$/);
|
|
641
|
+
});
|
|
642
|
+
cy.get('.svy-message-dialog')
|
|
643
|
+
.find('.svy-btn-primary')
|
|
644
|
+
.contains(/^(Yes|Ja)$/)
|
|
645
|
+
.should('exist')
|
|
646
|
+
.click();
|
|
647
|
+
cy.GLO_checkLoadingIndicator();
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
Cypress.Commands.add('GLO_selectTab', (selector, tabName, openOrClose) => {
|
|
651
|
+
cy.GLO_getElement(selector)
|
|
652
|
+
.contains('[tabcontainer] .tab', tabName)
|
|
653
|
+
.then(($tab) => {
|
|
654
|
+
const isOpenAttr = $tab[0].getAttribute('open'); // Attribute is a string 'true' or missing -> null
|
|
655
|
+
const isOpen = isOpenAttr === 'true'; // Convert to boolean
|
|
656
|
+
if (openOrClose !== isOpen) {
|
|
657
|
+
const action = openOrClose ? 'Opening' : 'Closing';
|
|
658
|
+
cy.log(`${action} tab ${tabName}`);
|
|
659
|
+
cy.wrap($tab).click();
|
|
660
|
+
cy.GLO_checkLoadingIndicator();
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
cy.log(`Tab ${tabName} is already ${isOpen ? 'open' : 'closed'}.`);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
Cypress.Commands.add('GLO_selectMenuButton', (buttonName, exactMatch = noEqualBool) => {
|
|
668
|
+
const searchParam = exactMatch ? new RegExp(`^\\s*${Cypress._.escapeRegExp(buttonName)}\\s*$`) : buttonName;
|
|
669
|
+
cy.GLO_clickDatacontrolBrowserButton('btnMenu');
|
|
670
|
+
cy.GLO_getVisible('.dx-submenu', 10000)
|
|
671
|
+
.contains(searchParam)
|
|
672
|
+
.click()
|
|
673
|
+
.then(() => {
|
|
674
|
+
cy.GLO_checkLoadingIndicator();
|
|
675
|
+
});
|
|
676
|
+
cy.GLO_log(` - Used workflow button '${buttonName}'`);
|
|
677
|
+
});
|
|
678
|
+
Cypress.Commands.add('GLO_selectMenuButtonControlView', (tabName, buttonName, exactMatch = noEqualBool) => {
|
|
679
|
+
const searchParam = exactMatch ? new RegExp(`^\\s*${Cypress._.escapeRegExp(buttonName)}\\s*$`) : buttonName;
|
|
680
|
+
cy.GLO_clickDatacontrolViewerButton(tabName, 'btnMenu');
|
|
681
|
+
cy.GLO_getVisible('.dx-submenu', 10000)
|
|
682
|
+
.contains(searchParam)
|
|
683
|
+
.click()
|
|
684
|
+
.then(() => {
|
|
685
|
+
cy.GLO_checkLoadingIndicator();
|
|
686
|
+
});
|
|
687
|
+
cy.GLO_log(` - Used workflow button '${buttonName}'`);
|
|
688
|
+
});
|
|
689
|
+
const navItem = (topBottom, programName, tabNumber, mode) => {
|
|
690
|
+
if (topBottom !== top && topBottom !== bottom)
|
|
691
|
+
throw new Error(`Incorrect type for GLO_${mode}Program. Available types: top, bottom`);
|
|
692
|
+
const location = topBottom === top ? 0 : 1;
|
|
693
|
+
const regex = new RegExp(`^${Cypress._.escapeRegExp(programName)}$`);
|
|
694
|
+
cy.get('drag-scroll')
|
|
695
|
+
.eq(location)
|
|
696
|
+
.within(() => {
|
|
697
|
+
const items = cy
|
|
698
|
+
.get('.navigation-item:not([hidden]) span:not([close-btn])')
|
|
699
|
+
.filter((_, el) => regex.test(el.textContent.trim()))
|
|
700
|
+
.parent();
|
|
701
|
+
const item = tabNumber !== undefined ? items.eq(tabNumber) : items.last();
|
|
702
|
+
if (mode === 'close')
|
|
703
|
+
item.find('[close-btn]').click();
|
|
704
|
+
else
|
|
705
|
+
item.find('span:not([close-btn])').first().click();
|
|
706
|
+
});
|
|
707
|
+
cy.GLO_checkLoadingIndicator();
|
|
708
|
+
};
|
|
709
|
+
Cypress.Commands.add('GLO_closeProgram', (topBottom, programName, tabNumber) => {
|
|
710
|
+
navItem(topBottom, programName, tabNumber, 'close');
|
|
711
|
+
cy.GLO_log(` - Closed program tab ${programName}`);
|
|
712
|
+
});
|
|
713
|
+
Cypress.Commands.add('GLO_dialogChoice', (confirmation) => {
|
|
714
|
+
// biome-ignore plugin: wait for dialog choice body to render
|
|
715
|
+
cy.wait(500);
|
|
716
|
+
cy.get('body').then(($body) => {
|
|
717
|
+
let CSSSearchStr;
|
|
718
|
+
if ($body.find('.svy-message-dialog').length > 0) {
|
|
719
|
+
CSSSearchStr = '.svy-message-dialog button';
|
|
720
|
+
}
|
|
721
|
+
else if ($body.find('.servoycore-dialog-body').length > 0) {
|
|
722
|
+
// Previous version
|
|
723
|
+
CSSSearchStr = '.servoycore-dialog-body button';
|
|
724
|
+
}
|
|
725
|
+
else if ($body.find('.svy-dialog.window').length > 0) {
|
|
726
|
+
// Another variant
|
|
727
|
+
CSSSearchStr = '.svy-dialog.window globiscomponents-button';
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
cy.GLO_log('Popup not found, skipping dialog option');
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if ($body.find('.window-title.type-error').length > 0 && $body.find('.window-title.type-error').is(':visible')) {
|
|
734
|
+
cy.GLO_log('Error dialog, failing test. use GLO_dialogRequiredCheck.');
|
|
735
|
+
cy.wrap(null).should(() => {
|
|
736
|
+
throw new Error('Error dialog, failing test. use GLO_dialogRequiredCheck.');
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
let found = false;
|
|
740
|
+
cy.get(CSSSearchStr)
|
|
741
|
+
.each(($btn) => {
|
|
742
|
+
if ($btn.text().includes(confirmation) && !found) {
|
|
743
|
+
found = true; // Skip other buttons when button has been found
|
|
744
|
+
cy.wrap($btn)
|
|
745
|
+
.contains(confirmation)
|
|
746
|
+
.click()
|
|
747
|
+
.then(() => {
|
|
748
|
+
cy.GLO_checkLoadingIndicator();
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
})
|
|
752
|
+
.then(() => {
|
|
753
|
+
if (!found) {
|
|
754
|
+
cy.GLO_log('Popup button not found, skipping dialog option');
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
Cypress.Commands.add('GLO_addInputDialog', (text) => {
|
|
760
|
+
cy.get('.svy-message-dialog .window-body form', { timeout: 15000 }).then(($form) => {
|
|
761
|
+
const inputType = $form.children().first().prop('tagName').toLowerCase();
|
|
762
|
+
if (inputType === 'input') {
|
|
763
|
+
cy.wrap($form).find('input').clear().type(text);
|
|
764
|
+
}
|
|
765
|
+
else if (inputType === 'select') {
|
|
766
|
+
cy.wrap($form)
|
|
767
|
+
.find('select')
|
|
768
|
+
.select(text)
|
|
769
|
+
.should(($select) => {
|
|
770
|
+
expect(String($select.val()).trim()).to.eq(text);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
throw new Error('Unsupported input type');
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
const GLO_inputInput = (selector, value, options = {}) => {
|
|
779
|
+
const { selectAllDelete, ...typeOptions } = options;
|
|
780
|
+
const { baseSelector, index } = extractSelectorParts(selector);
|
|
781
|
+
const inGrid = isGridRowSelector(baseSelector, index);
|
|
782
|
+
cy.GLO_getElement(inGrid ? selector : baseSelector, { log: false, ...typeOptions }).then(($el) => {
|
|
783
|
+
const $target = $el.find('.dx-texteditor-input:not([disabled])').eq(inGrid ? 0 : index);
|
|
784
|
+
const isTextarea = $target.is('textarea');
|
|
785
|
+
if (selectAllDelete)
|
|
786
|
+
cy.wrap($target, { log: false }).clear();
|
|
787
|
+
else
|
|
788
|
+
cy.GLO_clearField(selector);
|
|
789
|
+
cy.wrap($target, { log: false }).type(value, typeOptions);
|
|
790
|
+
if (!isTextarea)
|
|
791
|
+
cy.wrap($target, { log: false }).type('{enter}');
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
const GLO_inputMonaco = (dataUri, value) => {
|
|
795
|
+
cy.window().then((win) => {
|
|
796
|
+
const monaco = win.monaco;
|
|
797
|
+
const model = monaco.editor.getModels().find((m) => m.uri.toString() === dataUri);
|
|
798
|
+
if (model)
|
|
799
|
+
model.setValue(value);
|
|
800
|
+
else
|
|
801
|
+
throw new Error(`No monaco model found for ${dataUri}`);
|
|
802
|
+
});
|
|
803
|
+
};
|
|
804
|
+
Cypress.Commands.add('GLO_addInput', (selector, value, options) => {
|
|
805
|
+
if (value === undefined)
|
|
806
|
+
return;
|
|
807
|
+
else if (typeof value === 'number')
|
|
808
|
+
value = String(value);
|
|
809
|
+
else if (typeof value === 'object')
|
|
810
|
+
value = JSON.stringify(value);
|
|
811
|
+
options = options || {};
|
|
812
|
+
const { selectAllDelete, ...getOptions } = options;
|
|
813
|
+
const { baseSelector, index } = extractSelectorParts(selector);
|
|
814
|
+
const inGrid = isGridRowSelector(baseSelector, index);
|
|
815
|
+
cy.GLO_getElement(inGrid ? selector : baseSelector, getOptions).then(($el) => {
|
|
816
|
+
const isDropdown = $el
|
|
817
|
+
.find('.dx-texteditor-input:not([disabled])')
|
|
818
|
+
.eq(inGrid ? 0 : index)
|
|
819
|
+
.closest('dx-select-box, dx-tag-box').length > 0;
|
|
820
|
+
if ($el.parent().is('globiscomponents-monacoeditor')) {
|
|
821
|
+
if (selectAllDelete)
|
|
822
|
+
throw new Error('Error: selectAllDelete option is not applicable for monaco inputs and will be ignored.');
|
|
823
|
+
cy.wrap($el, { timeout: 10000 })
|
|
824
|
+
.find('.monaco-editor', { timeout: 10000 })
|
|
825
|
+
.invoke({ timeout: 10000 }, 'attr', 'data-uri')
|
|
826
|
+
.should('exist')
|
|
827
|
+
.then((dataUri) => {
|
|
828
|
+
GLO_inputMonaco(String(dataUri), value);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
else if (isDropdown) {
|
|
832
|
+
if (selectAllDelete)
|
|
833
|
+
throw new Error('Error: selectAllDelete option is not applicable for valuelist inputs and will be ignored.');
|
|
834
|
+
GLO_inputValuelist(selector, value);
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
GLO_inputInput(selector, value, options);
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
Cypress.Commands.add('GLO_addDate', (type, selector, value) => {
|
|
842
|
+
if (!value)
|
|
843
|
+
return;
|
|
844
|
+
const dateParts = value.split('/');
|
|
845
|
+
switch (type) {
|
|
846
|
+
case input:
|
|
847
|
+
cy.GLO_addInput(selector, value);
|
|
848
|
+
break;
|
|
849
|
+
case select: // Select from popup
|
|
850
|
+
if (dateParts[0].length === 1)
|
|
851
|
+
dateParts[0] = `0${dateParts[0]}`;
|
|
852
|
+
cy.GLO_getElement(selector).find('.dx-dropdowneditor-icon').click();
|
|
853
|
+
// Select correct year
|
|
854
|
+
selectYear(Number(dateParts[2]));
|
|
855
|
+
// Select correct month
|
|
856
|
+
selectMonth(Number(dateParts[1]));
|
|
857
|
+
cy.GLO_getVisible('.dx-calendar-body')
|
|
858
|
+
.last()
|
|
859
|
+
.within(() => {
|
|
860
|
+
cy.get('tr:visible').find(`[data-value="${dateParts[2]}/${dateParts[1]}/${dateParts[0]}"]`).filter(':not(".dx-calendar-other-month")').click();
|
|
861
|
+
});
|
|
862
|
+
break;
|
|
863
|
+
default:
|
|
864
|
+
throw new Error('Incorrect type for GLO_addDate. Available types: input, inputName, select, selectName');
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
Cypress.Commands.add('GLO_uploadFile', (selector, relativePath) => {
|
|
868
|
+
cy.GLO_getElement(selector)
|
|
869
|
+
.find('input[type=file]')
|
|
870
|
+
.selectFile(relativePath, { force: true })
|
|
871
|
+
.then(() => {
|
|
872
|
+
cy.GLO_checkLoadingIndicator();
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
Cypress.Commands.add('GLO_uploadFileDialog', (relativePath) => {
|
|
876
|
+
cy.GLO_getVisible('div.svy-upload.active').find('input[type=file]').selectFile(relativePath, { force: true, action: 'drag-drop' });
|
|
877
|
+
cy.GLO_checkLoadingIndicator();
|
|
878
|
+
cy.GLO_getVisible('button.svy-upload-btn-upload').click();
|
|
879
|
+
cy.GLO_checkLoadingIndicator();
|
|
880
|
+
});
|
|
881
|
+
Cypress.Commands.add('GLO_filterGrid', (colId, selector, text, exactMatch = noEqualBool, filterType) => {
|
|
882
|
+
if (text === undefined)
|
|
883
|
+
return;
|
|
884
|
+
else if (typeof text === 'object')
|
|
885
|
+
text = JSON.stringify(text);
|
|
886
|
+
const textString = String(text);
|
|
887
|
+
const searchParam = exactMatch ? new RegExp(`^\\s*${Cypress._.escapeRegExp(textString)}\\s*$`) : textString;
|
|
888
|
+
cy.GLO_getElement(selector).within(() => {
|
|
889
|
+
cy.get(`.ag-header-cell[col-id="${colId}"]`).as('filterHeaderCell');
|
|
890
|
+
cy.get('@filterHeaderCell').find('input:not(:where([hidden] *))').filter(':enabled').clear().as('COM_filterGridInput');
|
|
891
|
+
if (text !== '')
|
|
892
|
+
cy.get('@COM_filterGridInput').type(textString);
|
|
893
|
+
});
|
|
894
|
+
cy.get('@COM_filterGridInput').then(($element) => {
|
|
895
|
+
if ($element[0].getAttribute('role') === 'combobox') {
|
|
896
|
+
cy.get('ngb-typeahead-window').find('button').contains(searchParam).click();
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
cy.get('@COM_filterGridInput').type('{enter}');
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
if (filterType) {
|
|
903
|
+
cy.get('@filterHeaderCell').find('button').click();
|
|
904
|
+
cy.get('.ag-filter-menu .ag-filter-select .ag-picker-field-display').click();
|
|
905
|
+
// biome-ignore plugin: special dropdown for filter, doesn't work without small wait
|
|
906
|
+
cy.wait(250); // Special dropdown for filter, doesn't work without small wait..
|
|
907
|
+
cy.get('.ag-filter-menu .ag-picker-expanded')
|
|
908
|
+
.invoke('attr', 'aria-controls')
|
|
909
|
+
.then((dropDownId) => {
|
|
910
|
+
cy.get(`[id="${dropDownId}"]`)
|
|
911
|
+
.should('be.visible')
|
|
912
|
+
.within(() => {
|
|
913
|
+
cy.get('.ag-list-item').contains(filterType).should('be.visible').parent().click();
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
cy.GLO_checkAGLoader();
|
|
917
|
+
cy.get('.ag-filter-menu .ag-filter-select .ag-picker-field-display').should('contain.text', filterType);
|
|
918
|
+
cy.press(Cypress.Keyboard.Keys.ESC); // Close the filter menu
|
|
919
|
+
}
|
|
920
|
+
cy.GLO_checkLoadingIndicator();
|
|
921
|
+
cy.GLO_checkAGLoader();
|
|
922
|
+
});
|
|
923
|
+
Cypress.Commands.add('GLO_filterGridCustomView', (selector, headerName, text) => {
|
|
924
|
+
cy.GLO_getElement(selector).within(() => {
|
|
925
|
+
cy.contains('div.ag-header-cell', headerName)
|
|
926
|
+
.invoke('attr', 'aria-colindex')
|
|
927
|
+
.then((ariaColIndex) => {
|
|
928
|
+
cy.get('.ag-header-row-filter').find(`[aria-colindex="${ariaColIndex}"]`).find('input').filter(':enabled').clear().as('COM_filterGridInput');
|
|
929
|
+
if (text !== undefined && text !== '')
|
|
930
|
+
cy.get('.ag-header-row-filter').find(`[aria-colindex="${ariaColIndex}"]`).find('input').filter(':enabled').type(String(text));
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
cy.get('body').then(($body) => {
|
|
934
|
+
if ($body.find('ngb-typeahead-window').length > 0) {
|
|
935
|
+
cy.get('ngb-typeahead-window').find('button').contains(String(text)).click();
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
cy.GLO_checkAGLoader();
|
|
939
|
+
});
|
|
940
|
+
Cypress.Commands.add('GLO_switchCompany', (companyName) => {
|
|
941
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_top']).find('#userroles-dropdown').click();
|
|
942
|
+
cy.get('.dx-submenu').find(span).contains('Switch company').click();
|
|
943
|
+
cy.GLO_checkLoadingIndicator();
|
|
944
|
+
cy.get('.svy-dialog').find('.dx-dropdowneditor-icon').click();
|
|
945
|
+
cy.GLO_getVisible('.dx-scrollview-content').contains(companyName).click();
|
|
946
|
+
cy.GLO_clickButton(['ng$login_companies_dlg', 'btn_save']);
|
|
947
|
+
});
|
|
948
|
+
Cypress.Commands.add('GLO_getRole', (aliasName) => {
|
|
949
|
+
cy.GLO_getVisible('[userrole]').invoke('text').as(aliasName, { type: 'static' });
|
|
950
|
+
});
|
|
951
|
+
Cypress.Commands.add('GLO_checkValueGrid', (value, rowFilter, columnId, timeOut = 10000, interval, operator) => {
|
|
952
|
+
if (timeOut > 60000 && !interval)
|
|
953
|
+
interval = 10000;
|
|
954
|
+
validateGridValue(value, undefined, rowFilter, columnId, timeOut, interval, operator);
|
|
955
|
+
});
|
|
956
|
+
const selectYear = (targetYear) => {
|
|
957
|
+
cy.GLO_getVisible('.dx-calendar-navigator')
|
|
958
|
+
.last()
|
|
959
|
+
.find('span')
|
|
960
|
+
.invoke('text')
|
|
961
|
+
.then((currentDateJQuery) => {
|
|
962
|
+
const currentDate = currentDateJQuery;
|
|
963
|
+
const currentYear = Number(currentDate.split(' ')[1]);
|
|
964
|
+
if (currentYear < targetYear) {
|
|
965
|
+
cy.GLO_getVisible('i.dx-icon.dx-icon-chevronright').last().click();
|
|
966
|
+
// biome-ignore plugin: wait for year navigation click to register
|
|
967
|
+
cy.wait(500);
|
|
968
|
+
selectYear(targetYear);
|
|
969
|
+
}
|
|
970
|
+
else if (currentYear > targetYear) {
|
|
971
|
+
cy.GLO_getVisible('i.dx-icon.dx-icon-chevronleft').last().click();
|
|
972
|
+
// biome-ignore plugin: wait for year navigation click to register
|
|
973
|
+
cy.wait(500);
|
|
974
|
+
selectYear(targetYear);
|
|
975
|
+
}
|
|
976
|
+
// If Correct year do nothing
|
|
977
|
+
});
|
|
978
|
+
};
|
|
979
|
+
const selectMonth = (targetMonth) => {
|
|
980
|
+
cy.GLO_getVisible('.dx-calendar-navigator')
|
|
981
|
+
.last()
|
|
982
|
+
.find('span')
|
|
983
|
+
.invoke('text')
|
|
984
|
+
.then((currentDateJQuery) => {
|
|
985
|
+
const currentDate = currentDateJQuery;
|
|
986
|
+
const currentMonth = currentDate.split(' ')[0];
|
|
987
|
+
const currenMonthNumber = monthMap.get(currentMonth);
|
|
988
|
+
if (!currenMonthNumber)
|
|
989
|
+
throw new Error('Given month is invalid');
|
|
990
|
+
if (currenMonthNumber < targetMonth) {
|
|
991
|
+
cy.GLO_getVisible('i.dx-icon.dx-icon-chevronright').last().click();
|
|
992
|
+
// biome-ignore plugin: wait for month navigation click to register
|
|
993
|
+
cy.wait(500);
|
|
994
|
+
selectMonth(targetMonth);
|
|
995
|
+
}
|
|
996
|
+
else if (currenMonthNumber > targetMonth) {
|
|
997
|
+
cy.GLO_getVisible('i.dx-icon.dx-icon-chevronleft').last().click();
|
|
998
|
+
// biome-ignore plugin: wait for month navigation click to register
|
|
999
|
+
cy.wait(500);
|
|
1000
|
+
selectMonth(targetMonth);
|
|
1001
|
+
}
|
|
1002
|
+
// If Correct month do nothing
|
|
1003
|
+
});
|
|
1004
|
+
};
|
|
1005
|
+
Cypress.Commands.add('GLO_selectProgram', (topBottom, programName, tabnumber) => {
|
|
1006
|
+
navItem(topBottom, programName, tabnumber, 'select');
|
|
1007
|
+
});
|
|
1008
|
+
Cypress.Commands.add('GLO_getRowInGrid', (rowFilter) => {
|
|
1009
|
+
const rows = cy.get('.ag-center-cols-container [row-index]').filter(isInOpenContainer);
|
|
1010
|
+
return typeof rowFilter === 'number' ? rows.eq(rowFilter) : rows.filter(`:contains("${rowFilter}")`).first();
|
|
1011
|
+
});
|
|
1012
|
+
Cypress.Commands.add('GLO_selectRowInGrid', (rowFilter) => {
|
|
1013
|
+
let found = false;
|
|
1014
|
+
if (typeof rowFilter === 'number') {
|
|
1015
|
+
cy.get(`[row-id="root_${rowFilter}"]`).filter(isInOpenContainer).find('[data-ref="eInput"]').click();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
// Convert to array if it's a string
|
|
1019
|
+
const filters = typeof rowFilter === 'string' ? [rowFilter] : rowFilter;
|
|
1020
|
+
// Iterate through each row and check if it matches the filter
|
|
1021
|
+
cy.get('[row-index]')
|
|
1022
|
+
.filter(isInOpenContainer)
|
|
1023
|
+
.each(($row) => {
|
|
1024
|
+
const cellTexts = Array.from($row.find('.ag-cell-value')).map((el) => el.innerText.trim());
|
|
1025
|
+
// Check to see if all requested values are present in any of the cells.
|
|
1026
|
+
const matches = filters.every((filterValue) => cellTexts.some((val) => val.includes(filterValue)));
|
|
1027
|
+
if (matches) {
|
|
1028
|
+
found = true;
|
|
1029
|
+
const rowId = $row.attr('row-id');
|
|
1030
|
+
// No isInOpenContainer here: `.ag-root` is the grid of $row, which already passed that
|
|
1031
|
+
// filter above, so everything inside shares its ancestors. The re-query is only needed
|
|
1032
|
+
// because the checkbox lives in the pinned-left twin carrying the same row-id.
|
|
1033
|
+
cy.wrap($row)
|
|
1034
|
+
.closest('.ag-root')
|
|
1035
|
+
.within(() => {
|
|
1036
|
+
cy.get(`[row-id="${rowId}"]`).find('[data-ref="eInput"]').click();
|
|
1037
|
+
});
|
|
1038
|
+
return false; // Stop after the first match
|
|
1039
|
+
}
|
|
1040
|
+
})
|
|
1041
|
+
.then(() => {
|
|
1042
|
+
if (!found) {
|
|
1043
|
+
throw new Error(`No row found matching all of the following values: ${filters.join(', ')}`);
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
});
|
|
1047
|
+
Cypress.Commands.add('GLO_dblClickRowInGrid', (type, selector, value, columnId, skipCheck = noSkipCheck, equals = noEqualBool) => {
|
|
1048
|
+
if (value === undefined)
|
|
1049
|
+
return;
|
|
1050
|
+
cy.GLO_getElement(selector)
|
|
1051
|
+
.within(() => {
|
|
1052
|
+
dblClickRowInGrid(type, value, columnId, equals);
|
|
1053
|
+
})
|
|
1054
|
+
.then(() => {
|
|
1055
|
+
if (!skipCheck) {
|
|
1056
|
+
cy.get('@COM_compId')
|
|
1057
|
+
.then((compId) => {
|
|
1058
|
+
cy.get(`[row-index][comp-id="${compId}"]`, { timeout: 300000 }).should('not.be.visible');
|
|
1059
|
+
})
|
|
1060
|
+
.then(() => {
|
|
1061
|
+
cy.GLO_checkLoadingIndicator();
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
cy.GLO_checkLoadingIndicator();
|
|
1066
|
+
}
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
/**
|
|
1070
|
+
*
|
|
1071
|
+
* This a a helper function that will handle all action inside the within
|
|
1072
|
+
* This is seperated because somtimes we don't need to be inside a within see popupwindow
|
|
1073
|
+
*/
|
|
1074
|
+
const dblClickRowInGrid = (type, value, columnId, equals) => {
|
|
1075
|
+
const saveValues = ($div) => {
|
|
1076
|
+
const rowIndex = $div.attr('row-index');
|
|
1077
|
+
const compId = $div.attr('comp-id');
|
|
1078
|
+
const selector = `[row-index="${rowIndex}"][comp-id="${compId}"]`;
|
|
1079
|
+
cy.wrap(selector).as('COM_selectedRowSelector');
|
|
1080
|
+
cy.wrap(compId).as('COM_compId');
|
|
1081
|
+
};
|
|
1082
|
+
const helper = (filterSelector) => {
|
|
1083
|
+
if (columnId) {
|
|
1084
|
+
cy.get('[row-index]')
|
|
1085
|
+
.find(`[col-id="${columnId}"]`)
|
|
1086
|
+
.filter(filterSelector, { timeout: 10000 })
|
|
1087
|
+
.parentsUntil('.ag-center-cols-container')
|
|
1088
|
+
.filter('[row-index]')
|
|
1089
|
+
.first()
|
|
1090
|
+
.then(saveValues);
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
cy.get('[row-index]').filter(filterSelector, { timeout: 10000 }).first().then(saveValues);
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
cy.GLO_checkAGLoader();
|
|
1097
|
+
switch (type) {
|
|
1098
|
+
case row:
|
|
1099
|
+
cy.GLO_getRowInGrid(Number(value)).then(saveValues);
|
|
1100
|
+
break;
|
|
1101
|
+
case span:
|
|
1102
|
+
//* Equals(not working for Array)
|
|
1103
|
+
if (equals) {
|
|
1104
|
+
if (Array.isArray(value))
|
|
1105
|
+
throw new Error('Cannot use an array together with equals');
|
|
1106
|
+
const regex = new RegExp(`^\\s*${String(value)}\\s*$`);
|
|
1107
|
+
cy.get('[row-index]:visible')
|
|
1108
|
+
.find(columnId ? `[col-id="${columnId}"] ${span}` : span)
|
|
1109
|
+
.contains(regex, { timeout: 10000 })
|
|
1110
|
+
.parentsUntil('.ag-center-cols-container')
|
|
1111
|
+
.filter('[row-index]')
|
|
1112
|
+
.first()
|
|
1113
|
+
.then(saveValues);
|
|
1114
|
+
}
|
|
1115
|
+
else if (Array.isArray(value)) {
|
|
1116
|
+
//* Array
|
|
1117
|
+
if (columnId)
|
|
1118
|
+
throw new Error('Cannot use an array together with columnId');
|
|
1119
|
+
const spansArray = String(value)
|
|
1120
|
+
.split(',')
|
|
1121
|
+
.map((span) => span.trim());
|
|
1122
|
+
const filterSelector = spansArray.map((span) => `:contains("${span}")`).join('');
|
|
1123
|
+
helper(filterSelector);
|
|
1124
|
+
}
|
|
1125
|
+
else {
|
|
1126
|
+
//* One span
|
|
1127
|
+
helper(`:contains("${value}")`);
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
default:
|
|
1131
|
+
throw new Error('Incorrect type for GLO_dblClickRowInGrid. Available types: row, span');
|
|
1132
|
+
}
|
|
1133
|
+
// The actual dblClick on the row
|
|
1134
|
+
cy.get('@COM_selectedRowSelector').then((selector) => {
|
|
1135
|
+
columnId ? cy.get(selector).find(`[col-id="${columnId}"]`).dblclick() : cy.get(selector).find('[aria-colindex]').eq(1).dblclick();
|
|
1136
|
+
});
|
|
1137
|
+
};
|
|
1138
|
+
Cypress.Commands.add('GLO_clickRowInGrid', (value, columnId) => {
|
|
1139
|
+
if (value === undefined)
|
|
1140
|
+
throw new Error('Value missing for GLO_clickRowInGrid');
|
|
1141
|
+
columnId ? cy.GLO_getRowInGrid(value).find(`[col-id="${columnId}"]`).click() : cy.GLO_getRowInGrid(value).children().first().click();
|
|
1142
|
+
cy.GLO_checkLoadingIndicator();
|
|
1143
|
+
});
|
|
1144
|
+
// Shared popup-window search + grid selection logic used by GLO_popupWindow.
|
|
1145
|
+
// Assumes the search glass has already been clicked and the popup (`.window-body`) is open.
|
|
1146
|
+
const selectFromPopupWindow = (value, columnId, equals = noEqualBool) => {
|
|
1147
|
+
const loadTimeout = 30000; // 30 seconds
|
|
1148
|
+
// Helper consts to be able to check if grid has been updated with new data.
|
|
1149
|
+
// Window-body is popup element
|
|
1150
|
+
// Dynamically get the first columnId if there is no columnId given.
|
|
1151
|
+
const loadingColumnId = () => columnId
|
|
1152
|
+
? cy.wrap(columnId)
|
|
1153
|
+
: cy
|
|
1154
|
+
.get('.window-body:visible .ag-center-cols-container [row-index="0"] [role="gridcell"]', { timeout: 30000 })
|
|
1155
|
+
.filter((_, el) => Cypress.$(el).text().trim().length > 0)
|
|
1156
|
+
.first()
|
|
1157
|
+
.invoke('attr', 'col-id');
|
|
1158
|
+
// So we dont have to repeat the whole command everywhere, just looks better in code below.
|
|
1159
|
+
const searchData = () => loadingColumnId().then((colId) => cy.get(`.window-body [row-index] [col-id="${colId}"]`, { timeout: loadTimeout }).should(($els) => {
|
|
1160
|
+
assert.equal($els.toArray().filter((item) => (item.textContent || '').trim() === '').length, 0, 'Grid cells should not have empty values');
|
|
1161
|
+
assert.isTrue(Cypress.$('.ag-loading').length === 0 && Cypress.$('loading-indicator').length === 0, '(Grid) loadingindicator is not visible');
|
|
1162
|
+
}));
|
|
1163
|
+
// Actual function that checks if the grid has loaded and changed.
|
|
1164
|
+
const assertGridLoadedAndChanged = (beforeSearch) => ($afterSearch) => {
|
|
1165
|
+
const noLoadingIndicator = Cypress.$('loading-indicator').length === 0;
|
|
1166
|
+
const noAGLoader = Cypress.$('.ag-loading').length === 0;
|
|
1167
|
+
assert.isTrue(noAGLoader && noLoadingIndicator, '(Grid) loadingindicator is not visible');
|
|
1168
|
+
const afterTexts = Array.from($afterSearch, (el) => el.textContent.trim());
|
|
1169
|
+
const hasData = afterTexts.some((val) => !!val);
|
|
1170
|
+
const newData = !Cypress._.isEqual(afterTexts, beforeSearch);
|
|
1171
|
+
assert.isTrue(hasData && newData, 'Grid stabilized with new data');
|
|
1172
|
+
};
|
|
1173
|
+
// Get the state of the entries before searching. Save as an array of strings so its less heavy on the memory.
|
|
1174
|
+
searchData()
|
|
1175
|
+
.then(($els) => Array.from($els, (el) => el.textContent.trim()))
|
|
1176
|
+
.as('beforeSearch');
|
|
1177
|
+
cy.get('@beforeSearch').then((beforeSearch) => {
|
|
1178
|
+
// If the value is already in the visible grid we can skip the whole search.
|
|
1179
|
+
if (!beforeSearch.includes(String(value))) {
|
|
1180
|
+
cy.GLO_getElement(['svy_nav_selection', 'search_criteria']).type(String(value));
|
|
1181
|
+
cy.GLO_getElement(['svy_nav_selection', 'btn_search']).click();
|
|
1182
|
+
searchData().should(assertGridLoadedAndChanged(beforeSearch));
|
|
1183
|
+
if (columnId) {
|
|
1184
|
+
cy.GLO_getVisible('[row-index]')
|
|
1185
|
+
.find(`[col-id="${columnId}"]`)
|
|
1186
|
+
.then(($list) => {
|
|
1187
|
+
// Only fill in columnId filter if there is no exact match in said column yet.
|
|
1188
|
+
const match = $list.filter((_, el) => new RegExp(`^\\s*${Cypress._.escapeRegExp(String(value))}\\s*$`).test(Cypress.$(el).text().trim()));
|
|
1189
|
+
if (match.length !== 1) {
|
|
1190
|
+
searchData()
|
|
1191
|
+
.then(($els) => Array.from($els, (el) => el.textContent.trim()))
|
|
1192
|
+
.as('beforeSearch');
|
|
1193
|
+
cy.GLO_getVisible('.window-body').within(() => {
|
|
1194
|
+
cy.GLO_getVisible(`.ag-header-cell[col-id="${columnId}"]`)
|
|
1195
|
+
.invoke('attr', 'aria-colindex')
|
|
1196
|
+
.then((ariaColIndex) => {
|
|
1197
|
+
cy.get('.ag-header-row-filter').find(`[aria-colindex="${ariaColIndex}"]`).find('input').filter(':enabled').clear().type(String(value));
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
cy.get('body').then(($body) => {
|
|
1201
|
+
if ($body.find('ngb-typeahead-window').length > 0) {
|
|
1202
|
+
cy.get('ngb-typeahead-window').find('button').contains(String(value)).click();
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
searchData().should(assertGridLoadedAndChanged(beforeSearch));
|
|
1206
|
+
}
|
|
1207
|
+
else {
|
|
1208
|
+
cy.log('No columnId filtering needed, skipping.');
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
cy.GLO_getVisible('.window-body').within(() => {
|
|
1213
|
+
dblClickRowInGrid(span, value, columnId, equals);
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
cy.log('Value already exists in the grid, skipping search.');
|
|
1218
|
+
cy.GLO_getVisible('.window-body').within(() => {
|
|
1219
|
+
dblClickRowInGrid(span, value, columnId, equals);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
cy.get('@COM_compId')
|
|
1223
|
+
.then((compId) => {
|
|
1224
|
+
cy.get(`[row-index][comp-id="${compId}"]`, { timeout: 10000 }).should('not.exist');
|
|
1225
|
+
})
|
|
1226
|
+
.then(() => {
|
|
1227
|
+
cy.GLO_checkLoadingIndicator();
|
|
1228
|
+
});
|
|
1229
|
+
});
|
|
1230
|
+
};
|
|
1231
|
+
Cypress.Commands.add('GLO_popupWindow', (selector, value, columnId, equals = noEqualBool, useMagnifierFlag = buttonSearch) => {
|
|
1232
|
+
if (!value)
|
|
1233
|
+
return;
|
|
1234
|
+
if (useMagnifierFlag) {
|
|
1235
|
+
cy.GLO_clearField(selector);
|
|
1236
|
+
cy.GLO_clickButton(selector, 'fa-search');
|
|
1237
|
+
}
|
|
1238
|
+
else {
|
|
1239
|
+
// Clear the input itself rather than through GLO_clearField: on a selector field the
|
|
1240
|
+
// clear icon can be pressed without the value going away, and the typed value then lands
|
|
1241
|
+
// behind the old one.
|
|
1242
|
+
cy.GLO_addInput(selector, value, { selectAllDelete: true });
|
|
1243
|
+
cy.GLO_checkLoadingIndicator();
|
|
1244
|
+
}
|
|
1245
|
+
cy.get('body').then(($body) => {
|
|
1246
|
+
if ($body.find('.window-body').filter(':visible').length > 0) {
|
|
1247
|
+
selectFromPopupWindow(value, columnId, equals);
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
cy.GLO_checkField(selector, value, equals ? contains : undefined);
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
});
|
|
1254
|
+
Cypress.Commands.add('GLO_selectMenuButtonLeft', (buttonName) => {
|
|
1255
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_left']).within(() => {
|
|
1256
|
+
switch (buttonName) {
|
|
1257
|
+
case 'home':
|
|
1258
|
+
cy.get('[class="lucide lucide-house"]').click();
|
|
1259
|
+
break;
|
|
1260
|
+
case 'settings':
|
|
1261
|
+
cy.get('[class="lucide lucide-settings"]').click();
|
|
1262
|
+
break;
|
|
1263
|
+
case 'help':
|
|
1264
|
+
cy.get('[class="lucide lucide-circle-question-mark"]').click();
|
|
1265
|
+
break;
|
|
1266
|
+
case 'support':
|
|
1267
|
+
cy.get('[class="lucide lucide-headphones"]').click();
|
|
1268
|
+
break;
|
|
1269
|
+
case 'excel':
|
|
1270
|
+
cy.get('[class="lucide lucide-file-x"]').click();
|
|
1271
|
+
break;
|
|
1272
|
+
case 'info':
|
|
1273
|
+
cy.get('[class="lucide lucide-info"]').click();
|
|
1274
|
+
break;
|
|
1275
|
+
case 'refresh':
|
|
1276
|
+
cy.get('[class="lucide lucide-rotate-cw"]').click();
|
|
1277
|
+
break;
|
|
1278
|
+
case 'query':
|
|
1279
|
+
cy.get('[class="lucide lucide-database"]').click();
|
|
1280
|
+
break;
|
|
1281
|
+
case 'clients':
|
|
1282
|
+
cy.get('[class="lucide lucide-users"]').click();
|
|
1283
|
+
break;
|
|
1284
|
+
case 'users':
|
|
1285
|
+
cy.get('[class="lucide lucide-user-cog"]').click();
|
|
1286
|
+
break;
|
|
1287
|
+
case 'theme':
|
|
1288
|
+
cy.get('[class="lucide lucide-sun"]').click();
|
|
1289
|
+
break;
|
|
1290
|
+
default:
|
|
1291
|
+
throw new Error('Incorrect type for GLO_selectMenuButtonLeft. Available types: home, settings, excel, info, refresh');
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
cy.GLO_checkLoadingIndicator();
|
|
1295
|
+
});
|
|
1296
|
+
Cypress.Commands.add('GLO_changeUser', (username) => {
|
|
1297
|
+
cy.GLO_selectMenuButtonLeft('users');
|
|
1298
|
+
cy.GLO_addInput(['ng$log_in_as_other_user_dlg', 'sec_user_id'], username);
|
|
1299
|
+
cy.GLO_checkLoadingIndicator();
|
|
1300
|
+
cy.GLO_clickButton(['ng$log_in_as_other_user_dlg', 'btn_ok']);
|
|
1301
|
+
cy.get('body', { timeout: 30000 }).find('[data-cy$="ng$log_in_as_other_user_dlg.btn_ok"]', { timeout: 30000 }).should('not.exist');
|
|
1302
|
+
cy.GLO_checkLoadingIndicator();
|
|
1303
|
+
});
|
|
1304
|
+
Cypress.Commands.add('GLO_selectCustomView', (customViewName, subMenuName) => {
|
|
1305
|
+
cy.GLO_clickDatacontrolBrowserButton('btnCustomViews');
|
|
1306
|
+
if (subMenuName) {
|
|
1307
|
+
const menuPath = Array.isArray(customViewName) ? customViewName : [customViewName];
|
|
1308
|
+
const hoverThroughLevels = (path, index) => {
|
|
1309
|
+
if (index >= path.length) {
|
|
1310
|
+
const subRegex = new RegExp(`^\\s*${Cypress._.escapeRegExp(subMenuName)}\\s*$`);
|
|
1311
|
+
cy.get('.dx-menu-item-text', { timeout: 10000 })
|
|
1312
|
+
.contains(subRegex)
|
|
1313
|
+
.click()
|
|
1314
|
+
.then(() => {
|
|
1315
|
+
cy.GLO_checkLoadingIndicator();
|
|
1316
|
+
});
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
const levelRegex = new RegExp(`^\\s*${Cypress._.escapeRegExp(path[index])}\\s*$`);
|
|
1320
|
+
cy.get('.dx-menu-item', { timeout: 10000 })
|
|
1321
|
+
.contains('.dx-menu-item-text', levelRegex)
|
|
1322
|
+
.closest('.dx-menu-item')
|
|
1323
|
+
.trigger('mouseover')
|
|
1324
|
+
.should('be.visible')
|
|
1325
|
+
.then(() => {
|
|
1326
|
+
hoverThroughLevels(path, index + 1);
|
|
1327
|
+
});
|
|
1328
|
+
};
|
|
1329
|
+
hoverThroughLevels(menuPath, 0);
|
|
1330
|
+
}
|
|
1331
|
+
else {
|
|
1332
|
+
const singleViewName = Array.isArray(customViewName) ? customViewName[0] : customViewName;
|
|
1333
|
+
let searchPattern;
|
|
1334
|
+
if (singleViewName.includes('Save grid state')) {
|
|
1335
|
+
searchPattern = /^\s*Save grid state( as \.\.\.)?\s*$/;
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
searchPattern = new RegExp(`^\\s*${Cypress._.escapeRegExp(singleViewName)}\\s*$`);
|
|
1339
|
+
}
|
|
1340
|
+
cy.get('.dx-menu-item-text', { timeout: 10000 })
|
|
1341
|
+
.contains(searchPattern)
|
|
1342
|
+
.click()
|
|
1343
|
+
.then(() => {
|
|
1344
|
+
cy.GLO_checkLoadingIndicator();
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
Cypress.Commands.add('GLO_dblClickLabel', (selector, labelValue) => {
|
|
1349
|
+
cy.GLO_getElement(selector)
|
|
1350
|
+
.contains(labelValue)
|
|
1351
|
+
.dblclick()
|
|
1352
|
+
.then(() => {
|
|
1353
|
+
cy.GLO_checkLoadingIndicator();
|
|
1354
|
+
});
|
|
1355
|
+
});
|
|
1356
|
+
Cypress.Commands.add('GLO_validateCheckbox', (selector, value) => {
|
|
1357
|
+
if (value === undefined)
|
|
1358
|
+
return;
|
|
1359
|
+
cy.GLO_getElement(selector).find('dx-check-box').invoke('attr', 'aria-checked').should('eq', value);
|
|
1360
|
+
});
|
|
1361
|
+
/**
|
|
1362
|
+
* Command to check the presence of the AG Grid loader and wait for it to disappear.
|
|
1363
|
+
* It ensures that the AG Grid has finished loading.
|
|
1364
|
+
*/
|
|
1365
|
+
Cypress.Commands.add('GLO_checkAGLoader', () => {
|
|
1366
|
+
// biome-ignore plugin: wait because AGLoader is not instant
|
|
1367
|
+
cy.wait(1000); // Wait because AGLoader is not instant
|
|
1368
|
+
cy.get('.ag-loading', { timeout: 600000 }).should('not.exist');
|
|
1369
|
+
});
|
|
1370
|
+
Cypress.Commands.add('GLO_log', (logValue) => {
|
|
1371
|
+
cy.task('log', logValue);
|
|
1372
|
+
});
|
|
1373
|
+
Cypress.Commands.add('GLO_storeValue', (selector, asValue, log = hideInCommandLog) => {
|
|
1374
|
+
cy.GLO_getElement(selector)
|
|
1375
|
+
.find('.dx-texteditor-input')
|
|
1376
|
+
.invoke('val')
|
|
1377
|
+
.then((text) => {
|
|
1378
|
+
cy.wrap(text).as(asValue);
|
|
1379
|
+
if (log)
|
|
1380
|
+
cy.GLO_log(`${asValue}: ${String(text)}`);
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
Cypress.Commands.add('GLO_storeValueGrid', (selector, rowFilter, columnId, asValue, log = hideInCommandLog) => {
|
|
1384
|
+
if (typeof rowFilter === 'string') {
|
|
1385
|
+
cy.GLO_getElement(selector).within(() => {
|
|
1386
|
+
cy.GLO_getAllRowsInGrid()
|
|
1387
|
+
.filter(`:contains("${rowFilter}")`)
|
|
1388
|
+
.eq(0)
|
|
1389
|
+
.find(`[col-id="${columnId}"]`)
|
|
1390
|
+
.should('not.have.text', '')
|
|
1391
|
+
.then((cell) => {
|
|
1392
|
+
const text = cell.text().trim();
|
|
1393
|
+
cy.wrap(text).as(asValue);
|
|
1394
|
+
if (log)
|
|
1395
|
+
cy.GLO_log(`${asValue}: ${text}`);
|
|
1396
|
+
});
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
else {
|
|
1400
|
+
cy.GLO_getElement(selector).within(() => {
|
|
1401
|
+
cy.GLO_getRowInGrid(rowFilter)
|
|
1402
|
+
.find(`[col-id="${columnId}"]`)
|
|
1403
|
+
.should('not.have.text', '')
|
|
1404
|
+
.then((cell) => {
|
|
1405
|
+
const text = cell.text().trim();
|
|
1406
|
+
cy.wrap(text).as(asValue);
|
|
1407
|
+
if (log)
|
|
1408
|
+
cy.GLO_log(`${asValue}: ${text}`);
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
Cypress.Commands.add('GLO_checkNumberOfRowsInGrid', (selector, numberToValidate, lengthCondition = 'have.length', timeOut) => {
|
|
1414
|
+
const slowPollThreshold = 15000;
|
|
1415
|
+
const pollInterval = 5000;
|
|
1416
|
+
const HEADER_ROWS = 2;
|
|
1417
|
+
const getAriaRowcount = ($grid) => parseInt($grid.find('[role="grid"]').attr('aria-rowcount') || '0', 10);
|
|
1418
|
+
// Column header row + filter row, counted in the center viewport only (the pinned-left and
|
|
1419
|
+
// pinned-right containers repeat those same header rows).
|
|
1420
|
+
const headerRowCount = ($grid) => $grid.find('.ag-header-viewport .ag-header-row').length || HEADER_ROWS;
|
|
1421
|
+
const isLazyLoaded = ($grid) => {
|
|
1422
|
+
const ariaRowcount = getAriaRowcount($grid);
|
|
1423
|
+
const renderedRows = $grid.find('.ag-center-cols-container [row-index]').length;
|
|
1424
|
+
return renderedRows > 0 && (ariaRowcount === -1 || ariaRowcount > renderedRows + headerRowCount($grid));
|
|
1425
|
+
};
|
|
1426
|
+
// When a client-side column filter is active, the tab-/datacontrol-counter still shows the
|
|
1427
|
+
// unfiltered foundset total, so we must count the rendered (filtered) rows instead.
|
|
1428
|
+
const hasActiveColumnFilter = ($grid) => $grid.find('.ag-header-cell.ag-header-cell-filtered').length > 0 ||
|
|
1429
|
+
$grid.find('.ag-header-cell input').filter((_, el) => el.value.trim() !== '').length > 0;
|
|
1430
|
+
const getCountFromTabLabel = ($grid) => {
|
|
1431
|
+
const $tabs = $grid.closest('[data-cy$=".tabs"]').addBack().filter('[data-cy$=".tabs"]').add($grid.parents().filter('[data-cy]').first().find('[data-cy$=".tabs"]'));
|
|
1432
|
+
const text = $tabs.find('[open="true"] span').first().text();
|
|
1433
|
+
const match = /\((\d+)\)/.exec(text);
|
|
1434
|
+
if (!match)
|
|
1435
|
+
return null;
|
|
1436
|
+
return parseInt(match[1], 10);
|
|
1437
|
+
};
|
|
1438
|
+
const getCountFromDataControl = ($grid) => {
|
|
1439
|
+
const text = $grid.closest('.formslist').find('#txtCount').text().trim();
|
|
1440
|
+
const parts = text.split('/');
|
|
1441
|
+
if (parts.length !== 2)
|
|
1442
|
+
return null;
|
|
1443
|
+
const total = parseInt(parts[1].trim(), 10);
|
|
1444
|
+
return Number.isNaN(total) ? null : total;
|
|
1445
|
+
};
|
|
1446
|
+
// AG Grid publishes the displayed row total (header rows included, pinned "sum" rows
|
|
1447
|
+
// excluded) in aria-rowcount, so a virtualised grid still reports rows the viewport never
|
|
1448
|
+
// rendered. Verified on ng$consignment_dtl_general.grid: 25 goods -> aria-rowcount 27 with
|
|
1449
|
+
// 2 header rows, while only 12 rows exist in the DOM.
|
|
1450
|
+
const getCountFromAriaRowcount = ($grid) => {
|
|
1451
|
+
const total = getAriaRowcount($grid) - headerRowCount($grid);
|
|
1452
|
+
return total >= 0 ? total : null; // An aria-rowcount of -1 means the total is unknown (infinite row model)
|
|
1453
|
+
};
|
|
1454
|
+
// Form-level counters (#txtCount navigator, tab "(n)" label) only match a grid on list
|
|
1455
|
+
// forms. On non-lst forms with multiple grids the navigator reads the form's own (often
|
|
1456
|
+
// 0/0) foundset, so fall back to ag-grid's own row count there.
|
|
1457
|
+
const formIsListForm = /lst$/i.test(selector[0]);
|
|
1458
|
+
const getTotalCount = ($grid) => {
|
|
1459
|
+
if (!isLazyLoaded($grid))
|
|
1460
|
+
return null; // Everything is rendered: counting the DOM rows is exact
|
|
1461
|
+
if (!formIsListForm)
|
|
1462
|
+
return getCountFromAriaRowcount($grid);
|
|
1463
|
+
if (hasActiveColumnFilter($grid))
|
|
1464
|
+
return null;
|
|
1465
|
+
return getCountFromTabLabel($grid) ?? getCountFromDataControl($grid) ?? getCountFromAriaRowcount($grid);
|
|
1466
|
+
};
|
|
1467
|
+
const toNumberAssertion = (condition) => {
|
|
1468
|
+
const stripped = condition.replace('have.length', '').split('.').filter(Boolean).join('.').trim();
|
|
1469
|
+
if (!stripped)
|
|
1470
|
+
return 'equal';
|
|
1471
|
+
return `be.${stripped}`;
|
|
1472
|
+
};
|
|
1473
|
+
const runChainAssertion = (subject) => {
|
|
1474
|
+
const effectiveCondition = typeof subject === 'number' ? toNumberAssertion(lengthCondition) : lengthCondition;
|
|
1475
|
+
const parts = effectiveCondition.split('.');
|
|
1476
|
+
let assertion = expect(subject).to;
|
|
1477
|
+
let context = assertion;
|
|
1478
|
+
for (const part of parts) {
|
|
1479
|
+
context = assertion;
|
|
1480
|
+
if (!(part in assertion))
|
|
1481
|
+
throw new Error(`Invalid Chai property: ${part} in chain "${effectiveCondition}"`);
|
|
1482
|
+
assertion = assertion[part];
|
|
1483
|
+
}
|
|
1484
|
+
if (typeof assertion !== 'function')
|
|
1485
|
+
throw new Error(`${effectiveCondition} did not resolve to a function`);
|
|
1486
|
+
assertion.call(context, numberToValidate);
|
|
1487
|
+
};
|
|
1488
|
+
if (!timeOut || timeOut < slowPollThreshold) {
|
|
1489
|
+
cy.GLO_getElement(selector, { timeout: timeOut }).should(($grid) => {
|
|
1490
|
+
const tabCount = getTotalCount($grid);
|
|
1491
|
+
if (tabCount !== null) {
|
|
1492
|
+
runChainAssertion(tabCount);
|
|
1493
|
+
}
|
|
1494
|
+
else {
|
|
1495
|
+
runChainAssertion($grid.find('.ag-center-cols-container [row-index]'));
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
const maxAttempts = Math.max(1, Math.ceil(timeOut / pollInterval));
|
|
1501
|
+
let attempts = 0;
|
|
1502
|
+
let lastError;
|
|
1503
|
+
const log = Cypress.log({ name: 'checkNumberOfRowsInGrid', message: `Polling rows (${lengthCondition} ${numberToValidate})…`, autoEnd: false, timeout: 0 });
|
|
1504
|
+
const checkStatus = (resolve, reject) => {
|
|
1505
|
+
cy.GLO_getElement(selector, { timeout: timeOut }).then(($grid) => {
|
|
1506
|
+
const $rows = $grid.find('.ag-center-cols-container [row-index]');
|
|
1507
|
+
let actual = $rows.length;
|
|
1508
|
+
let passed = false;
|
|
1509
|
+
try {
|
|
1510
|
+
const tabCount = getTotalCount($grid);
|
|
1511
|
+
if (tabCount !== null) {
|
|
1512
|
+
actual = tabCount;
|
|
1513
|
+
runChainAssertion(actual);
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
runChainAssertion($rows);
|
|
1517
|
+
}
|
|
1518
|
+
passed = true;
|
|
1519
|
+
}
|
|
1520
|
+
catch (e) {
|
|
1521
|
+
lastError = e;
|
|
1522
|
+
if (e.message.includes('Invalid Chai property')) {
|
|
1523
|
+
log.end();
|
|
1524
|
+
reject(e);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (passed) {
|
|
1529
|
+
cy.log(`SUCCESS: ${actual} to ${lengthCondition} ${numberToValidate}`);
|
|
1530
|
+
log.end();
|
|
1531
|
+
resolve();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
attempts += 1;
|
|
1535
|
+
if (attempts > maxAttempts) {
|
|
1536
|
+
log.end();
|
|
1537
|
+
cy.log(`FAILURE: ${actual} to ${lengthCondition} ${numberToValidate} after ${Math.round((maxAttempts * pollInterval) / 100) / 10}s, error: ${lastError?.message || 'unknown'}`);
|
|
1538
|
+
reject(new Error(`Timed out after ${Math.round((maxAttempts * pollInterval) / 100) / 10}s: row count was ${actual}, expected "${lengthCondition} ${numberToValidate}"`));
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
// biome-ignore plugin: wait for next poll interval before checking again
|
|
1542
|
+
cy.wait(pollInterval, { log: false }).then(() => {
|
|
1543
|
+
log.set({ message: `Still polling ${actual} to ${lengthCondition} ${numberToValidate} ${attempts}/${maxAttempts}` });
|
|
1544
|
+
checkStatus(resolve, reject);
|
|
1545
|
+
});
|
|
1546
|
+
});
|
|
1547
|
+
};
|
|
1548
|
+
const promise = new Cypress.Promise((resolve, reject) => {
|
|
1549
|
+
checkStatus(resolve, reject);
|
|
1550
|
+
});
|
|
1551
|
+
cy.then(() => promise);
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
Cypress.Commands.add('GLO_getNumberOfRowsInGrid', (selector, timeOut) => {
|
|
1555
|
+
return cy.GLO_getAllRowsInGrid(selector, timeOut).its('length');
|
|
1556
|
+
});
|
|
1557
|
+
Cypress.Commands.add('GLO_clearField', (selector) => {
|
|
1558
|
+
let loadingIndicator;
|
|
1559
|
+
let skipCheck = false;
|
|
1560
|
+
let zeroDefaultingField = false;
|
|
1561
|
+
const { baseSelector, index } = extractSelectorParts(selector);
|
|
1562
|
+
const inGrid = isGridRowSelector(baseSelector, index);
|
|
1563
|
+
const effectiveSelector = inGrid ? selector : baseSelector;
|
|
1564
|
+
const effectiveIndex = inGrid ? 0 : index;
|
|
1565
|
+
const stringPartCount = baseSelector.filter((s) => typeof s === 'string').length;
|
|
1566
|
+
const getElement = () => {
|
|
1567
|
+
return stringPartCount >= 2 ? cy.GLO_getElement(effectiveSelector) : cy.GLO_getElement(effectiveSelector).find('.control-field');
|
|
1568
|
+
};
|
|
1569
|
+
const pressClearIcon = (field) => {
|
|
1570
|
+
return cy.wrap(field, { log: false }).then(($el) => {
|
|
1571
|
+
const $buttonContainers = $el.find('.dx-texteditor-buttons-container .dx-clear-button-area');
|
|
1572
|
+
const $visibleContainer = $buttonContainers.filter((_, el) => {
|
|
1573
|
+
return el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0;
|
|
1574
|
+
});
|
|
1575
|
+
const $clearIcon = $visibleContainer.eq(effectiveIndex).find('.dx-icon-clear');
|
|
1576
|
+
if ($clearIcon.length > 0) {
|
|
1577
|
+
return cy
|
|
1578
|
+
.wrap($clearIcon, { log: false })
|
|
1579
|
+
.click({ force: true })
|
|
1580
|
+
.then(() => {
|
|
1581
|
+
loadingIndicator = true;
|
|
1582
|
+
return true;
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
return cy.wrap(false);
|
|
1586
|
+
});
|
|
1587
|
+
};
|
|
1588
|
+
const clearInput = (element) => {
|
|
1589
|
+
cy.wrap(element, { log: false }).then(($el) => {
|
|
1590
|
+
const $input = $el.find('.dx-texteditor-input').eq(effectiveIndex);
|
|
1591
|
+
const tag = String($input.prop('tagName')).toLowerCase();
|
|
1592
|
+
if ($input.length === 0) {
|
|
1593
|
+
cy.GLO_log('___No input found, add correct data-cy for clearing.___');
|
|
1594
|
+
skipCheck = true;
|
|
1595
|
+
}
|
|
1596
|
+
else if (tag === 'input' && $input.val()) {
|
|
1597
|
+
cy.wrap($input, { log: false }).type('{selectall}{del}{enter}');
|
|
1598
|
+
loadingIndicator = true;
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
cy.wrap($input, { log: false }).type('{selectall}{del}');
|
|
1602
|
+
}
|
|
1603
|
+
});
|
|
1604
|
+
};
|
|
1605
|
+
const processField = () => {
|
|
1606
|
+
return getElement().then(($field) => {
|
|
1607
|
+
// Some fields get cleared to 0 instead of to '' so we need to make sure the 'clearcheck' at the end of this command will correctly check vs 0 or ''.
|
|
1608
|
+
const isDxNumberBox = $field.find('dx-number-box').length > 0;
|
|
1609
|
+
const hasVisibleNumericInput = $field.find('.dx-texteditor-input[inputmode="decimal"]').length > 0;
|
|
1610
|
+
zeroDefaultingField = isDxNumberBox && hasVisibleNumericInput;
|
|
1611
|
+
pressClearIcon($field).then((hasIconButton) => {
|
|
1612
|
+
if (!hasIconButton)
|
|
1613
|
+
clearInput($field);
|
|
1614
|
+
});
|
|
1615
|
+
});
|
|
1616
|
+
};
|
|
1617
|
+
const getAndProcessField = () => {
|
|
1618
|
+
cy.log('___Clearing field___');
|
|
1619
|
+
processField().then(() => {
|
|
1620
|
+
if (loadingIndicator) {
|
|
1621
|
+
cy.GLO_checkLoadingIndicator();
|
|
1622
|
+
}
|
|
1623
|
+
else if (!skipCheck) {
|
|
1624
|
+
const expectedValue = zeroDefaultingField ? 0 : '';
|
|
1625
|
+
cy.GLO_checkField(selector, expectedValue, equal, 30000);
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
};
|
|
1629
|
+
getAndProcessField();
|
|
1630
|
+
});
|
|
1631
|
+
Cypress.Commands.add('GLO_readFile', (fileName, fileType) => {
|
|
1632
|
+
cy.readFile(`${Cypress.config('downloadsFolder')}/${fileName}.${fileType}`);
|
|
1633
|
+
});
|
|
1634
|
+
Cypress.Commands.add('GLO_dialogRequiredCheck', (confirmation, title, content, contentMatchType = 'exact') => {
|
|
1635
|
+
cy.GLO_getVisible('.svy-message-dialog, .svy-dialog', 120000).within(($dialog) => {
|
|
1636
|
+
if ($dialog.find('.window-title.type-error:visible').length > 0) {
|
|
1637
|
+
if (typeof title === 'undefined' && typeof content === 'undefined') {
|
|
1638
|
+
cy.GLO_log('Error dialog, failing test. Explicit definition of the title or content required.');
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (title)
|
|
1642
|
+
cy.get('.window-title').should('have.text', title);
|
|
1643
|
+
if (content) {
|
|
1644
|
+
cy.get('.window-body').then(($body) => {
|
|
1645
|
+
const hasTextarea = $body.find('textarea').length > 0;
|
|
1646
|
+
let matchType;
|
|
1647
|
+
if (hasTextarea) {
|
|
1648
|
+
matchType = contentMatchType === 'partial' ? 'contain.value' : 'have.value';
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
matchType = contentMatchType === 'partial' ? 'contain.text' : 'have.text';
|
|
1652
|
+
}
|
|
1653
|
+
cy.get(hasTextarea ? '.window-body textarea' : '.window-body span').should(matchType, content);
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
cy.get('button').contains(confirmation).closest('button').click();
|
|
1657
|
+
});
|
|
1658
|
+
cy.GLO_checkLoadingIndicator();
|
|
1659
|
+
});
|
|
1660
|
+
Cypress.Commands.add('GLO_radioButton', (selector, selectorName) => {
|
|
1661
|
+
if (selectorName === undefined)
|
|
1662
|
+
return;
|
|
1663
|
+
cy.GLO_getElement(selector).contains(selectorName).parent().find('.dx-radiobutton-icon').click();
|
|
1664
|
+
});
|
|
1665
|
+
Cypress.Commands.add('GLO_radioListCData', (selector, radioButtonName) => {
|
|
1666
|
+
if (!radioButtonName)
|
|
1667
|
+
return;
|
|
1668
|
+
cy.GLO_getElement(selector)
|
|
1669
|
+
.find('.dx-item-content')
|
|
1670
|
+
.filter(`:contains("${String(radioButtonName)}")`)
|
|
1671
|
+
.then(($element) => {
|
|
1672
|
+
cy.wrap($element).parents('.dx-radiobutton').click();
|
|
1673
|
+
});
|
|
1674
|
+
});
|
|
1675
|
+
Cypress.Commands.add('GLO_validateRadioListCData', (selector, radioButtonName, expectedState) => {
|
|
1676
|
+
cy.GLO_getElement(selector)
|
|
1677
|
+
.find('.dx-item-content')
|
|
1678
|
+
.filter(`:contains("${radioButtonName}")`)
|
|
1679
|
+
.then(($element) => {
|
|
1680
|
+
if (expectedState) {
|
|
1681
|
+
cy.wrap($element).parents('.dx-radiobutton').find('.dx-radiobutton-icon-dot').should('exist');
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
cy.wrap($element).parents('.dx-radiobutton').find('.dx-radiobutton-icon-dot').should('not.exist');
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
});
|
|
1688
|
+
Cypress.Commands.add('GLO_changeUserRole', (userRole) => {
|
|
1689
|
+
cy.GLO_getElement(['ng$main_form2', 'navbar_top']).find('.lucide-User').click();
|
|
1690
|
+
cy.get('.dx-menu-item-text').contains(userRole).click();
|
|
1691
|
+
cy.GLO_dialogRequiredCheck('Yes');
|
|
1692
|
+
});
|
|
1693
|
+
Cypress.Commands.add('GLO_selectRows', (selector, rowNumber) => {
|
|
1694
|
+
const executeForLoop = (rowNumber) => {
|
|
1695
|
+
for (let i = 1; i < rowNumber; i++) {
|
|
1696
|
+
cy.GLO_selectRowInGrid(i);
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
cy.GLO_getElement(selector)
|
|
1700
|
+
.find('.ag-body-viewport')
|
|
1701
|
+
.within(() => {
|
|
1702
|
+
cy.GLO_clickRowInGrid(0); // Always click the first row
|
|
1703
|
+
if (rowNumber !== undefined) {
|
|
1704
|
+
executeForLoop(rowNumber);
|
|
1705
|
+
}
|
|
1706
|
+
else {
|
|
1707
|
+
cy.GLO_getAllRowsInGrid().then(($rows) => {
|
|
1708
|
+
executeForLoop($rows.length);
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
});
|
|
1712
|
+
});
|
|
1713
|
+
Cypress.Commands.add('GLO_sortGrid', (selector, headerText) => {
|
|
1714
|
+
cy.GLO_getElement(selector)
|
|
1715
|
+
.find('.ag-header-viewport .ag-header-row.ag-header-row-column')
|
|
1716
|
+
.within(() => {
|
|
1717
|
+
cy.get('.ag-header-cell').contains(headerText).click();
|
|
1718
|
+
});
|
|
1719
|
+
cy.GLO_checkAGLoader();
|
|
1720
|
+
});
|
|
1721
|
+
Cypress.Commands.add('GLO_fillInfieldInGrid', (selector, rowFilter, columnId, value, gridInputType = gridInput) => {
|
|
1722
|
+
if (!value)
|
|
1723
|
+
return;
|
|
1724
|
+
if (typeof rowFilter === 'string') {
|
|
1725
|
+
cy.GLO_getAllRowsInGrid().filter(`:contains("${rowFilter}")`).find(`[col-id="${columnId}"]`).click();
|
|
1726
|
+
cy.GLO_getAllRowsInGrid().filter(`:contains("${rowFilter}")`).find(`[col-id="${columnId}"]`).dblclick();
|
|
1727
|
+
}
|
|
1728
|
+
else {
|
|
1729
|
+
cy.GLO_getElement(selector).within(() => {
|
|
1730
|
+
cy.GLO_clickRowInGrid(rowFilter, columnId);
|
|
1731
|
+
});
|
|
1732
|
+
cy.GLO_dblClickRowInGrid(row, selector, rowFilter, columnId, skipCheck);
|
|
1733
|
+
}
|
|
1734
|
+
cy.get(gridInputType === 'aggrid-texteditor-without-enter' ? gridInput : gridInputType, { timeout: 1000 }).should('exist');
|
|
1735
|
+
switch (gridInputType) {
|
|
1736
|
+
case gridInput:
|
|
1737
|
+
cy.GLO_getVisible(gridInputType).type(`${String(value)}{enter}`);
|
|
1738
|
+
break;
|
|
1739
|
+
case gridInputWithoutEnter:
|
|
1740
|
+
cy.GLO_getVisible(gridInput).type(String(value));
|
|
1741
|
+
break;
|
|
1742
|
+
case gridDropDown:
|
|
1743
|
+
cy.GLO_getVisible('aggrid-typeahededitor input').type(String(value));
|
|
1744
|
+
cy.GLO_getVisible('ngb-typeahead-window').find('button').contains(String(value)).click();
|
|
1745
|
+
break;
|
|
1746
|
+
case gridSelect:
|
|
1747
|
+
cy.GLO_getVisible(`${gridSelect} select`).select(String(value)).type('{enter}');
|
|
1748
|
+
break;
|
|
1749
|
+
default:
|
|
1750
|
+
throw new Error('Incorrect type for GLO_fillInfieldInGrid. Available types: gridInput, gridDropDown');
|
|
1751
|
+
}
|
|
1752
|
+
cy.GLO_checkLoadingIndicator();
|
|
1753
|
+
});
|
|
1754
|
+
Cypress.Commands.add('GLO_dropdownSelectFirstvalue', (selector) => {
|
|
1755
|
+
cy.GLO_getElement(selector).within(() => {
|
|
1756
|
+
cy.get('.dx-dropdowneditor-icon').click();
|
|
1757
|
+
});
|
|
1758
|
+
cy.GLO_getVisible('.dx-scrollview-content .dx-list-item').first().click();
|
|
1759
|
+
});
|
|
1760
|
+
Cypress.Commands.add('GLO_storeCurrentRecordId', (storeValueName) => {
|
|
1761
|
+
cy.GLO_selectMenuButtonLeft('info');
|
|
1762
|
+
cy.GLO_storeValue(['ng$svy_nav_globis_console_dlg', 'primary_key'], storeValueName);
|
|
1763
|
+
cy.GLO_clickButton(['ng$svy_nav_globis_console_dlg', 'close_btn']);
|
|
1764
|
+
});
|
|
1765
|
+
Cypress.Commands.add('GLO_checkAndSelectCompany', (requestedCompanyCode) => {
|
|
1766
|
+
cy.GLO_getElement(['ng$main_form2'], { timeout: 30000 })
|
|
1767
|
+
.find('.footer span')
|
|
1768
|
+
.contains('Company:')
|
|
1769
|
+
.invoke('text')
|
|
1770
|
+
.then((companyText) => {
|
|
1771
|
+
const $currentCompanyCode = companyText.replace('Company:', '').trim();
|
|
1772
|
+
if ($currentCompanyCode === requestedCompanyCode) {
|
|
1773
|
+
cy.log(`Current company is ${$currentCompanyCode}. No switch required.`);
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
cy.log(`Requested company is ${requestedCompanyCode}. Current company is ${$currentCompanyCode}. Switch required!`);
|
|
1777
|
+
cy.GLO_switchCompany(requestedCompanyCode);
|
|
1778
|
+
cy.log(`Company has been switched! Currently working on company ${requestedCompanyCode}`);
|
|
1779
|
+
}
|
|
1780
|
+
});
|
|
1781
|
+
});
|
|
1782
|
+
Cypress.Commands.add('GLO_selectAllGrid', (selector) => {
|
|
1783
|
+
cy.GLO_getElement(selector).within(() => {
|
|
1784
|
+
cy.GLO_getRowInGrid(0).find('[col-id]').eq(2).rightclick();
|
|
1785
|
+
});
|
|
1786
|
+
cy.get('a').contains('Select all', { matchCase: false }).click();
|
|
1787
|
+
});
|
|
1788
|
+
Cypress.Commands.add('GLO_clickButtonInGrid', { prevSubject: ['optional', 'element'] }, (subject, buttonColumn, rowFilter, validationColumn, x = 10, y = 50) => {
|
|
1789
|
+
const getRow = () => {
|
|
1790
|
+
if (subject) {
|
|
1791
|
+
if (rowFilter !== undefined)
|
|
1792
|
+
throw new Error('Cannot use rowFilter when chained!');
|
|
1793
|
+
return cy.wrap(subject);
|
|
1794
|
+
}
|
|
1795
|
+
else if (rowFilter !== undefined) {
|
|
1796
|
+
return cy.GLO_getRowInGrid(rowFilter);
|
|
1797
|
+
}
|
|
1798
|
+
else {
|
|
1799
|
+
return cy.GLO_getVisible('[row-index]').first();
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
if (validationColumn) {
|
|
1803
|
+
getRow()
|
|
1804
|
+
.find(`[col-id="${validationColumn}"]`)
|
|
1805
|
+
.invoke('text')
|
|
1806
|
+
.then((text) => cy.wrap(text.trim()).as('beforeValue'));
|
|
1807
|
+
}
|
|
1808
|
+
getRow()
|
|
1809
|
+
.find(`[col-id="${buttonColumn}"]`)
|
|
1810
|
+
.then(($btn) => {
|
|
1811
|
+
const xCor = ($btn.width() * x) / 100;
|
|
1812
|
+
const yCor = ($btn.height() * y) / 100;
|
|
1813
|
+
getRow().find(`[col-id="${buttonColumn}"]`).click(xCor, yCor);
|
|
1814
|
+
});
|
|
1815
|
+
if (validationColumn) {
|
|
1816
|
+
cy.get('@beforeValue').then((beforeValue) => {
|
|
1817
|
+
getRow()
|
|
1818
|
+
.find(`[col-id="${validationColumn}"]`, { timeout: 30000 })
|
|
1819
|
+
.should(($afterValue) => {
|
|
1820
|
+
expect($afterValue.text().trim()).not.to.equal(beforeValue);
|
|
1821
|
+
});
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
cy.GLO_checkLoadingIndicator();
|
|
1825
|
+
});
|
|
1826
|
+
Cypress.Commands.add('GLO_oktaAlternativeLogin', (altUser, company) => {
|
|
1827
|
+
cy.GLO_logout();
|
|
1828
|
+
// biome-ignore plugin: wait for Okta to process logout
|
|
1829
|
+
cy.wait(10000); // Wait for Okta to process logout
|
|
1830
|
+
if (cypressWebsocketLoader) {
|
|
1831
|
+
cy.on('window:before:load', (win) => {
|
|
1832
|
+
attachWebSocketSpy(win);
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
if (altUser) {
|
|
1836
|
+
cy.origin(Cypress.expose('oktaUrl'), () => {
|
|
1837
|
+
cy.get('.okta-container', { timeout: 120000 });
|
|
1838
|
+
cy.get('.o-form-input', { timeout: 60000 })
|
|
1839
|
+
.find('[autocomplete="username"]')
|
|
1840
|
+
.clear()
|
|
1841
|
+
.type(Cypress.expose('username2'));
|
|
1842
|
+
cy.task('log', 'Filled in username');
|
|
1843
|
+
cy.get('[type="submit"]').click();
|
|
1844
|
+
cy.env(['password2']).then(({ password2 }) => {
|
|
1845
|
+
cy.get('.o-form-input')
|
|
1846
|
+
.find('[type="password"]', { timeout: 20000 })
|
|
1847
|
+
.type(password2, { log: false });
|
|
1848
|
+
});
|
|
1849
|
+
cy.task('log', 'Filled in password');
|
|
1850
|
+
cy.get('[type="submit"]').click();
|
|
1851
|
+
});
|
|
1852
|
+
cy.GLO_log(`Logged in as ${Cypress.expose('username2')}`);
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
cy.origin(Cypress.expose('oktaUrl'), () => {
|
|
1856
|
+
cy.get('.okta-container', { timeout: 120000 });
|
|
1857
|
+
cy.get('.o-form-input', { timeout: 60000 })
|
|
1858
|
+
.find('[autocomplete="username"]')
|
|
1859
|
+
.clear()
|
|
1860
|
+
.type(Cypress.expose('username'));
|
|
1861
|
+
cy.task('log', 'Filled in username');
|
|
1862
|
+
cy.get('[type="submit"]').click();
|
|
1863
|
+
cy.env(['password']).then(({ password }) => {
|
|
1864
|
+
cy.get('.o-form-input')
|
|
1865
|
+
.find('[type="password"]', { timeout: 20000 })
|
|
1866
|
+
.type(password, { log: false });
|
|
1867
|
+
});
|
|
1868
|
+
cy.task('log', 'Filled in password');
|
|
1869
|
+
cy.get('[type="submit"]').click();
|
|
1870
|
+
});
|
|
1871
|
+
cy.GLO_log(`Logged in as ${Cypress.expose('username')}`);
|
|
1872
|
+
}
|
|
1873
|
+
if (cypressWebsocketLoader)
|
|
1874
|
+
waitForWebSocketAfterOkta();
|
|
1875
|
+
cy.get('[data-cy$="ng$main_form2.navbar_top"]', { timeout: 120000 }).should('exist');
|
|
1876
|
+
if (company)
|
|
1877
|
+
cy.GLO_checkAndSelectCompany(company);
|
|
1878
|
+
});
|
|
1879
|
+
Cypress.Commands.add('GLO_getAllRowsInGrid', (selector, timeOut = defaultTimeout) => {
|
|
1880
|
+
if (selector) {
|
|
1881
|
+
const deadline = Date.now() + timeOut;
|
|
1882
|
+
return cy
|
|
1883
|
+
.GLO_getElement(selector, { timeout: timeOut })
|
|
1884
|
+
.find('.ag-center-cols-container [row-index]', { timeout: timeOut + 500 })
|
|
1885
|
+
.should(($rows) => {
|
|
1886
|
+
if ($rows.length === 0 && Date.now() < deadline)
|
|
1887
|
+
throw new Error('GLO_getAllRowsInGrid: waiting for rows');
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
else {
|
|
1891
|
+
return cy.get('.ag-center-cols-container [row-index] [role="gridcell"]', { timeout: timeOut }).parent().filter(isInOpenContainer);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
Cypress.Commands.add('GLO_validateGridOrFail', (waitValue, failValue, rowNumber, columnId, timeOut = 600000, interval = 15000) => {
|
|
1895
|
+
validateGridValue(waitValue, failValue, rowNumber, columnId, timeOut, interval);
|
|
1896
|
+
});
|
|
1897
|
+
const validateGridValue = (value, failValue, rowFilter, columnId, timeOut = 10000, interval, operator = contains) => {
|
|
1898
|
+
const assertOrCheckCellValue = ($cells, toCheck, doAssert) => {
|
|
1899
|
+
const compare = (cellText) => {
|
|
1900
|
+
const text = cellText.trim();
|
|
1901
|
+
const normalizedValue = typeof toCheck === 'number' ? toCheck : String(toCheck).trim();
|
|
1902
|
+
const inputVal = typeof normalizedValue === 'number' ? parseLocalizedNumber(text) : text;
|
|
1903
|
+
if (operator === equal) {
|
|
1904
|
+
return inputVal === normalizedValue;
|
|
1905
|
+
}
|
|
1906
|
+
else if (operator === notEqual) {
|
|
1907
|
+
return inputVal !== normalizedValue;
|
|
1908
|
+
}
|
|
1909
|
+
else if (operator === contains) {
|
|
1910
|
+
return String(inputVal).includes(String(normalizedValue));
|
|
1911
|
+
}
|
|
1912
|
+
else if (operator === greaterEqual && typeof inputVal === 'number' && typeof normalizedValue === 'number') {
|
|
1913
|
+
return inputVal >= normalizedValue;
|
|
1914
|
+
}
|
|
1915
|
+
throw new Error('Incorrect operator for GLO_checkField. Available operators: equal, notEqual, contains, greaterEqual');
|
|
1916
|
+
};
|
|
1917
|
+
const match = $cells.toArray().find((cell) => compare(cell.textContent));
|
|
1918
|
+
const found = !!match;
|
|
1919
|
+
if (doAssert)
|
|
1920
|
+
assert.isTrue(found, `Cell value "${operator}" "${String(toCheck)}"`);
|
|
1921
|
+
return found;
|
|
1922
|
+
};
|
|
1923
|
+
const toCheckData = () => {
|
|
1924
|
+
const logOpt = { log: !interval };
|
|
1925
|
+
if (columnId && rowFilter !== undefined)
|
|
1926
|
+
return typeof rowFilter === 'number'
|
|
1927
|
+
? cy
|
|
1928
|
+
.GLO_getAllRowsInGrid(undefined, timeOut)
|
|
1929
|
+
.find(`[col-id="${columnId}"]`, logOpt)
|
|
1930
|
+
.eq(rowFilter, { timeout: timeOut, ...logOpt })
|
|
1931
|
+
: cy
|
|
1932
|
+
.GLO_getAllRowsInGrid(undefined, timeOut)
|
|
1933
|
+
.filter(`:contains("${rowFilter}")`, { timeout: timeOut, ...logOpt })
|
|
1934
|
+
.find(`[col-id="${columnId}"]`, logOpt);
|
|
1935
|
+
else if (columnId)
|
|
1936
|
+
return cy.GLO_getAllRowsInGrid(undefined, timeOut).find(`[col-id="${columnId}"]`, { timeout: timeOut, ...logOpt });
|
|
1937
|
+
else if (rowFilter !== undefined)
|
|
1938
|
+
return (typeof rowFilter === 'number'
|
|
1939
|
+
? cy.GLO_getAllRowsInGrid(undefined, timeOut).eq(rowFilter, { timeout: timeOut, ...logOpt })
|
|
1940
|
+
: cy.GLO_getAllRowsInGrid(undefined, timeOut).filter(`:contains("${rowFilter}")`, { timeout: timeOut, ...logOpt })).find('.ag-cell-value');
|
|
1941
|
+
else
|
|
1942
|
+
return cy.GLO_getAllRowsInGrid(undefined, timeOut);
|
|
1943
|
+
};
|
|
1944
|
+
if (Array.isArray(value)) {
|
|
1945
|
+
if (columnId)
|
|
1946
|
+
throw new Error('Cannot use combination of array and column id');
|
|
1947
|
+
const spansArray = value.map((span) => span.trim());
|
|
1948
|
+
const filterSelector = spansArray.map((span) => `:has(span:contains("${span}"))`).join('');
|
|
1949
|
+
if (rowFilter !== undefined)
|
|
1950
|
+
cy.GLO_getRowInGrid(rowFilter).filter(filterSelector, { timeout: timeOut }).should('exist');
|
|
1951
|
+
else
|
|
1952
|
+
cy.GLO_getAllRowsInGrid().filter(filterSelector, { timeout: timeOut }).should('exist');
|
|
1953
|
+
}
|
|
1954
|
+
else if (!interval) {
|
|
1955
|
+
toCheckData().should(($cells) => assertOrCheckCellValue($cells, value, true));
|
|
1956
|
+
}
|
|
1957
|
+
else {
|
|
1958
|
+
const pollInterval = interval;
|
|
1959
|
+
const maxAttempts = Math.max(1, Math.ceil(timeOut / pollInterval));
|
|
1960
|
+
let attempts = 0;
|
|
1961
|
+
const log = Cypress.log({ name: 'gridValueValidator', message: `Polling for "${String(value)}"…`, autoEnd: false, timeout: 0 });
|
|
1962
|
+
const checkStatus = (resolve, reject) => {
|
|
1963
|
+
toCheckData().then(($cells) => {
|
|
1964
|
+
if (failValue !== undefined && assertOrCheckCellValue($cells, failValue, false)) {
|
|
1965
|
+
cy.log(`Found fail value: ${String(failValue)}`);
|
|
1966
|
+
log.end();
|
|
1967
|
+
reject(new Error(`Found fail value: ${String(failValue)}`));
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
if (assertOrCheckCellValue($cells, value, false)) {
|
|
1971
|
+
cy.log(`Succeeded finding "${String(value)}"`);
|
|
1972
|
+
log.end();
|
|
1973
|
+
resolve();
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
attempts += 1;
|
|
1977
|
+
if (attempts >= maxAttempts) {
|
|
1978
|
+
cy.log(`Timed out after ${Math.round((maxAttempts * pollInterval) / 100) / 10}s waiting for: ${String(value)}`);
|
|
1979
|
+
log.end();
|
|
1980
|
+
reject(new Error(`Timed out after ${Math.round((maxAttempts * pollInterval) / 100) / 10}s waiting for: ${String(value)}`));
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
cy.wait(pollInterval, { log: false }).then(() => {
|
|
1984
|
+
log.set({ message: `Still polling… ${attempts}/${maxAttempts}` });
|
|
1985
|
+
checkStatus(resolve, reject);
|
|
1986
|
+
});
|
|
1987
|
+
});
|
|
1988
|
+
};
|
|
1989
|
+
const promise = new Cypress.Promise((resolve, reject) => {
|
|
1990
|
+
checkStatus(resolve, reject);
|
|
1991
|
+
});
|
|
1992
|
+
cy.then(() => promise);
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
Cypress.Commands.add('GLO_getCurrentRecordCount', () => {
|
|
1996
|
+
return cy
|
|
1997
|
+
.GLO_getElement(['ng$datacontrol', 'datacontrol'])
|
|
1998
|
+
.find('#txtCount')
|
|
1999
|
+
.invoke('text')
|
|
2000
|
+
.then((records) => {
|
|
2001
|
+
const [currentStr, totalStr] = records.split('/');
|
|
2002
|
+
if (!currentStr || !totalStr) {
|
|
2003
|
+
throw new Error(`Could not parse record count from "${records}"`);
|
|
2004
|
+
}
|
|
2005
|
+
return {
|
|
2006
|
+
currentRecord: parseInt(currentStr.trim(), 10),
|
|
2007
|
+
totalRecords: parseInt(totalStr.trim(), 10)
|
|
2008
|
+
};
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
Cypress.Commands.add('GLO_storeObject', (formName, elementNames, index, log = hideInCommandLog) => {
|
|
2012
|
+
const keys = (Array.isArray(elementNames) ? elementNames : [elementNames]);
|
|
2013
|
+
// Initialize saveData object to prevent cypress from failing when saving first alias.
|
|
2014
|
+
cy.then(() => {
|
|
2015
|
+
const current = Cypress.expose('saveData') ?? {};
|
|
2016
|
+
Cypress.expose('saveData', current);
|
|
2017
|
+
cy.wrap(current, { log: false }).as('saveData');
|
|
2018
|
+
});
|
|
2019
|
+
// The actual storing of values.
|
|
2020
|
+
cy.wrap(keys, { log: false }).each((key) => {
|
|
2021
|
+
cy.GLO_getElement([formName, key])
|
|
2022
|
+
.find('.dx-texteditor-input', { log: false })
|
|
2023
|
+
.invoke('val')
|
|
2024
|
+
.then((text) => {
|
|
2025
|
+
const value = String(text);
|
|
2026
|
+
const saveData = Cypress.expose('saveData') ?? {};
|
|
2027
|
+
// Create the nested object if it doesn't exist yet.
|
|
2028
|
+
saveData[formName] ??= {};
|
|
2029
|
+
if (typeof index === 'number') {
|
|
2030
|
+
const existing = saveData[formName][key];
|
|
2031
|
+
// Determine if we need to create a new array or use the existing one.
|
|
2032
|
+
const arr = Array.isArray(existing) ? existing : [];
|
|
2033
|
+
arr[index] = value;
|
|
2034
|
+
saveData[formName][key] = arr;
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
saveData[formName][key] = value;
|
|
2038
|
+
}
|
|
2039
|
+
Cypress.expose('saveData', saveData);
|
|
2040
|
+
cy.wrap(saveData, { log: false }).as('saveData');
|
|
2041
|
+
const indexSuffix = typeof index === 'number' ? `[${index}]` : '';
|
|
2042
|
+
if (log)
|
|
2043
|
+
cy.GLO_log(`Value saved at saveData.${formName}.${key}${indexSuffix}: ${value}`);
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
});
|
|
2047
|
+
Cypress.Commands.add('GLO_checkFieldInvalid', (selector, shouldBeInvalid = isInvalid) => {
|
|
2048
|
+
cy.GLO_getElement(selector).within(() => {
|
|
2049
|
+
if (shouldBeInvalid) {
|
|
2050
|
+
cy.get('.dx-texteditor').should('have.class', 'dx-invalid');
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
cy.get('.dx-texteditor').should('not.have.class', 'dx-invalid');
|
|
2054
|
+
}
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
Cypress.Commands.add('GLO_checkFieldEnabled', (selector, shouldBeEnabled = enabled) => {
|
|
2058
|
+
cy.GLO_getElement(selector).within(() => {
|
|
2059
|
+
if (shouldBeEnabled) {
|
|
2060
|
+
cy.get('.dx-texteditor').should('not.have.class', 'dx-state-disabled');
|
|
2061
|
+
}
|
|
2062
|
+
else {
|
|
2063
|
+
cy.get('.dx-texteditor').should('have.class', 'dx-state-disabled');
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
Cypress.Commands.add('GLO_checkFieldBgColor', (selector, color = 'red') => {
|
|
2068
|
+
const [formName, fieldOrFields] = selector;
|
|
2069
|
+
const fields = Array.isArray(fieldOrFields) ? fieldOrFields : [fieldOrFields];
|
|
2070
|
+
for (const field of fields) {
|
|
2071
|
+
cy.GLO_getElement([formName, field]).within(() => {
|
|
2072
|
+
cy.get('input.dx-texteditor-input').should('have.class', `input_bg_${color}`);
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
});
|
|
2076
|
+
Cypress.Commands.add('GLO_extendedFilter', (filter, clearExistingFilters) => {
|
|
2077
|
+
// **************
|
|
2078
|
+
// * Selectors *
|
|
2079
|
+
// **************
|
|
2080
|
+
const filterComponent = () => cy.GLO_getElement(['ng$extended_filter', 'Extended_Filter_Builder'], { log: false });
|
|
2081
|
+
const getFilterFieldSelector = (path, fieldType, clauseType, includeInputClass = true) => {
|
|
2082
|
+
const prefix = clauseType
|
|
2083
|
+
? { Attribute: 'clause-', 'Related field': 'relation-', 'Dynamic attribute': 'clause-', Group: 'group-', 'Nested group': 'group-' }[clauseType] || ''
|
|
2084
|
+
: '';
|
|
2085
|
+
return `[data-cy="filter-builder-${fieldType === 'value' ? '' : prefix}${fieldType}-${path}"]${includeInputClass ? ' .dx-texteditor-input' : ''}`;
|
|
2086
|
+
};
|
|
2087
|
+
// **************
|
|
2088
|
+
// * Buttons *
|
|
2089
|
+
// **************
|
|
2090
|
+
const buttonClearall = () => filterComponent().find('[data-cy="filter-builder-clear-all"]').click();
|
|
2091
|
+
const buttonRunquery = () => filterComponent().find('[data-cy="filter-builder-run-query"]').should('not.be.disabled').click();
|
|
2092
|
+
const buttonAdd = (filterType, groupPath) => {
|
|
2093
|
+
const click = (buttonType) => {
|
|
2094
|
+
const prefix = groupPath ? `filter-builder-group-${groupPath}-` : 'filter-builder-';
|
|
2095
|
+
return filterComponent().find(`[data-cy="${prefix}${buttonType}"]`).click();
|
|
2096
|
+
};
|
|
2097
|
+
switch (filterType) {
|
|
2098
|
+
case 'Related field':
|
|
2099
|
+
return click('add-related-field');
|
|
2100
|
+
case 'Dynamic attribute':
|
|
2101
|
+
return click('add-dynamic-attribute');
|
|
2102
|
+
case 'Nested group':
|
|
2103
|
+
if (!groupPath)
|
|
2104
|
+
throw new Error('Nested group can only be added inside a group');
|
|
2105
|
+
return click('add-nested-group');
|
|
2106
|
+
case 'Group':
|
|
2107
|
+
return groupPath ? click('add-nested-group') : click('add-group');
|
|
2108
|
+
default:
|
|
2109
|
+
return click('add-attribute');
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2112
|
+
// **************
|
|
2113
|
+
// * Inputs *
|
|
2114
|
+
// **************
|
|
2115
|
+
const dropDown = (path, clauseType, dropDownType, value) => {
|
|
2116
|
+
const selector = getFilterFieldSelector(path, dropDownType, clauseType);
|
|
2117
|
+
filterComponent()
|
|
2118
|
+
.find(selector)
|
|
2119
|
+
.then(($el) => {
|
|
2120
|
+
const id = $el[0].getAttribute('aria-controls');
|
|
2121
|
+
if (!id)
|
|
2122
|
+
cy.wrap($el).click();
|
|
2123
|
+
});
|
|
2124
|
+
filterComponent()
|
|
2125
|
+
.find(selector)
|
|
2126
|
+
.then(($el) => {
|
|
2127
|
+
const id = $el[0].getAttribute('aria-controls');
|
|
2128
|
+
if (!id)
|
|
2129
|
+
throw new Error('Missing aria-controls');
|
|
2130
|
+
cy.get(`[id="${id}"]`, { timeout: 30000 }).should('exist').should('not.have.class', 'dx-empty-collection').find('.dx-item').should('have.length.greaterThan', 0);
|
|
2131
|
+
cy.get(`[id="${id}"] .dx-scrollview-content`).children().contains(value).click();
|
|
2132
|
+
});
|
|
2133
|
+
switch (dropDownType) {
|
|
2134
|
+
case 'select':
|
|
2135
|
+
filterComponent()
|
|
2136
|
+
.find(getFilterFieldSelector(path, dropDownType, clauseType, false))
|
|
2137
|
+
.parent()
|
|
2138
|
+
.find('.dx-tag button')
|
|
2139
|
+
.filter((_, el) => (el.getAttribute('aria-label') ?? '').includes(value))
|
|
2140
|
+
.should('have.length.greaterThan', 0);
|
|
2141
|
+
break;
|
|
2142
|
+
case 'value':
|
|
2143
|
+
if (value === 'Select All')
|
|
2144
|
+
return;
|
|
2145
|
+
filterComponent()
|
|
2146
|
+
.find(`${getFilterFieldSelector(path, dropDownType, clauseType, false)} .dx-tag`)
|
|
2147
|
+
.filter((_, el) => (el.getAttribute('aria-label') ?? '').includes(value))
|
|
2148
|
+
.should('have.length.greaterThan', 0);
|
|
2149
|
+
break;
|
|
2150
|
+
case 'field':
|
|
2151
|
+
case 'logical-operator':
|
|
2152
|
+
case 'operator':
|
|
2153
|
+
filterComponent().find(selector).invoke('val').should('include', value);
|
|
2154
|
+
break;
|
|
2155
|
+
default:
|
|
2156
|
+
break;
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
// This is basically the same as checkBoxHelper but adapted for the selector inside the extended filter component.
|
|
2160
|
+
const checkBox = (path, v) => {
|
|
2161
|
+
const retries = 3;
|
|
2162
|
+
const clickElement = (remaining = retries) => {
|
|
2163
|
+
return filterComponent()
|
|
2164
|
+
.find(getFilterFieldSelector(path, 'value', undefined, false))
|
|
2165
|
+
.then(($element) => {
|
|
2166
|
+
const currentState = $element[0].getAttribute('aria-checked');
|
|
2167
|
+
if (currentState === v)
|
|
2168
|
+
return;
|
|
2169
|
+
if (remaining <= 0)
|
|
2170
|
+
throw new Error('Failed to set checkbox/switch to the desired state after multiple attempts.');
|
|
2171
|
+
filterComponent()
|
|
2172
|
+
.find(getFilterFieldSelector(path, 'value', undefined, false))
|
|
2173
|
+
.find('.dx-checkbox-icon')
|
|
2174
|
+
.click()
|
|
2175
|
+
.then(() => {
|
|
2176
|
+
cy.GLO_checkLoadingIndicator();
|
|
2177
|
+
clickElement(remaining - 1);
|
|
2178
|
+
});
|
|
2179
|
+
});
|
|
2180
|
+
};
|
|
2181
|
+
clickElement();
|
|
2182
|
+
};
|
|
2183
|
+
const applyValueForOperator = (operator, path, clauseType, values) => {
|
|
2184
|
+
const vals = Array.isArray(values) ? values : [values];
|
|
2185
|
+
vals.forEach((v, i) => {
|
|
2186
|
+
switch (operator) {
|
|
2187
|
+
case 'is empty':
|
|
2188
|
+
case 'is not empty':
|
|
2189
|
+
case 'today':
|
|
2190
|
+
case 'this week':
|
|
2191
|
+
case 'this month':
|
|
2192
|
+
return;
|
|
2193
|
+
case 'between':
|
|
2194
|
+
filterComponent()
|
|
2195
|
+
.find(getFilterFieldSelector(path, `value-${i === 0 ? 'from' : 'to'}`))
|
|
2196
|
+
.type(`{selectall}{del}${v}{enter}`);
|
|
2197
|
+
return;
|
|
2198
|
+
case 'in':
|
|
2199
|
+
case 'not in':
|
|
2200
|
+
filterComponent()
|
|
2201
|
+
.find(getFilterFieldSelector(path, 'value', undefined, false))
|
|
2202
|
+
.parent()
|
|
2203
|
+
.then(($field) => {
|
|
2204
|
+
if ($field.find('button.fa-search').length > 0) {
|
|
2205
|
+
if (i >= 1)
|
|
2206
|
+
throw new Error('Selector only supports a single value (for now).');
|
|
2207
|
+
cy.wrap($field).find('button.fa-search').click();
|
|
2208
|
+
cy.GLO_getElement(['svy_nav_selection', 'search_criteria']).type(String(v));
|
|
2209
|
+
cy.GLO_clickButton(['svy_nav_selection', 'btn_search']);
|
|
2210
|
+
dblClickRowInGrid(span, String(v));
|
|
2211
|
+
cy.GLO_checkLoadingIndicator();
|
|
2212
|
+
}
|
|
2213
|
+
else {
|
|
2214
|
+
dropDown(path, clauseType, 'value', String(v));
|
|
2215
|
+
if (i === vals.length - 1)
|
|
2216
|
+
filterComponent()
|
|
2217
|
+
.find(getFilterFieldSelector(path, 'value', clauseType))
|
|
2218
|
+
.click();
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
return;
|
|
2222
|
+
case '=':
|
|
2223
|
+
// biome-ignore lint/suspicious/noFallthroughSwitchClause: intentional - falls through to the shared date/time handling below when v isn't check/uncheck
|
|
2224
|
+
case '≠':
|
|
2225
|
+
if (v === check || v === unCheck) {
|
|
2226
|
+
checkBox(path, v);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
// Fall through
|
|
2230
|
+
case 'before':
|
|
2231
|
+
case 'after':
|
|
2232
|
+
case '<= today +/- days':
|
|
2233
|
+
case '>= today +/- days':
|
|
2234
|
+
case 'contains':
|
|
2235
|
+
case 'starts with':
|
|
2236
|
+
case 'ends with':
|
|
2237
|
+
case '>':
|
|
2238
|
+
case '>=':
|
|
2239
|
+
case '<':
|
|
2240
|
+
case '<=':
|
|
2241
|
+
filterComponent().find(getFilterFieldSelector(path, 'value')).type(`{selectall}{del}${v}{enter}`);
|
|
2242
|
+
return;
|
|
2243
|
+
default: {
|
|
2244
|
+
const _exhaustive = operator;
|
|
2245
|
+
throw new Error(`Unhandled operator: ${String(_exhaustive)}`);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
};
|
|
2250
|
+
const applyLeaf = (leaf, path, parentGroupPath, isFirstRoot) => {
|
|
2251
|
+
const clauseType = leaf.type ?? 'Attribute';
|
|
2252
|
+
if (parentGroupPath)
|
|
2253
|
+
buttonAdd(clauseType, parentGroupPath);
|
|
2254
|
+
else {
|
|
2255
|
+
if (isFirstRoot && clauseType !== 'Attribute')
|
|
2256
|
+
filterComponent().find('[data-cy="filter-builder-clause-remove-0"]').click();
|
|
2257
|
+
if (!isFirstRoot || clauseType !== 'Attribute')
|
|
2258
|
+
buttonAdd(clauseType);
|
|
2259
|
+
}
|
|
2260
|
+
leaf.logicalOperator && !isFirstRoot && dropDown(path, clauseType, 'logical-operator', leaf.logicalOperator);
|
|
2261
|
+
if (leaf.type === 'Related field')
|
|
2262
|
+
(Array.isArray(leaf.relationSelect) ? leaf.relationSelect : [leaf.relationSelect]).forEach((v, i) => {
|
|
2263
|
+
dropDown(path, clauseType, 'select', v);
|
|
2264
|
+
if (i !== leaf.relationSelect.length - 1)
|
|
2265
|
+
filterComponent()
|
|
2266
|
+
.find(getFilterFieldSelector(path, 'select', clauseType))
|
|
2267
|
+
.click();
|
|
2268
|
+
});
|
|
2269
|
+
dropDown(path, clauseType, 'field', leaf.field);
|
|
2270
|
+
dropDown(path, clauseType, 'operator', leaf.operator);
|
|
2271
|
+
if (!('values' in leaf))
|
|
2272
|
+
return;
|
|
2273
|
+
applyValueForOperator(leaf.operator, path, clauseType, leaf.values);
|
|
2274
|
+
};
|
|
2275
|
+
const applyNode = (node, path, parentGroupPath, isFirstRoot) => {
|
|
2276
|
+
const isGroup = (n) => n.type === 'Group';
|
|
2277
|
+
const isNestedGroup = (n) => n.type === 'Nested group';
|
|
2278
|
+
if (isGroup(node)) {
|
|
2279
|
+
if (parentGroupPath)
|
|
2280
|
+
buttonAdd('Group', parentGroupPath);
|
|
2281
|
+
else {
|
|
2282
|
+
if (isFirstRoot)
|
|
2283
|
+
filterComponent().find('[data-cy="filter-builder-clause-remove-0"]').click();
|
|
2284
|
+
buttonAdd('Group');
|
|
2285
|
+
}
|
|
2286
|
+
node.logicalOperator && dropDown(path, 'Group', 'logical-operator', node.logicalOperator);
|
|
2287
|
+
node.values.forEach((child, i) => {
|
|
2288
|
+
applyNode(child, `${path}-${i}`, path, false);
|
|
2289
|
+
});
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if (isNestedGroup(node)) {
|
|
2293
|
+
if (!parentGroupPath)
|
|
2294
|
+
throw new Error('Nested group requires a parent group');
|
|
2295
|
+
buttonAdd('Nested group', parentGroupPath);
|
|
2296
|
+
node.logicalOperator && dropDown(path, 'Nested group', 'logical-operator', node.logicalOperator);
|
|
2297
|
+
node.values.forEach((child, i) => {
|
|
2298
|
+
applyNode(child, `${path}-${i}`, path, false);
|
|
2299
|
+
});
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
applyLeaf(node, path, parentGroupPath, isFirstRoot);
|
|
2303
|
+
};
|
|
2304
|
+
cy.get('div[id^="crumb_"]:not([hidden])', { log: false }).then(($crumbRoot) => {
|
|
2305
|
+
$crumbRoot.find('[data-cy="ng$extended_filter.Extended_Filter_Builder"]').length === 0 && cy.GLO_clickDatacontrolBrowserButton('btnFilter');
|
|
2306
|
+
});
|
|
2307
|
+
if (clearExistingFilters)
|
|
2308
|
+
buttonClearall();
|
|
2309
|
+
filter.forEach((node, i) => {
|
|
2310
|
+
applyNode(node, `${i}`, undefined, i === 0);
|
|
2311
|
+
});
|
|
2312
|
+
buttonRunquery();
|
|
2313
|
+
cy.GLO_checkLoadingIndicator();
|
|
2314
|
+
});
|
|
2315
|
+
Cypress.Commands.add('GLO_selectNamedSearch', (searchName) => {
|
|
2316
|
+
cy.GLO_clickDatacontrolBrowserButton('btnExtendedSearch');
|
|
2317
|
+
cy.GLO_getVisible('.dx-submenu', 10000).contains(searchName).click();
|
|
2318
|
+
cy.GLO_checkLoadingIndicator();
|
|
2319
|
+
});
|