<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cypress on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/cypress/</link><description>Recent content in Cypress on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 12 May 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/cypress/index.xml" rel="self" type="application/rss+xml"/><item><title>End to end testing - Cypress basics</title><link>https://blog.iankulin.com/end-to-end-testing-cypress-basics/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/end-to-end-testing-cypress-basics/</guid><description>&lt;p&gt;When you&amp;rsquo;ve made a change to your web-app, do you run it then click around the new bits to check it works? Good start, but instead of doing that yourself, do it in a faster, more comprehensive and automated way with an end-to-end (E2E) testing setup using &lt;a href="https://www.cypress.io/"&gt;Cypress&lt;/a&gt;. Here&amp;rsquo;s how.&lt;/p&gt;
&lt;h3 id="e2e"&gt;E2E&lt;/h3&gt;
&lt;p&gt;End to End testing is testing your app as a user might - by clicking links, entering data, looking at the screen and checking everything is okay, but it&amp;rsquo;s scripted like a unit test and the results are checked with assertions. Like unit testing this allows you to build up a collection of comprehensive tests that easily detect for unexpected behaviours - not just in the results of functions in your app, but in the user experience of the app.&lt;/p&gt;
&lt;p&gt;In the case of Cypress, this works by running your app in an instrumented browser. The tests are written in JavaScript and might ask things like &amp;ldquo;Click the &amp;lsquo;Home&amp;rsquo; link&amp;rdquo; and have an assertion similar to &amp;ldquo;check the home page loaded&amp;rdquo;. Let&amp;rsquo;s see how that will look.&lt;/p&gt;
&lt;h3 id="how-it-looks"&gt;How it looks&lt;/h3&gt;
&lt;p&gt;In the app I&amp;rsquo;m working on, if you view an individual customer (say at &amp;ldquo;&lt;a href="http://127.0.0.1:3002/customers/1"&gt;http://127.0.0.1:3002/customers/1&lt;/a&gt;&amp;rdquo;) there&amp;rsquo;s a &amp;ldquo;Home&amp;rdquo; link at the top which takes you to the list of customers (at &amp;ldquo;&lt;a href="http://127.0.0.1:3002/customers"&gt;http://127.0.0.1:3002/customers&lt;/a&gt;&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-10.22.34.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the test code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe(&amp;#39;Page Navigation&amp;#39;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#39;should navigate to the customers list when clicking the Home link&amp;#39;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // visit the customer details page
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#39;http://127.0.0.1:3002/customers/1&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // find and click the Home link
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#39;a&amp;#39;).contains(&amp;#39;Home&amp;#39;).click();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // verify we navigated to the customers list page
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#39;eq&amp;#39;, &amp;#39;http://127.0.0.1:3002/customers&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;ve been writing unit tests before, this format will be familiar, but let&amp;rsquo;s look at the steps:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.visit(&amp;#39;http://127.0.0.1:3002/customers/1&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You guessed it - we&amp;rsquo;re telling Cypress to visit that page.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;a&amp;#39;).contains(&amp;#39;Home&amp;#39;).click();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m not sure if Cypress uses &lt;a href="https://jquery.com/"&gt;JQuery&lt;/a&gt;, or just a JQuery like syntax, either way, what we&amp;rsquo;re doing here is selecting the &amp;lsquo;&amp;lt;a &amp;hellip;&amp;gt;&amp;rsquo; tag. Of course our page probably contains several anchor tags, so we&amp;rsquo;re refining this search to the anchor tag that contains &amp;lsquo;Home&amp;rsquo;. Note that there&amp;rsquo;s an implied assertion here. If there is no &lt;a&gt; link on the page containing &amp;lsquo;Home&amp;rsquo;, this test will fail with an error saying something like &amp;ldquo;Expected to find content: &amp;lsquo;Home&amp;rsquo; within the element: &lt;a&gt; but never did.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Finally the &lt;code&gt;click()&lt;/code&gt; at the end of the statement tells Cypress to click this link.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.url().should(&amp;#39;eq&amp;#39;, &amp;#39;http://127.0.0.1:3002/customers&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we look at this statement, consider that we haven&amp;rsquo;t told Cypress to wait for a bit for the results of our click() to process - one of the benefits of Cypress is it just figures that out magically.&lt;/p&gt;
&lt;p&gt;This statement is an assertion - the URL &lt;code&gt;should&lt;/code&gt; equal (&lt;code&gt;eq&lt;/code&gt;) the URL we&amp;rsquo;ve provided.&lt;/p&gt;
&lt;p&gt;So that gives us a quick overview of a simple test. Naturally Cypress has a heap more operators and assertion types to help us test our application - basically everything you could think of as user-facing testing. Let&amp;rsquo;s look at a simple demo app then work through the tests we might try for this.&lt;/p&gt;
&lt;h3 id="the-app"&gt;The App&lt;/h3&gt;
&lt;p&gt;This app is a simple demo I wrote for an earlier blog post about using the Express router. We have &lt;em&gt;Customers&lt;/em&gt; and &lt;em&gt;Orders&lt;/em&gt;, a single &lt;em&gt;customer&lt;/em&gt; can have zero-many &lt;em&gt;orders&lt;/em&gt;. The opening page is a list of all customers. Clicking on a customer shows the details for that customer, including a list of their orders. Clicking on an order shows the detail for that order, including a link the customer it belongs to.&lt;/p&gt;
&lt;p&gt;The Customer and Order detail views have delete links, and a deletion of a customer should cascade to delete that customer&amp;rsquo;s orders.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-11.50.32-1.png" width="946" alt=""&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-11.50.22-1.png" width="999" alt=""&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-10.22.34-2.png" alt=""&gt;&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-12-at-13.12.28-1.png" width="861" alt=""&gt;
&lt;h3 id="installing-cypress"&gt;Installing Cypress&lt;/h3&gt;
&lt;p&gt;Installing Cypress is straightforward. The install steps from the docs are &lt;a href="https://docs.cypress.io/app/get-started/install-cypress"&gt;here&lt;/a&gt;, but really it&amp;rsquo;s just starting your Node project (so you&amp;rsquo;ve got a package.json) then &lt;code&gt;npm install cypress --save-dev&lt;/code&gt; to add it as a dev dependency. It&amp;rsquo;s a big download so expect it to take a bit. It includes lodash, some AWS stuff, tldts, day.js, a heap of vue stuff - just, it&amp;rsquo;s a lot of big dependencies. Also since Cypress itself does some cool stuff linking into the browser - that functionality requires some code.&lt;/p&gt;
&lt;h3 id="the-tests"&gt;The Tests&lt;/h3&gt;
&lt;p&gt;Actually - the code in our very simple demo above covers about 70% of the testing I do, and the pattern of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select a user element&lt;/li&gt;
&lt;li&gt;Click it or add some text&lt;/li&gt;
&lt;li&gt;Select another user element to check that worked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;comes up again and again. So I&amp;rsquo;m going to try not to repeat myself too much. Most of what&amp;rsquo;s new in the following tests will be extra selectors, and assertions. We won&amp;rsquo;t cover all of them, but rather a smattering to get started with.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // test for customers list page
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; describe(&amp;#34;Customers Page&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;should have the home page redirect to customers page&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#34;http://localhost:3002&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#34;include&amp;#34;, &amp;#34;/customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;h1&amp;#34;).contains(&amp;#34;Customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;should display a list of customers&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#34;http://localhost:3002/customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;li&amp;#34;).should(&amp;#34;have.length.at.least&amp;#34;, 5);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;li&amp;#34;).eq(0).contains(&amp;#34;Alice&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;should have working links to customer details&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#34;http://localhost:3002/customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // click the first customer (Alice)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;a&amp;#34;).contains(&amp;#34;Alice Johnson&amp;#34;).click();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#34;include&amp;#34;, &amp;#34;/customers/&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;h2&amp;#34;).contains(&amp;#34;Alice Johnson&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="should"&gt;.should()&lt;/h4&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;a href="https://docs.cypress.io/api/commands/should"&gt;massive list of should() assertions&lt;/a&gt;, and they depend a bit on what you&amp;rsquo;ve chained on to. In the first example we looked at we used &lt;code&gt;&amp;quot;eq&amp;quot;&lt;/code&gt; for equals, in the example directly above we&amp;rsquo;ve used &lt;code&gt;&amp;quot;include&amp;quot;&lt;/code&gt; for a partial match, and &lt;code&gt;&amp;quot;have.length.at.least&amp;quot;&lt;/code&gt; for what it says on the box.&lt;/p&gt;
&lt;p&gt;Another handy thing might be testing for &lt;code&gt;&amp;quot;not.exist&amp;quot;.&lt;/code&gt; In my example app if I want to test deleting a &lt;em&gt;customer&lt;/em&gt;, I can check they exist in the customers list, click delete, then check that they no longer exist in the list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#34;should delete a customer when delete link is clicked&amp;#34;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // first check the customer exists
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#34;http://localhost:3002/customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;a&amp;#34;).contains(&amp;#34;Hannah Abbott&amp;#34;).should(&amp;#34;exist&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // visit the customer page and delete
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#34;http://localhost:3002/customers/8&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;a&amp;#34;).contains(&amp;#34;Delete customer&amp;#34;).click();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // verify the customer is deleted
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#34;include&amp;#34;, &amp;#34;/customers&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#34;a&amp;#34;).contains(&amp;#34;Hannah Abbott&amp;#34;).should(&amp;#34;not.exist&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="get"&gt;.get()&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ve already seen selecting an anchor tag with get(&amp;ldquo;a&amp;rdquo;) - this will work for any HTML tag, but of course you&amp;rsquo;ll frequently need more specificity than that. As &lt;a href="https://docs.cypress.io/api/commands/get#__docusaurus_skipToContent_fallback"&gt;described in the docs&lt;/a&gt;, most of the JQuery selectors will also work with get.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Select by element type
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;button&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Select by class
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;.my-class&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Select by ID
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;#my-id&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Combining selectors
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;button.primary#submit&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// Select by attribute
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(&amp;#39;[data-test=&amp;#34;submit-button&amp;#34;]&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Those first four are straightforward, but you might not know about attributes.&lt;/p&gt;
&lt;h3 id="attributes"&gt;Attributes&lt;/h3&gt;
&lt;p&gt;As part of the HTML specification, tags can have attributes. You&amp;rsquo;ve been using them all along. For example. this button:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;button id=&amp;#34;submit&amp;#34; class=&amp;#34;btn primary&amp;#34; type=&amp;#34;submit&amp;#34;&amp;gt;Submit&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;has attributes for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;class&lt;/li&gt;
&lt;li&gt;type&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These all have particular meanings for HTML, CSS and JavaScript, but actually we can make up our own. For example we could say:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;button type=&amp;#34;submit&amp;#34; data-test=&amp;#34;submit-button&amp;#34;&amp;gt;Submit&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s no specification for &amp;lsquo;data-test&amp;rsquo;, it&amp;rsquo;s just a convention, we could just have easily said:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;button type=&amp;#34;submit&amp;#34; data-green-zebra=&amp;#34;submit-button&amp;#34;&amp;gt;Submit&amp;lt;/button&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I&amp;rsquo;ve kept the &lt;code&gt;data-&lt;/code&gt; prefix - that is part of the &lt;a href="https://www.w3schools.com/tags/att_global_data.asp"&gt;HTML5 specification&lt;/a&gt;. We could probably make up anything and it would work, but maybe it would conflict with something in a future HTML version, so best stick to &amp;ldquo;data-&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Using attributes for specifying the element we want is highly recommended. Although the element you want to click might currently be the third &lt;a&gt; in a &lt;ul&gt; inside the &lt;nav&gt; - it&amp;rsquo;s easy to imagine it being moved in a future update to your app. Once we&amp;rsquo;ve written an E2E test, we want it to continue to work across future app development, and using a &amp;lsquo;data-test&amp;rsquo; attribute supports this.&lt;/p&gt;
&lt;h3 id="invoke-and-then"&gt;.invoke() and then()&lt;/h3&gt;
&lt;p&gt;Sometimes the exact test you need might not be available, or you need to do some operation as part of your testing that requires a bit more processing. In that case, you can chain the &lt;code&gt;invoke()&lt;/code&gt; method. This allows you to call any jQuery method on an element you&amp;rsquo;ve selected with Cypress, letting you extract specific properties or manipulate the element in ways that aren&amp;rsquo;t covered by Cypress&amp;rsquo;s built-in assertions.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve got it, you can use &lt;code&gt;then()&lt;/code&gt; to run an arrow function against it to do something. The pseudo codes looks a bit like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy.get(selector)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .invoke(jQueryMethod) // Extract what you need
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .then((result) =&amp;gt; { // Process it with your own logic
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // Custom processing
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s look at an example. Imagine the HTML of our page looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;span class=&amp;#34;price&amp;#34;&amp;gt;$24.99&amp;lt;/span&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And we want to check that the price was greater than $20 - perhaps we are supposed to have added tax or something. Our test could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;.price&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;invoke&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;text&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;then&lt;span style="color:#eceff4"&gt;((&lt;/span&gt;priceText&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; priceValue &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; parseFloat&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;priceText&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;replace&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;$&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expect&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;priceValue&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;to&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;be&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;greaterThan&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;20.0&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This selects this span based on it&amp;rsquo;s class, then saves the text &amp;lsquo;$24.99&amp;rsquo; to &lt;code&gt;priceText&lt;/code&gt;, extracts the value to a JavaScript number, and asserts it to be greater than 20.&lt;/p&gt;
&lt;p&gt;In my demo app, I use this to check the cascading delete - when we delete the customer, the orders for that customer should also be deleted. Rather than hard code the order number we can use invoke/then to extract it from the text which looks like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;8 - 2025-03-08 - $200&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then, after deleting the customer (and orders) we navigate to the orders page to make sure that order number does not exist there any more.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; describe&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Cascading Deletions&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;should delete owned orders when a customer is deleted&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; first make a note of an order &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; a specific customer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;visit&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;http://localhost:3002/customers/4&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;h2&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;contains&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Diana Prince&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; note an order ID that belongs to this customer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;ul li a&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;first&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;invoke&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;then&lt;span style="color:#eceff4"&gt;((&lt;/span&gt;orderText&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; extract the full order text to use &lt;span style="color:#81a1c1;font-weight:bold"&gt;for&lt;/span&gt; matching later
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; orderTextFull &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; orderText&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trim&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; extract just the order ID number
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; orderId &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; orderText&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;split&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34; &amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)[&lt;/span&gt;&lt;span style="color:#b48ead"&gt;0&lt;/span&gt;&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;trim&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; delete the customer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;contains&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Delete customer&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;click&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; verify customer is deleted
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;url&lt;span style="color:#eceff4"&gt;()&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;should&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;include&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;/customers&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;a&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;contains&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;Diana Prince&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;should&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;not.exist&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; check that the order is also deleted 
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;visit&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;http://localhost:3002/orders&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; make sure we&lt;span style="color:#a3be8c"&gt;&amp;#39;re matching the exact order (not just a substring)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;a&lt;span style="color:#eceff4"&gt;[&lt;/span&gt;href&lt;span style="color:#81a1c1"&gt;=&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;/orders/${orderId}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;]&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;should&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;not.exist&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is a lot, lot more to Cypress to this, but with what we&amp;rsquo;ve covered here it&amp;rsquo;s possible to write a comprehensive suite of tests that will test all of the functionality in this demo app in &lt;a href="https://github.com/IanKulin/route-demo/blob/main/cypress/e2e/home.cy.js"&gt;about 200 lines&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="using-cypress"&gt;Using Cypress&lt;/h3&gt;
&lt;p&gt;So that&amp;rsquo;s all the code, but how does it look to test like this? For me this is one of the things that makes end-to-end testing cool. I love seeing it&amp;rsquo;s click away in the browser at super speed as my tests turn green.&lt;/p&gt;
&lt;p&gt;First of all I start my app - so however you normally do this. For me, it&amp;rsquo;s dropping to the terminal in VSCode and running it in Node. Something like &lt;code&gt;node index.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s going we can start Cypress. Since the first terminal is running my node app, we need to spawn another terminal to run Cypress in. This is a simple matter in VSCode - just hit that + button I&amp;rsquo;ve circled in the screen shot below. You can swap between the different terminals you have open by clicking on them in the list underneath that + button.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-16.02.23.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;We start Cypress in the new terminal with &lt;code&gt;npx cypress open&lt;/code&gt; but the magic does not happen in the terminal, this thing pops up:&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-16.09.06.png" width="900" alt=""&gt;
&lt;p&gt;We&amp;rsquo;re doing E2E testing, so select that.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-16.09.16.png" width="900" alt=""&gt;
&lt;p&gt;I&amp;rsquo;m in a Chrome mood today, so next we see this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-16.09.29.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve only got one test file - &lt;code&gt;home.cy.js&lt;/code&gt;, so I click that. The tests are listed down the left side of the browser, and my app in an iFrame to the right. As the tests are running, I can see the app flicking through each step. In a couple of seconds the seventeen tests that comprise many page manipulations and assertions are finished and I can see the results.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-17.14.27.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;If we click on a test, the details for it open up, and a screenshot of the application state at the time of that test is displayed.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-17.16.52.png" width="900" alt=""&gt;
&lt;p&gt;The most common debugging problem I&amp;rsquo;ve run into is when I didn&amp;rsquo;t write the selection correctly (and didn&amp;rsquo;t use a data- attribute). These are easily checked in this view by hovering over the one we&amp;rsquo;re interested in - the element that Cypress used in this step will be highlighted.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-17.20.34.png" width="900" alt=""&gt;
&lt;p&gt;So, how does a failed test look. I can create that in this test suit just by running the tests again. It won&amp;rsquo;t be able to delete orders or customers it deleted in the earlier run.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-15-at-17.24.56.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;So, at the start of this &amp;lsquo;delete order&amp;rsquo; test, I&amp;rsquo;m checking if the order exists, and it doesn&amp;rsquo;t (because we didn&amp;rsquo;t reset after deleting it last time). We can see from the error message that Cypress waited 4 seconds in case it was a timing issue. It&amp;rsquo;s displayed the test case where the failure has occurred. This along with the before and after snapshots of the app around each test make locating problems a breeze.&lt;/p&gt;
&lt;h3 id="resets"&gt;Resets&lt;/h3&gt;
&lt;p&gt;The pattern above (where you run a test twice and it fails the second time because the first execution changed the state) is common. To avoid this, we need some system of resetting the state. Cypress has a mocha like &amp;lsquo;beforeEach&amp;rsquo; ability. You most always need this for logging things in:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;describe(&amp;#39;My app tests&amp;#39;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; beforeEach(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; // This code runs before each test in this block
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.visit(&amp;#39;/login&amp;#39;); // for example
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#39;input[name=username]&amp;#39;).type(&amp;#39;user&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#39;input[name=password]&amp;#39;).type(&amp;#39;password&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#39;button[type=submit]&amp;#39;).click();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#39;should show the dashboard after login&amp;#39;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#39;include&amp;#39;, &amp;#39;/dashboard&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.contains(&amp;#39;Welcome&amp;#39;).should(&amp;#39;be.visible&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; it(&amp;#39;should navigate to settings&amp;#39;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.get(&amp;#39;nav&amp;#39;).contains(&amp;#39;Settings&amp;#39;).click();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cy.url().should(&amp;#39;include&amp;#39;, &amp;#39;/settings&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But for apps that need things like a fresh database before testing, it&amp;rsquo;s a bit trickier. In the past I&amp;rsquo;ve sometimes created some sort of /test-reset endpoint which feels like an unreasonable security risk. The proper answer is to shell out with a Cypress task. That way we can do things like copy in test data, or spin up a whole test environment in a container. These are meaty topics for another post - but really, our tests should be re-run-able, and run-able in any order so we might come back to that.&lt;/p&gt;
&lt;h3 id="test"&gt;Test&lt;/h3&gt;
&lt;p&gt;Any testing is better than none, and if you use these sorts of tools that make it easier you&amp;rsquo;ll find you&amp;rsquo;ll add to them, especially when errors crop up. If I sit down to add end-to-end tests to an existing app, I nearly always find things I want to change to make it better. Use end to end testing.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/route-demo"&gt;code&lt;/a&gt;&lt;/p&gt;</description></item></channel></rss>