diff --git a/ui-tests/cypress/e2e/onboarding/onboarding.cy.js b/ui-tests/cypress/e2e/onboarding/onboarding.cy.js index 6b5bc896314..e21f535af90 100644 --- a/ui-tests/cypress/e2e/onboarding/onboarding.cy.js +++ b/ui-tests/cypress/e2e/onboarding/onboarding.cy.js @@ -155,6 +155,7 @@ describe('Complete Onboarding', () => { }); navigationHelpers.isNavigatedToDashboard(); + cy.initSoftAssert(); quickstartPopoeverHelpers.verifyDefaultPageElements(); quickstartPopoeverHelpers.closeQuickStartPopover(); homePageHelpers.verifyEmptyPageElements(); @@ -268,6 +269,7 @@ describe('Complete Onboarding', () => { dbCountlyOutPageHelpers.verifyEmptyPageElements(); dbCountlyOutPageHelpers.clickCountlyFileSystemDatabaseTab(); dbCountlyFsPageHelpers.verifyEmptyPageElements(); + cy.assertAll(); }); it('should be complete onboarding flow with creating own desktop type application with default app key and enable tracking and subscribe to newsletter', function() { @@ -323,6 +325,7 @@ describe('Complete Onboarding', () => { }); navigationHelpers.isNavigatedToDashboard(); + cy.initSoftAssert(); quickstartPopoeverHelpers.closeQuickStartPopover(); homePageHelpers.verifyFullDataPageElements(); navigationHelpers.goToAnalyticsUsersOverview(); @@ -445,5 +448,6 @@ describe('Complete Onboarding', () => { dbCountlyOutPageHelpers.clickCountlyFileSystemDatabaseTab(); //dbCountlyFsPageHelpers.verifyFullDataPageElements(); //TODO: Data is not being generated with the populator. Need to generate the data dbCountlyFsPageHelpers.verifyEmptyPageElements(); + cy.assertAll(); }); }); diff --git a/ui-tests/cypress/support/commands.js b/ui-tests/cypress/support/commands.js index a6491fb4744..c10ecb1891e 100644 --- a/ui-tests/cypress/support/commands.js +++ b/ui-tests/cypress/support/commands.js @@ -3,6 +3,86 @@ const helper = require('./helper'); const chai = require('chai'); const expect = chai.expect; +// ───────────────────────────────────────────── +// Soft Assertion Commands +// ───────────────────────────────────────────── + +// Initializes the soft assertion error list +Cypress.Commands.add('initSoftAssert', () => { + cy.wrap([]).as('softErrors'); +}); + +// Collects soft assertion failures without stopping the test +Cypress.Commands.add('softFail', (message) => { + cy.get('@softErrors', { log: false }).then((errors) => { + errors.push(message); + cy.wrap(errors, { log: false }).as('softErrors'); + }).catch(() => { + // if alias missing → fail immediately + throw new Error(message); + }); +}); + +// Safe wrapper to continue test after a failed Cypress command +Cypress.Commands.add('safeCheck', (fn, description) => { + cy.then(() => { + return fn().catch((err) => { + cy.softFail(`${description} - ${err.message}`); + return null; + }); + }); +}); + +// Fails the test if there were any collected soft assertion errors +Cypress.Commands.add('assertAll', () => { + cy.get('@softErrors').then((errors) => { + if (errors.length > 0) { + throw new Error(`Soft assertion errors:\n${errors.join('\n')}`); + } + }); +}); + +// ───────────────────────────────────────────── +// getElement helper (supports { soft: true }) +// ───────────────────────────────────────────── +Cypress.Commands.add('getElement', (selector, parent = null, options = {}) => { + const { soft = false, timeout = 5000 } = options; + let finalSelector; + + if (!selector.includes('[data-test-id=')) { + if (selector.startsWith('.') || selector.startsWith('#')) { + finalSelector = selector; + } + else { + finalSelector = parent + ? `${parent} [data-test-id="${selector}"]` + : `[data-test-id="${selector}"]`; + } + } + else { + finalSelector = selector; + } + + if (soft) { + return cy.get('body', { timeout }).then(($body) => { + const found = $body.find(finalSelector); + if (found.length > 0) { + return cy.wrap(found); + } + else { + cy.softFail(`❌ Element not found: ${finalSelector}`); + return cy.wrap(Cypress.$([])); // empty set + } + }); + } + else { + return cy.get(finalSelector, { timeout }); + } +}); + +// ───────────────────────────────────────────── +// Common Element Commands +// ───────────────────────────────────────────── Cypress.Commands.add("typeInput", (element, tag) => { cy.getElement(element).clear().type(tag); }); @@ -31,7 +111,8 @@ Cypress.Commands.add('getText', { prevSubject: true }, (subject) => { Cypress.Commands.add("clickDataTableMoreButtonItem", (element, rowIndex = 0) => { cy.getElement("datatable-more-button-area") - .eq(rowIndex).invoke('show') + .eq(rowIndex) + .invoke('show') .trigger('mouseenter', { force: true }); cy.clickElement(element, true); @@ -47,6 +128,21 @@ Cypress.Commands.add("clickBody", () => { cy.checkPaceRunning(); }); +Cypress.Commands.add('dragAndDropFile', (element, filePath) => { + cy.getElement(element) + .attachFile(filePath, { + encoding: 'utf-8', + subjectType: 'drag-n-drop' + }); +}); + +Cypress.Commands.add('uploadFile', (filePath) => { + cy.get('input[type="file"]').attachFile(filePath, { force: true }); +}); + +// ───────────────────────────────────────────── +// Selectors and Dropdowns +// ───────────────────────────────────────────── Cypress.Commands.add("selectOption", (element, option) => { cy.getElement(element).click(); cy.clickOption('.el-select-dropdown__item', option); @@ -63,12 +159,10 @@ Cypress.Commands.add("selectCheckboxOption", (element, ...options) => { cy.clickOption('.el-checkbox__label', options[i]); } - cy - .elementExists(`${element}-select-x-confirm-button`) + cy.elementExists(`${element}-select-x-confirm-button`) .then((isExists) => { if (isExists) { cy.clickElement(`${element}-select-x-confirm-button`); - } }); @@ -76,7 +170,7 @@ Cypress.Commands.add("selectCheckboxOption", (element, ...options) => { }); Cypress.Commands.add("clickOption", (element, option) => { - cy.getElement(element).contains(new RegExp("^" + option + "$", "g")).click({force: true}); + cy.getElement(element).contains(new RegExp("^" + option + "$", "g")).click({ force: true }); }); Cypress.Commands.add("selectValue", (element, valueText) => { @@ -99,28 +193,13 @@ Cypress.Commands.add("selectColor", (element, colorCode) => { cy.clickElement('.cly-vue-button.button-green-skin'); }); -Cypress.Commands.add('dragAndDropFile', (element, filePath) => { - cy.getElement(element) - .attachFile(filePath, { - encoding: 'utf-8', - subjectType: 'drag-n-drop' - }); -}); - -Cypress.Commands.add('uploadFile', (filePath) => { - cy.get('input[type="file"]').attachFile(filePath, { force: true }); -}); - +// ───────────────────────────────────────────── +// Assertions and Element Checks +// ───────────────────────────────────────────── Cypress.Commands.add("shouldTooltipContainText", (element, text) => { - cy.getElement(element) - .eq(0).invoke('show') - .trigger('mouseenter'); - + cy.getElement(element).eq(0).invoke('show').trigger('mouseenter'); cy.shouldContainText('.tooltip-inner', text); - - cy.getElement(element) - .eq(0).invoke('show') - .trigger('mouseleave'); + cy.getElement(element).eq(0).invoke('show').trigger('mouseleave'); }); Cypress.Commands.add("shouldBeVisible", (element) => { @@ -131,6 +210,10 @@ Cypress.Commands.add("shouldBeDisabled", (element) => { cy.getElement(element).should("be.disabled"); }); +Cypress.Commands.add("shouldNotBeDisabled", (element) => { + cy.getElement(element).should("not.be.disabled"); +}); + Cypress.Commands.add("shouldBeHasDisabledClass", (element) => { cy.get(`[data-test-id="${element}"].is-disabled`).should("exist"); }); @@ -139,10 +222,6 @@ Cypress.Commands.add("shouldNotBeHasDisabledClass", (element) => { cy.get(`[data-test-id="${element}"].is-disabled`).should("not.exist"); }); -Cypress.Commands.add("shouldNotBeDisabled", (element) => { - cy.getElement(element).should("not.be.disabled"); -}); - Cypress.Commands.add("shouldContainText", (element, text) => { cy.getElement(element).should("contain", text); }); @@ -181,18 +260,18 @@ Cypress.Commands.add("shouldUrlInclude", (url) => { cy.url().should('include', url); }); +// ───────────────────────────────────────────── +// Utility Checks +// ───────────────────────────────────────────── Cypress.Commands.add('elementExists', (selector) => { - cy.wait(500); if (!selector.includes('[data-test-id=') && (!selector[0].includes('.') || !selector[0].includes('#'))) { selector = `[data-test-id="${selector}"]`; } - cy - .get('body') - .then(($body) => { - return $body.find(selector).length > 0; - }); + cy.get('body').then(($body) => { + return $body.find(selector).length > 0; + }); }); Cypress.Commands.add('shouldBeExist', (element) => { @@ -204,25 +283,24 @@ Cypress.Commands.add('shouldNotExist', (element) => { }); Cypress.Commands.add('checkPaceRunning', () => { - cy - .elementExists('.pace-running') - .then((isExists) => { - if (isExists) { - cy.shouldNotExist('.pace-running'); - } - }); + cy.elementExists('.pace-running').then((isExists) => { + if (isExists) { + cy.shouldNotExist('.pace-running'); + } + }); }); Cypress.Commands.add('checkPaceActive', () => { - cy - .elementExists('.pace-active') - .then((isExists) => { - if (isExists) { - cy.shouldNotExist('.pace-active'); - } - }); + cy.elementExists('.pace-active').then((isExists) => { + if (isExists) { + cy.shouldNotExist('.pace-active'); + } + }); }); +// ───────────────────────────────────────────── +// Scroll Helpers +// ───────────────────────────────────────────── Cypress.Commands.add("scrollPageSlightly", (element = '.main-view', index = 0) => { cy.get(element).eq(index).then(($el) => { const currentScroll = $el[0].scrollTop; @@ -255,6 +333,9 @@ Cypress.Commands.add("scrollDataTableToLeft", (element = '.el-table__body-wrappe cy.get(element).eq(index).scrollTo('left', { ensureScrollable: false }); }); +// ───────────────────────────────────────────── +// verifyElement — Soft Assertion Aware +// ───────────────────────────────────────────── Cypress.Commands.add('verifyElement', ({ labelElement, labelText, @@ -279,103 +360,112 @@ Cypress.Commands.add('verifyElement', ({ if (!shouldNot) { - if (labelElement != null && isElementVisible === true) { - cy.shouldBeVisible(labelElement); + if (labelElement && isElementVisible) { + cy.safeCheck(() => cy.shouldBeVisible(labelElement), `Label element "${labelElement}" should be visible`); } - if (labelText != null) { - cy.shouldContainText(labelElement, labelText); + if (labelText) { + cy.safeCheck(() => cy.shouldContainText(labelElement, labelText), `Label text mismatch for "${labelElement}"`); } - if (tooltipElement != null) { - cy.shouldBeVisible(tooltipElement); + if (tooltipElement) { + cy.safeCheck(() => cy.shouldBeVisible(tooltipElement), `Tooltip element "${tooltipElement}" should be visible`); } - if (tooltipText != null) { - cy.shouldTooltipContainText(tooltipElement, tooltipText); + if (tooltipText) { + cy.safeCheck(() => cy.shouldTooltipContainText(tooltipElement, tooltipText), `Tooltip text mismatch for "${tooltipElement}"`); } - if (element != null && isElementVisible === true) { - cy.shouldBeVisible(element); + if (element && isElementVisible) { + cy.safeCheck(() => cy.shouldBeVisible(element), `Element "${element}" should be visible`); } - if (elementText != null) { - cy.shouldContainText(element, elementText); + if (elementText) { + cy.safeCheck(() => cy.shouldContainText(element, elementText), `Element text mismatch for "${element}"`); } - if (elementPlaceHolder != null) { - cy.shouldPlaceholderContainText(element, elementPlaceHolder); + if (elementPlaceHolder) { + cy.safeCheck(() => cy.shouldPlaceholderContainText(element, elementPlaceHolder), `Placeholder mismatch for "${element}"`); } - if (hrefContainUrl != null) { - cy.shouldHrefContainUrl(element, hrefContainUrl); + if (hrefContainUrl) { + cy.safeCheck(() => cy.shouldHrefContainUrl(element, hrefContainUrl), `Href mismatch for "${element}"`); } - if (value != null) { - cy.shouldHaveValue(element, value); + if (value) { + cy.safeCheck(() => cy.shouldHaveValue(element, value), `Value mismatch for "${element}"`); } if (isChecked != null) { - isChecked ? cy.shouldBeVisible(`[data-test-id="${element}"]` + '.is-checked') : cy.shouldNotExist(`[data-test-id="${element}"]` + '.is-checked'); + const selector = `[data-test-id="${element}"]`; + if (isChecked) { + cy.safeCheck(() => cy.shouldBeVisible(selector + '.is-checked'), `Element "${selector}" should be checked`); + } + else { + cy.safeCheck(() => cy.shouldNotExist(selector + '.is-checked'), `Element "${selector}" should not be checked`); + } } if (isDisabled != null) { - isDisabled ? cy.shouldBeDisabled(element) : cy.shouldNotBeDisabled(element); + if (isDisabled) { + cy.safeCheck(() => cy.shouldBeDisabled(element), `Element "${element}" should be disabled`); + } + else { + cy.safeCheck(() => cy.shouldNotBeDisabled(element), `Element "${element}" should not be disabled`); + } } - if (selectedIconColor != null) { - var selector; - unVisibleElement != null ? selector = unVisibleElement : selector = element; - cy.getElement(`[data-test-id="${selector}"]`).invoke("attr", "style").should("contain", helper.hexToRgb(selectedIconColor)); + if (selectedIconColor) { + const selector = unVisibleElement || element; + cy.safeCheck(() => { + cy.getElement(`[data-test-id="${selector}"]`) + .invoke('attr', 'style') + .should('contain', helper.hexToRgb(selectedIconColor)); + }, `Selected icon color mismatch for "${selector}"`); } - if (selectedFontColor != null) { - cy.getElement(`[data-test-id="${element}"]`).invoke("attr", "style").should("contain", helper.hexToRgb(selectedFontColor)); + if (selectedFontColor) { + cy.safeCheck(() => { + cy.getElement(`[data-test-id="${element}"]`) + .invoke('attr', 'style') + .should('contain', helper.hexToRgb(selectedFontColor)); + }, `Selected font color mismatch for "${element}"`); } - if (selectedMainColor != null) { - cy.getElement(`[data-test-id="${element}"]`).invoke("attr", "style").should("contain", helper.hexToRgb(selectedMainColor)); + if (selectedMainColor) { + cy.safeCheck(() => { + cy.getElement(`[data-test-id="${element}"]`) + .invoke('attr', 'style') + .should('contain', helper.hexToRgb(selectedMainColor)); + }, `Selected main color mismatch for "${element}"`); } - if (attr != null && attrText != null) { - cy.getElement(`[data-test-id="${element}"]`).invoke("attr", attr).should("contain", attrText); + if (attr && attrText) { + cy.safeCheck(() => { + cy.getElement(`[data-test-id="${element}"]`) + .invoke('attr', attr) + .should('contain', attrText); + }, `Attribute "${attr}" mismatch for "${element}"`); } + } else { - if (element != null && isElementVisible === true) { - cy.shouldBeVisible(element); - cy.shouldNotBeEqual(element, elementText); + if (element && isElementVisible) { + cy.safeCheck(() => cy.shouldBeVisible(element), `Element "${element}" should be visible`); + cy.safeCheck(() => cy.shouldNotBeEqual(element, elementText), `Element "${element}" text should not equal "${elementText}"`); } - if (labelElement != null && isElementVisible === true) { - cy.shouldBeVisible(labelElement); - cy.shouldNotBeEqual(labelElement, labelText); + if (labelElement && isElementVisible) { + cy.safeCheck(() => cy.shouldBeVisible(labelElement), `Label element "${labelElement}" should be visible`); + cy.safeCheck(() => cy.shouldNotBeEqual(labelElement, labelText), `Label text should not equal "${labelText}"`); } } }); +// ───────────────────────────────────────────── +// DB helper +// ───────────────────────────────────────────── Cypress.Commands.add('dropMongoDatabase', () => { cy.exec("mongosh mongodb/countly --eval 'db.dropDatabase()'"); -}); - -Cypress.Commands.add('getElement', (selector, parent = null) => { - - if (!selector.includes('[data-test-id=')) { - if (selector[0].includes('.') || selector[0].includes('#')) { - return cy.get(selector); - } - else { - if (parent !== null) { - selector = `${parent} [data-test-id="${selector}"]`; - } - else { - selector = `[data-test-id="${selector}"]`; - } - return cy.get(selector); - } - } - else { - return cy.get(selector); - } }); \ No newline at end of file