{"id":11280,"date":"2024-04-22T18:59:55","date_gmt":"2024-04-22T18:59:55","guid":{"rendered":"https:\/\/testgrid.io\/blog\/?p=11280"},"modified":"2024-08-01T09:05:05","modified_gmt":"2024-08-01T09:05:05","slug":"cypress-best-practices","status":"publish","type":"post","link":"https:\/\/testgrid.io\/blog\/cypress-best-practices\/","title":{"rendered":"10 Best Practices to Improve Your Cypress Testing"},"content":{"rendered":"\n<p>In the current software testing landscape, testing complexity is on the rise. However, modern frameworks like Cypress simplify executing component and <a href=\"https:\/\/testgrid.io\/blog\/end-to-end-testing-a-detailed-guide\/\">end to end testing<\/a>. Here are some essential considerations for developing an e2e UI testing framework with Cypress.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Defining AUT scopes:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The purpose of <a href=\"https:\/\/testgrid.io\/blog\/cypress-testing\/\">Cypress testing<\/a> is purely for automating end-to-end (e2e) tests. So when writing the tests, we should ensure that they focus solely on the application&#8217;s functionality\/journey.<\/li>\n\n\n\n<li><em>If any third party site, systems dependency is required <\/em>for the respective test means then we <em>should not have that test in our e2e suite<\/em>.<\/li>\n<\/ul>\n\n\n\n<p>If the test involves some other origin, then we need to use the \u2018cy.origin()\u2019 method.<\/p>\n\n\n\n<p>Refer: https:\/\/docs.cypress.io\/api\/commands\/origin<br>Better avoid automating anything in the secondary origin URLs.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>it('Dont automate anything in the secondary origin', () =&gt; {\n       cy.visit('https:\/\/webdriveruniversity.com\/'); \/\/primary origin\n       cy.get('#automation-test-store').click();\/\/click the link, which navigates to\n       cy.visit('https:\/\/automationteststore.com\/') \/\/secondary origin in new tab\n   });<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Test Isolation:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The test \u2018it\u2019 blocks in the e2e suite should be designed in a way that <strong><em>each blocks are independent and are not linked or dependent on other \u2018it\u2019 blocks.<\/em><\/strong><\/li>\n\n\n\n<li>This enables the suite to be more robust and we can achieve parallel execution when implemented in continuous integration servers.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>beforeEach('Login', () =&gt; {\n cy.visit('https:\/\/katalon-demo-cura.herokuapp.com');\n cy.get('#btn-make-appointment').click();\n cy.get('#txt-username').type(\"John Doe\");\n cy.get('#txt-password').type('ThisIsNotAPassword');\n cy.get('#btn-login').click();\n});\n\nit('using type, checkbox, radiobox in the forms', () =&gt; {\n cy.get('#combo_facility').select('Seoul CURA Healthcare Center');\n cy.get('#combo_facility').select(1);\ncy.get('#chk_hospotal_readmission').not('&#91;disabled]').check().should('be.checked');\ncy.get('#chk_hospotal_readmission').not('&#91;disabled]').uncheck().should('not.be.checked');\n});\n\n\/\/This is not the correct way\nit(\u2018same flow- but separate test\u2019, () =&gt; { \n cy.get('.radio-inline &#91;type=\"radio\"]').check('Medicare').should('be.checked');\n cy.get('#radio_program_medicaid').check();\n CalendarUtils.selectDate('.input-group-addon', '24-08-2023');\n cy.get('#btn-book-appointment').click();\n cy.contains('Appointment Confirmation');\n cy.get('div&#91;class=\"col-xs-12 text-center\"] h2', { timeout: 10000 })\n });<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Proper usage of custom commands:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>By default, cypress has the feature of creating custom functions.<\/li>\n\n\n\n<li>We can create the function by defining the parameters<\/li>\n\n\n\n<li>In the test, we can just invoke the custom function with the expected arguments<\/li>\n\n\n\n<li>This will <strong><em>help encapsulate the actions and other logic in the test<\/em><\/strong><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>Cypress.Commands.add('login', (username, password) =&gt; {\n   cy.visit('https:\/\/katalon-demo-cura.herokuapp.com')\n   cy.get('#btn-make-appointment').click()\n   cy.get('#txt-username').type(username)\n   cy.get('#txt-password').type(password)\n   cy.get('#btn-login').click()\n})<\/code><\/pre>\n\n\n\n<p>\/\/Invoking the \u2018login\u2019 function in beforeEach<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>describe('Command specs', () =&gt; {\nbeforeEach('Login', () =&gt; {\n   cy.login('John Doe','ThisIsNotAPassword')\n});\n\n\nit('add registration details', () =&gt; {\n   cy.makeAppointment('2023-08-23', 'Seoul CURA Healthcare Center', 'Medicaid');\n});\n})\n<\/code><\/pre>\n\n\n\n<p>By creating this kind of custom commands, we can make the actual tests clean and focus solely on the functionality<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using appropriate function\/property names:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>When using <strong><em>POM [Page Object Model]<\/em><\/strong> kind frameworks, we solely create classes, functions, objects, variables, selectors for modularising the framework.<\/li>\n\n\n\n<li>For easy maintenance and clean code we should ensure that the naming convention is <strong><em>\u2018consistent and relevant\u2019<\/em><\/strong><\/li>\n<\/ul>\n\n\n\n<p>Reference for Locator naming:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>class AppointmentPageObjects {\n   \/\/ Element references\n   buttonMakeAppointment = '#btn-make-appointment';\n   textUsername = '#txt-username';\n   textPassword = '#txt-password';\n   buttonLogin = '#btn-login';\n   comboFacility = '#combo_facility';\n   checkboxHospitalReadmission = '#chk_hospotal_readmission';\n   radioMedicare = '.radio-inline &#91;type=\"radio\"]';\n   radioMedicaid = '#radio_program_medicaid';\n   textComment = 'txt_comment'\n   datePicker = '.input-group-addon';\n   buttonBookAppointment = '#btn-book-appointment';\n   confirmationMessage = 'div&#91;class=\"col-xs-12 text-center\"] h2';\n}\n\nexport default AppointmentPageObjects;<\/code><\/pre>\n\n\n\n<p>Reference for the function naming:<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>enterUsername(username) {\n       cy.get(this.pageObjects.txtUsername).type(username);\n   }\nenterPassword(password) {\n       cy.get(this.pageObjects.txtPassword).type(password);\n   }\nclickLogin() {\n       cy.get(this.pageObjects.btnLogin).click();\n   }\nselectFacilityByName(name) {\n       cy.get(this.pageObjects.comboFacility).select(name);\n   }\ncheckReadmission() {\n    cy.get(this.pageObjects.chkHospitalReadmission).not('&#91;disabled]').check();\n   }\nuncheckReadmission() {   cy.get(this.pageObjects.chkHospitalReadmission).not('&#91;disabled]').uncheck();\n   }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using fixtures for effective data driven tests:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>By default, cypress has the \u2018fixture\u2019 functions, so that we can pass the test datas<\/li>\n\n\n\n<li>The most common data type is \u2018json\u2019, cypress supports other formats as well, please refer to the documentation https:\/\/docs.cypress.io\/api\/commands\/fixture<\/li>\n<\/ul>\n\n\n\n<p><strong>users.json<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>&#91;\n   {\n        \"username\": \"standard_user\",\n        \"password\": \"secret_sauce\"\n    },\n    {\n        \"username\": \"problem_user\",\n        \"password\": \"secret_sauce\"\n    },\n    {\n        \"username\": \"locked_out_user\",\n        \"password\": \"secret_sauce\"\n    },\n    {\n        \"username\": \"performance_glitch_user\",\n        \"password\": \"secret_sauce\"\n    }\n]<\/code><\/pre>\n\n\n\n<p><strong>Invoking the test data in the actual tests<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>it('Test2- login with multiple credentials', () =&gt; {\n   cy.fixture('users').then((users) =&gt; {\n       users.forEach(user =&gt; {\n           \/\/ your test logic\n           cy.visit('https:\/\/www.saucedemo.com\/');\n           cy.get('#user-name').type(user.username);\n           cy.get('#password').type(user.password);\n           cy.get('#login-button').click();\n          \n       });\n   });\n});\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using web first assertions &amp; having multiple assertions:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>First, let&#8217;s see what is a web first assertion and what is not a web first assertion.\n<ul class=\"wp-block-list\">\n<li><strong>Web First Assertions<\/strong> focus on interactions with the web application through its UI or API, simulating user actions and verifying outcomes such as UI updates, navigation, and API responses.<\/li>\n\n\n\n<li><strong>Non-Web First Assertions<\/strong> focus on JavaScript functions or logic within the application context and asserting their outcomes. This approach is more about verifying the internal logic than user interaction scenarios.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>\/\/ Web-first assertion: Wait for a specific elements\ncy.get('.success-message').should('be.visible');\ncy.get('.radio-inline &#91;type=\"radio\"]').check('Medicare').should('be.checked')\ncy.get('.left-nav').children().should('have.length', 8)\ncy.get('@xyz').click().should('be.disabled')\n\n\n\/\/Non-web first assertion:\ncy.window().then((win) =&gt; {\n   const sum = win.add(5, 3);\n   expect(sum).to.equal(8);\n });\n<\/code><\/pre>\n\n\n\n<p>Designing tests to have multiple assertions in a single test is good, instead of having one specific assertion in separate tests.<\/p>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>\/\/Having multiple assertions in the same test\nit('validates and formats first name', () =&gt; {\n   cy.get('&#91;data-testid=\"first-name\"]')\n     .type('johnny')\n     .should('have.attr', 'data-validation', 'required')\n     .and('have.class', 'active')\n     .and('have.value', 'Johnny')\n })\n\n\n\/\/Having multiple tests with having only one specific assertion\nit('has active class', () =&gt; {\n   cy.get('&#91;data-testid=\"first-name\"]').should('have.class', 'active')\n })\n\n\n it('has formatted first name', () =&gt; {\n   cy.get('&#91;data-testid=\"first-name\"]')\n         .should('have.value', 'Johnny')\n })\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Using proper waits aka retry-ability:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cypress inherently retries commands that query the DOM.<\/li>\n\n\n\n<li>It automatically pauses and reattempts most commands, waiting for the targeted element to appear in the DOM.<\/li>\n\n\n\n<li>If an element remains unactionable beyond the time specified in the <a href=\"https:\/\/docs.cypress.io\/guides\/references\/legacy-configuration#Timeouts\" target=\"_blank\" rel=\"noopener\">defaultCommandTimeout <\/a>setting, the command will be deemed a failure.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code has-white-color has-black-background-color has-text-color has-background\"><code>\/\/not required\n   cy.visit('https:\/\/katalon-demo-cura.herokuapp.com')\n   cy.wait(5000)\n   cy.get('#btn-make-appointment').click()\n\n\/\/instead we can use like this\n   cy.visit('https:\/\/katalon-demo-cura.herokuapp.com')\n   cy.get('#btn-make-appointment').should('be.visible')\n   cy.get('#btn-make-appointment').click()\n<\/code><\/pre>\n\n\n\n<p>Note: <strong>Since we are having the \u2018defautCommandTimeout\u2019 in our config, cypress will retry till the configured timing for command and assertion.<\/strong><\/p>\n\n\n\n<p><strong>So if \u2018#btn-make-appointment\u2019 is not available instantly, cypress will retry the assertion until the configured timing, and eventually this will sort out the usage of \u2018cy.wait()\u2019 there.<\/strong><\/p>\n\n\n\n<p>When we are talking how to use \u2018wait()\u2019 method, we should know how the <a href=\"https:\/\/docs.cypress.io\/guides\/core-concepts\/retry-ability\" target=\"_blank\" rel=\"noopener\">retry-ability<\/a> of cypress works and we should know the three concepts:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Commands<\/li>\n\n\n\n<li>Queries<\/li>\n\n\n\n<li>Assertions<\/li>\n<\/ul>\n\n\n\n<p><strong>Just refer the cypress documentation link above to know what are the \u2018Principles and key characteristics of\u2019 commands, queries, assertions and of course what is a \u2018non-query\u2019.<\/strong><\/p>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\">Here is a sequence diagram of retries for command, query and assertion:<\/span><\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"754\" height=\"1164\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2024\/04\/A-sequence-diagram-illustrating-the-retry-logic-for-commands-queries-and-assertions-in-Cypress-testing-framework.png\" alt=\"sequence diagram illustrating the retry logic for commands, queries, and assertions in Cypress \" class=\"wp-image-11364\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Configuring retries, timeout setting configuration:<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>In the cypress runner we can see the settings JSON, we can use these properties as per requirements and AUT.&nbsp;<\/li>\n\n\n\n<li>In the case of ci-cd, we need to have these in our \u2018cypress.config.js\u2019 by default we don&#8217;t have all these properties in our config.js file.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1350\" height=\"744\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2024\/04\/Configuring-retries-timeout-setting-configuration.jpg\" alt=\"Cypress configuration file \" class=\"wp-image-11365\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"1110\" height=\"784\" src=\"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2024\/04\/image1-1.jpg\" alt=\"\" class=\"wp-image-11366\" loading=\"lazy\" title=\"\"><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conditional based testing:<\/h2>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\">Dos and Donts:<\/span><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>End to End testing strategy <strong>should be in a determined manner<\/strong>.&nbsp;<\/li>\n\n\n\n<li>We should create the test scripts and business\/user flows which will return determined results.<\/li>\n\n\n\n<li>So before applying any conditional-based testing scenarios, we should be sure of the state of the elements<br>            \u2610<strong>most of the applications today are highly dynamic and mutable.<\/strong><br>          \u2610<strong>their states and the DOM are continuously changing over a period of time<\/strong> . If the state of the DOM is highly unpredictable, we should not go for a conditional approach, which will result in more <strong>test flakiness.<\/strong><\/li>\n<\/ul>\n\n\n\n<p><em>Cypress has provided dedicated documentation for<\/em><a href=\"https:\/\/docs.cypress.io\/guides\/core-concepts\/conditional-testing#Definition\" target=\"_blank\" rel=\"noopener\"><em> conditional testing.<\/em><\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Version compatibility [cypress core &amp; plugins]<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We all know that all extended functionalities rely on the plugins of Cypress.<\/li>\n\n\n\n<li>In order to maintain a stable project, we must ensure the version we are using for Cypress and its plugins are in sync \/ would support the expected functionalities.<\/li>\n<\/ul>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\">Issues:<\/span><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>As Cypress evolves, some features may be deprecated in favor of new approaches.&nbsp;<\/li>\n\n\n\n<li>Plugins relying on these deprecated features may not function properly without updates.<\/li>\n<\/ul>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\">Best Practice\/ Solution:<\/span><\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Stay updated with the latest versions and change logs on the details of the functionalities added and if some are deprecated like that.<\/li>\n\n\n\n<li>Consider locking down the versions of Cypress and its plugins to ensure stability<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>Conclusion:<\/strong><\/p>\n\n\n\n<p>By incorporating these considerations into the creation and maintenance of our Cypress e2e testing framework, we can achieve greater robustness, eliminate flakiness, and facilitate seamless CI\/CD integration.<\/p>\n\n\n\n<p>Also, it is an iterative process, we should inspect our approach and adapt it according to the project requirements and priorities.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the current software testing landscape, testing complexity is on the rise. However, modern frameworks like Cypress simplify executing component and end to end testing. Here are some essential considerations for developing an e2e UI testing framework with Cypress. Defining AUT scopes: If the test involves some other origin, then we need to use the [&hellip;]<\/p>\n","protected":false},"author":34,"featured_media":11496,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[209],"tags":[],"class_list":["post-11280","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-test-automation"],"acf":[],"images":{"medium":"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2024\/04\/10-Best-Practices-to-Improve-Your-cypress-testing.jpg","large":"https:\/\/testgrid.io\/blog\/wp-content\/uploads\/2024\/04\/10-Best-Practices-to-Improve-Your-cypress-testing.jpg"},"_links":{"self":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/11280","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/users\/34"}],"replies":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/comments?post=11280"}],"version-history":[{"count":17,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/11280\/revisions"}],"predecessor-version":[{"id":12104,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/posts\/11280\/revisions\/12104"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/media\/11496"}],"wp:attachment":[{"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/media?parent=11280"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/categories?post=11280"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/testgrid.io\/blog\/wp-json\/wp\/v2\/tags?post=11280"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}