diff --git a/cypress.config.js b/cypress.config.js index a6a3def7..7ec39405 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -6,6 +6,12 @@ module.exports = defineConfig({ supportFile: 'src/test/cypress/support/index.js', fixturesFolder: 'src/test/cypress/fixtures', specPattern: 'src/test/cypress/integration/**/*.spec.js', + viewportHeight: 660, + viewportWidth: 1240, + experimentalMemoryManagement: true, + numTestsKeptInMemory: 0, + video: false, + screenshotOnRunFailure: false, setupNodeEvents(on, config) { // implement node event listeners here } diff --git a/package.json b/package.json index c386547c..eb2dd589 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,10 @@ "scripts": { "front": "webpack serve --open", "back": "cd ../vn-database && myvc start && cd ../salix && gulp backOnly", - "cy:open": "cypress open", - "test:e2e": "cypress run", + "cy:open": "cd ../salix && gulp docker && cd ../hedera-web && cypress open", + "test:e2e": "cd ../salix && gulp docker && cd ../hedera-web && cypress run", + "cy:open-mindshore": "cd ../salix && gulp docker && cd ../hedera-web-mindshore && cypress open", + "test:e2e-mindshore": "cd ../salix && gulp docker && cd ../hedera-web-mindshore && cypress run", "build": "rm -rf build/ ; webpack", "clean": "rm -rf build/", "lint": "eslint --ext .js,.vue ./" diff --git a/src/composables/useNotify.js b/src/composables/useNotify.js index aa8d68f3..8c0175fa 100644 --- a/src/composables/useNotify.js +++ b/src/composables/useNotify.js @@ -12,7 +12,10 @@ export default function useNotify() { Notify.create({ message: i18n.global.t(message), type, - icon: icon || defaultIcons[type] + icon: icon || defaultIcons[type], + attrs: { + 'data-testid': `${type}Notify` + } }); }; diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index d3262f91..4cc19c21 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -88,7 +88,13 @@ const logoutSupplantedUser = async () => { <div class="user-info"> <div> <span id="user-name">{{ mainUser?.nickname }}</span> - <QBtn flat icon="logout" alt="_Exit" @click="logout()" /> + <QBtn + flat + icon="logout" + alt="_Exit" + @click="logout()" + data-testid="logoutButton" + /> </div> <div v-if="supplantedUser" id="supplant" class="supplant"> <span id="supplanted"> diff --git a/src/pages/Ecomerce/CatalogCard.vue b/src/pages/Ecomerce/CatalogCard.vue index a53ce109..d8680c1d 100644 --- a/src/pages/Ecomerce/CatalogCard.vue +++ b/src/pages/Ecomerce/CatalogCard.vue @@ -15,7 +15,12 @@ const { t } = useI18n(); </script> <template> - <QCard v-if="viewMode === 'grid'" v-ripple class="catalog-card"> + <QCard + v-if="viewMode === 'grid'" + v-ripple + class="catalog-card" + data-testid="catalogCardGrid" + > <VnImg storage="catalog" size="200x200" @@ -23,7 +28,11 @@ const { t } = useI18n(); height="210px" rounded="bottom" /> - <div class="column" style="height: 205px; padding: 10px"> + <div + class="column" + style="height: 205px; padding: 10px" + data-testid="catalogCardGridBody" + > <div class="column" style="margin-bottom: auto"> <div class="text-subtitle2 ellipsis-2-lines"> {{ item.item }} @@ -98,7 +107,7 @@ const { t } = useI18n(); </div> </div> </QCard> - <CardList v-else class="vn-w-sm"> + <CardList v-else class="vn-w-sm" data-testid="catalogCardList"> <template #prepend> <VnImg storage="catalog" diff --git a/src/pages/Ecomerce/CatalogView.vue b/src/pages/Ecomerce/CatalogView.vue index e203a3d5..ed7f8bee 100644 --- a/src/pages/Ecomerce/CatalogView.vue +++ b/src/pages/Ecomerce/CatalogView.vue @@ -19,6 +19,7 @@ @click="redirectToBasket()" rounded no-caps + data-testid="catalogGoToBasketButton" > <QTooltip> {{ t('shoppingCart') }} @@ -36,7 +37,7 @@ </div> </Teleport> <div style="padding-bottom: 5em"> - <QDrawer v-model="rightDrawerOpen" side="right" :width="250"> + <QDrawer v-model="rightDrawerOpen" side="right" :width="250" persistent> <div class="q-pa-md"> <div class="basket-info q-gutter-y-sm"> <span v-if="order?.nickname">{{ order.nickname }}</span> @@ -49,7 +50,12 @@ }) }} </span> - <QBtn rounded no-caps @click="redirectToCheckout()"> + <QBtn + rounded + no-caps + @click="redirectToCheckout()" + data-testid="orderModifyButton" + > {{ t('modify') }} </QBtn> </div> @@ -74,6 +80,7 @@ :class="{ active: category == cat.id }" :key="cat.id" @click="selectedCategory = cat.id" + data-testid="catalogCategoryButton" > <img :src="`statics/category/${cat.code}.svg`" /> <QTooltip>{{ cat.name }}</QTooltip> @@ -91,6 +98,7 @@ :options="itemFamilies" :disable="!category" :label="t('family')" + data-testid="catalogFamilySelect" /> <VnSelect v-model="selectedColor" @@ -243,6 +251,7 @@ flat dense @click="onAddLotClick(lot)" + data-testid="addItemQuantityButton" > <QTooltip>{{ t('add') }}</QTooltip> </QBtn> @@ -267,6 +276,7 @@ flat color="white" @click="onConfirmClick()" + data-testid="catalogAddToBasketButton" > <QTooltip>{{ t('confirm') }}</QTooltip> </QBtn> diff --git a/src/test/cypress/integration/UserFlows.spec.js b/src/test/cypress/integration/UserFlows.spec.js index a5ce3254..e22f0359 100644 --- a/src/test/cypress/integration/UserFlows.spec.js +++ b/src/test/cypress/integration/UserFlows.spec.js @@ -1,60 +1,10 @@ -describe('User flow 1', () => { - // 1- Loguear como empleado - // 2- Ir a la sección de pedidos pendientes - // 3- Crear un pedido - beforeEach(() => { - cy.changeLanguage('es'); - }); - - const checkoutNextStep = () => { - cy.get('[data-testid="checkoutStepperRightButton"]') - .should('be.visible') - .click(); - }; - - it('should success', () => { - // 1- Loguear como empleado - cy.loginFlow('employee'); - - // 2- Ir a la sección de pedidos pendientes - cy.visit('/#/ecomerce/pending'); - cy.get('[data-testid="pendingOrdersNewOrder"]').should('exist'); - cy.get('[data-testid="pendingOrdersNewOrder"]').click(); - // 3- Crear un pedido - cy.url().should('contain', '/#/ecomerce/checkout'); - cy.get('[data-testid="checkoutStepper"]').should('exist'); - cy.get('[data-testid="checkoutStepper"]').should( - 'contain', - '¿Quieres recibir o recoger el pedido?' - ); - cy.get('[aria-label="Recibir en mi tienda"]').click(); - - checkoutNextStep(); - cy.get('[data-testid="checkoutStepper"]').should( - 'contain', - '¿Qué día quieres recibir el pedido?' - ); - checkoutNextStep(); - cy.get('[data-testid="checkoutStepper"]').should( - 'contain', - '¿Dónde quieres recibir el pedido?' - ); - cy.get('[data-testid="checkoutAddressStep"]').within(() => { - cy.get('label:first').click(); - }); - checkoutNextStep(); - cy.get('[data-testid="checkoutStepper"]').should( - 'contain', - '¿Cómo quieres recibir el pedido?' - ); - cy.get('[data-testid="agencyStepSelect"]').should('exist'); - cy.selectOption('[data-testid="agencyStepSelect"]', 'Other agency'); - checkoutNextStep(); - checkoutNextStep(); - cy.url().should('contain', '/#/ecomerce/catalog'); - cy.get('.q-notification__message:first').should( - 'have.text', - '¡Pedido cargado en la cesta!' - ); +describe('User flow: Login, create a new order, add item to basket', () => { + it('success', () => { + // 2- Loguear como empleado + cy.login('employee'); + // 2- Crear una orden + cy.CreateOrderReceiveFlow(); + // 3- Filtrar items y agregar item al carrito + cy.AddItemToBasketFlow(); }); }); diff --git a/src/test/cypress/integration/catalog/CatalogView.spec.js b/src/test/cypress/integration/catalog/CatalogView.spec.js new file mode 100644 index 00000000..c245c522 --- /dev/null +++ b/src/test/cypress/integration/catalog/CatalogView.spec.js @@ -0,0 +1,16 @@ +describe('CatalogView', () => { + beforeEach(() => { + // 1- Loguear usuario + cy.login('employee'); + // 2- Crear una orden + cy.CreateOrderReceiveFlow(); + }); + + it('Adds item to basket', () => cy.AddItemToBasketFlow()); + + it('Goes to basket', () => { + cy.get('[data-testid="catalogGoToBasketButton"]').should('exist'); + cy.get('[data-testid="catalogGoToBasketButton"]').click(); + cy.url().should('contain', '/#/ecomerce/basket'); + }); +}); diff --git a/src/test/cypress/integration/catalog/CatalogViewCommands.js b/src/test/cypress/integration/catalog/CatalogViewCommands.js new file mode 100644 index 00000000..b3ff438f --- /dev/null +++ b/src/test/cypress/integration/catalog/CatalogViewCommands.js @@ -0,0 +1,22 @@ +Cypress.Commands.add('AddItemToBasketFlow', () => { + // 1- Seleccionar categoría + cy.get('[data-testid="catalogCategoryButton"]').should('exist'); + cy.get('[data-testid="catalogCategoryButton"]:first').click(); + // 2- Seleccionar familia + cy.get('[data-testid="catalogFamilySelect"]').should('exist'); + cy.selectOption('[data-testid="catalogFamilySelect"]', 'Anthurium'); + cy.getValue('[data-testid="catalogFamilySelect"]').should( + 'equal', + 'Anthurium' + ); + cy.get('[data-testid="catalogFamilySelect"]').should('exist'); + // 3- Seleccionar item + cy.get('[data-testid="catalogCardGridBody"]').should('exist'); + cy.get('[data-testid="catalogCardGridBody"]:first').click(); + // 4- Añadir item al carrito + cy.get('[data-testid="addItemQuantityButton"]').should('exist'); + cy.get('[data-testid="addItemQuantityButton"]:first').click(); + cy.get('[data-testid="catalogAddToBasketButton"]').should('exist'); + cy.get('[data-testid="catalogAddToBasketButton"]').click(); + cy.get('[data-testid="positiveNotify"]').should('include.text', 'Añadido'); +}); diff --git a/src/test/cypress/integration/checkout/CheckoutStepper.spec.js b/src/test/cypress/integration/checkout/CheckoutStepper.spec.js new file mode 100644 index 00000000..26b54665 --- /dev/null +++ b/src/test/cypress/integration/checkout/CheckoutStepper.spec.js @@ -0,0 +1,23 @@ +describe('CheckoutStepper', () => { + beforeEach(() => { + cy.login('employee'); + }); + + it('Creates new order to receive', () => cy.CreateOrderReceiveFlow()); + + it('Creates new pickup order', () => cy.CreateOrderPickupFlow()); + + it('Modifies an order', () => { + cy.CreateOrderReceiveFlow(); + cy.get('[data-testid="orderModifyButton"]').click(); + cy.get('[data-testid="warningNotify"]').should( + 'include.text', + 'Recuerda que si vuelves a configurar el pedido los precios o cantidades de tus artículos podrían cambiar' + ); + cy.CreateOrderPickup(false); + cy.get('[data-testid="positiveNotify"]').should( + 'include.text', + 'Pedido actualizado' + ); + }); +}); diff --git a/src/test/cypress/integration/checkout/CheckoutStepperCommands.js b/src/test/cypress/integration/checkout/CheckoutStepperCommands.js new file mode 100644 index 00000000..0441b570 --- /dev/null +++ b/src/test/cypress/integration/checkout/CheckoutStepperCommands.js @@ -0,0 +1,95 @@ +const checkoutNextStep = () => { + cy.get('[data-testid="checkoutStepperRightButton"]') + .should('be.visible') + .click(); +}; + +Cypress.Commands.add('CreateOrderReceive', () => { + cy.get('[data-testid="checkoutStepper"]').should('exist'); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Quieres recibir o recoger el pedido?' + ); + cy.get('[aria-label="Recibir en mi tienda"]').click(); + + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Qué día quieres recibir el pedido?' + ); + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Dónde quieres recibir el pedido?' + ); + cy.get('[data-testid="checkoutAddressStep"]').within(() => { + cy.get('label:first').click(); + }); + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Cómo quieres recibir el pedido?' + ); + cy.get('[data-testid="agencyStepSelect"]').should('exist'); + cy.selectOption('[data-testid="agencyStepSelect"]', 'Other agency'); + checkoutNextStep(); + checkoutNextStep(); + cy.url().should('contain', '/#/ecomerce/catalog'); +}); + +Cypress.Commands.add('CreateOrderReceiveFlow', (changeLanguage = false) => { + if (changeLanguage) cy.changeLanguageFlow('es'); + cy.visit('/#/ecomerce/pending'); + cy.get('[data-testid="pendingOrdersNewOrder"]').should('exist'); + cy.get('[data-testid="pendingOrdersNewOrder"]').click(); + cy.CreateOrderReceive(); + cy.get('.q-notification__message:first').should( + 'have.text', + '¡Pedido cargado en la cesta!' + ); +}); + +Cypress.Commands.add('CreateOrderPickup', () => { + cy.get('[data-testid="checkoutStepper"]').should('exist'); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Quieres recibir o recoger el pedido?' + ); + cy.get('[aria-label="Recoger en almacén"]').click(); + + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿Qué día quieres recibir el pedido?' + ); + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿A qué dirección quieres asociar el pedido? (Opcional)' + ); + cy.get('[data-testid="checkoutAddressStep"]').within(() => { + cy.get('label:first').click(); + }); + checkoutNextStep(); + cy.get('[data-testid="checkoutStepper"]').should( + 'contain', + '¿En qué almacén quieres recoger tu pedido?' + ); + cy.get('[data-testid="pickupStepSelect"]').should('exist'); + cy.selectOption('[data-testid="pickupStepSelect"]', 'Teleportation device'); + checkoutNextStep(); + checkoutNextStep(); + cy.url().should('contain', '/#/ecomerce/catalog'); +}); + +Cypress.Commands.add('CreateOrderPickupFlow', (changeLanguage = false) => { + if (changeLanguage) cy.changeLanguageFlow('es'); + cy.visit('/#/ecomerce/pending'); + cy.get('[data-testid="pendingOrdersNewOrder"]').should('exist'); + cy.get('[data-testid="pendingOrdersNewOrder"]').click(); + cy.CreateOrderPickup(); + cy.get('.q-notification__message:first').should( + 'have.text', + '¡Pedido cargado en la cesta!' + ); +}); diff --git a/src/test/cypress/integration/login/LoginViewCommands.js b/src/test/cypress/integration/login/LoginViewCommands.js new file mode 100644 index 00000000..af464a13 --- /dev/null +++ b/src/test/cypress/integration/login/LoginViewCommands.js @@ -0,0 +1,69 @@ +// Login view commands +Cypress.Commands.add('login', user => { + cy.request({ + method: 'POST', + url: '/api/Accounts/login', + body: { + user, + password: 'nightmare' + } + }).then(response => { + window.localStorage.setItem('token', response.body.token); + cy.request({ + method: 'GET', + url: '/api/VnUsers/ShareToken', + headers: { + Authorization: window.localStorage.getItem('token') + } + }).then(({ body }) => { + window.localStorage.setItem( + 'tokenMultimedia', + body.multimediaToken.id + ); + }); + }); +}); + +Cypress.Commands.add('logout', user => { + cy.request({ + method: 'POST', + url: '/api/Accounts/logout', + headers: { + Authorization: window.localStorage.getItem('token') + } + }).then(response => { + localStorage.clear(); + sessionStorage.clear(); + }); +}); + +Cypress.Commands.add('loginFlow', (user, visitLogin = true) => { + if (visitLogin) cy.visit('/#/login'); + cy.get('[data-testid="loginUserInput"]').type(user); + cy.getValue('[data-testid="loginUserInput"]').should('equal', user); + cy.get('[data-testid="loginPasswordInput"]').type('nightmare'); + cy.getValue('[data-testid="loginPasswordInput"]').should( + 'equal', + 'nightmare' + ); + + cy.get('button[type="submit"]').click(); + cy.url().should('contain', '/#/cms/home'); +}); + +Cypress.Commands.add('waitForElement', (element, timeout = 5000) => { + cy.get(element, { timeout }).should('be.visible'); +}); + +Cypress.Commands.add('changeLanguage', language => { + const languagesOrder = ['en', 'es', 'ca', 'fr', 'pt']; + const index = languagesOrder.indexOf(language); + cy.waitForElement('[data-testid="switchLanguage"]'); + cy.get('[data-testid="switchLanguage"]').click(); + cy.get('.q-menu .q-item').eq(index).click(); // Selecciona y hace clic en el tercer elemento "index" de la lista +}); + +Cypress.Commands.add('changeLanguageFlow', language => { + cy.visit('/#/login'); + cy.changeLanguage(language); +}); diff --git a/src/test/cypress/support/commands.js b/src/test/cypress/support/commands.js index 5d15cf8f..ec169e53 100644 --- a/src/test/cypress/support/commands.js +++ b/src/test/cypress/support/commands.js @@ -24,65 +24,24 @@ // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -Cypress.Commands.add('login', user => { - cy.request({ - method: 'POST', - url: '/api/Accounts/login', - body: { - user, - password: 'nightmare' - } - }).then(response => { - window.localStorage.setItem('token', response.body.token); - cy.request({ - method: 'GET', - url: '/api/VnUsers/ShareToken', - headers: { - Authorization: window.localStorage.getItem('token') - } - }).then(({ body }) => { - window.localStorage.setItem( - 'tokenMultimedia', - body.multimediaToken.id - ); - }); - }); -}); +import '../integration/catalog/CatalogViewCommands'; // CatalogView component commands +import '../integration/login/LoginViewCommands'; // LoginView component commands +import '../integration/checkout/CheckoutStepperCommands'; // CheckoutStepper component commands -Cypress.Commands.add('loginFlow', user => { - cy.visit('/#/login'); - cy.get('[data-testid="loginUserInput"]').type(user); - cy.get('[data-testid="loginPasswordInput"]').type('nightmare'); - cy.get('button[type="submit"]').click(); - cy.url().should('contain', '/#/cms/home'); -}); - -Cypress.Commands.add('logout', user => { - cy.request({ - method: 'POST', - url: 'Accounts/logout' - }).then(response => { - localStorage.clear(); - sessionStorage.clear(); - }); -}); - -Cypress.Commands.add('waitForElement', (element, timeout = 5000) => { - cy.get(element, { timeout }).should('be.visible'); -}); - -Cypress.Commands.add('changeLanguage', language => { - const languagesOrder = ['en', 'es', 'ca', 'fr', 'pt']; - const index = languagesOrder.indexOf(language); - cy.visit('/#/login'); - cy.waitForElement('[data-testid="switchLanguage"]'); - cy.get('[data-testid="switchLanguage"]').click(); - cy.get('.q-menu .q-item').eq(index).click(); // Selecciona y hace clic en el tercer elemento "index" de la lista -}); - -// Fill Inputs +// Common commands Cypress.Commands.add('selectOption', (selector, option) => { cy.waitForElement(selector); cy.get(selector).click(); cy.get('.q-menu .q-item').contains(option).click(); }); + +Cypress.Commands.add('getValue', selector => { + cy.get(selector).then($el => { + if ($el.find('.q-checkbox__inner').length > 0) { + // retornar el valor del atributo aria-checked + return cy.get(selector).invoke('attr', 'aria-checked'); + } else { + return cy.get(selector).invoke('val'); + } + }); +});