In this workshop, I will introduce you to the migrator.cypress.io project in an unconventional way, where in addition to going over the Cypress commands equivalent to Protractor (and showing you how Cypress is simpler), I will also introduce you how I tested such a project evolving the automated test scripts on-demand, architecting the test suite in an evolutionary way.
Web Testing Architecture and Refactoring With Cypress
AI Generated Video Summary
The Workshop on Migrator.cyprus.io covered the migration of Protractor test scripts to Cypress using the Migrator.cyprus.io application. The workshop included running tests, discussing project architecture, and code improvements. Custom commands were introduced to reduce code duplication. The workshop also covered guidelines for writing code, version updates, and optimization techniques. Participants shared their experiences and recommendations for using Cypress for UI and API testing.
1. Introduction to Migrator.cyprus.io
Welcome to the workshop. My name is Valmir. I will introduce you to Migrator.cyprus.io, an application created by the Cyprus team for educational purposes. It allows you to understand how a test script created with Protractor would look like if you migrated to Cyprus. In this workshop, I will present how I tested this application using Cyprus itself. I will show you how to start a suite of tasks and evolve it on demand, adding and removing complexity as needed. There are 19 participants in the workshop.
Welcome to the workshop. My name is Valmir. I am a Cyprus ambassador. Can you see my... Besides my screen, can you see myself as well? Because now that he is sharing my screen, I can't see myself anymore. So, please let me know if you can see me.
So I was explaining before that in this workshop, I will introduce you to Migrator.cyprus.io, which is this application here. And basically, Migrator.cyprus.io is an application created by the Cyprus team as with educational purposes. And the idea is that it allows you to understand how a test script that was created with Protractor would look like if you migrated to Cyprus. So you can check it out at Migrator.cyprus.io. There is an example when you start the application for the first time when you click migrate, you see the equivalent code in Cyprus here. So in here you have Protractor syntax. And in here you have the exact same thing in the Cyprus site. Right?
And what I was saying is that for this workshop, I will present you how I tested this application using Cyprus itself. And in the way I will present it to you, it's going to be in an unconventional way. It's going to be maybe in a way that you might not be expecting. I hope you like the way I'm going to present it. I have already done this workshop in Portuguese, I'm originally from Brazil, so I speak Portuguese, and I have a YouTube channel. And in there I have done this same workshop in the way that I am going to do for you here today. And people pretty much liked it, so I hope you like it too. And what I'm going to show you is how you start a suite of tasks and you evolve it on demand. You start adding complexity just when it's needed, when the application is communicating to you that it needs more complexity, and also when it needs you to remove the complexity. So there's someone else joining. Let me admit this new person. So we have, at the moment, 19 people, so welcome, you all.
2. Introduction to Cypress Migrator Project
The project is available on GitHub. You can clone it, fork it, and run it locally. There are prerequisites and installation instructions provided. The project includes a test suite written in Protractor syntax, which can be migrated to Cypress. The Cypress commands are shorter and more concise. The project uses Cypress 11.0.1 and a custom command called 'press'. The cypress.config.js file is configured with the base URL. There are separate directories for support, end-to-end tests, and fixtures. The workshop is being recorded, and access will be provided for latecomers. Questions can be asked during the workshop.
The project itself is available on GitHub, on my profile, so I'm going to share it with you both here in the chat on Zoom and also on Discord in case you are there. So you can come to this project on GitHub if you want. If you want, you can leave a start. You can clone the project on your own computer if you wanted. If you want, you can also fork it in case you want to contribute back with anything else that might come to your mind. In this project, I have created some documentation, like very simple documentation for you to get started. So in case, you come here to GitHub.com slash WLSF82 slash cypress dash monitor dash task. You'll be able to clone it. After cloning it, I left some prerequirements here. So these are the versions of node and MPM that I used when I created this project. So if you want to use it, I recommend that you use these or later LTS versions. After you cloned it, you can change the director to go inside the project that you just cloned, and then you can run MPM install. After you install the dependencies, you can then run MPM test to run the test in headless mode, which is the mode where you don't see the browser being opened. But if you want, you can also run the tests with MPM run side, oh here, and PM run side column open to open the Cypress app where you'd be able to see the tests running in Cypress. in what is called interactive mode. The project itself is available on GitHub. So here is the application that we are going to test and here's the project of these applications. So if you want to come here, leave a status, clone the project for whatever you want. So this project is open source, the Cypress migrator, you can clone it and you can set up it locally if you want to have the project run locally instead of using the version that is running on the Internet, okay? And in case you want to run the same test against your local environment, I have created an npm script as well for you, which is npm run test local, or you can open the Cypress app to run them in interactive mode against your local environment, as well with npm run site column, open column local. After you run the tasks in headless mode, this is the summary of the results in my computer here. At the time I created this documentation, it took 51 seconds to run 31 tasks, which is what we have. If you want to know more about me, there is the link to my website here. And in my website, you'll find a little bit about myself. It's kind of a timeline where you can find links to my courses on Udemy. I have courses on Udemy about Cypress and about other technologies I have both in English and in Portuguese as well. You can find my profile on GitHub, my YouTube channel, most of the content on my YouTube channel are in Portuguese since I am originally from Brazil, and Portuguese is my mother language. But there is one playlist called English Content where I interview other Cypress ambassadors and people from the Cypress team. I even interviewed Gleb Bahmutov, who was the ex-VP of Engineering at Cypress. You can also find my Twitter profile on LinkedIn, Medium, and Def Community. Feel free to, like, from this project here, which I shared both on Discord and also here in the chat. You have access to the Cypress Migrator app. The link is here in the readme. To the project on GitHub, as well, and to my website in the footer here. All right? To get started, what I want to show you is, like, what does it do, right? So, for instance, this Cypress Migrator project here, it basically allows you to... And let me restart. The focus of today's workshop is not going to be on migrating commands from Protractor to Cypress. It's going to be on what I call evolutionary architecture for your task suite. Okay? And the migrator is just the application that will be tested. But it's important to understand what the application does, so I'm going to give you just a sneak peek of that. So, when you access it for the first time, as I showed you before, it has a task suite here written in Protractor syntax. So, it describes Cypress docs, then it has a test case. It should show the correct title, correct site title, and redirect URL. Then, there's the command that visits the page with browser.driver.get, passes the URL as an argument of this get command, then runs the assertions, expect browser.get title to equal ycypress, pipe Cypress documentation, and also expect browser.get current URL to equal to the URL docs.cypress.io slash guides slash overview slash ycypress. When you click migrate, you'll see the equivalent here in Cypress where you see that instead of browser.driver.get, we have a cy.visit. Instead of expect browser.get title to equal, we have cy.title should equal and the text that we want it to be equal. So here, should, in this case, is receiving two arguments, equal as the first one and the second one what we want it to be equal. And then instead of expect browser.get URL, a current URL to equal to the URL, we have cy.location. Get the href, and it should equal to this URL here. All right? So we can, for instance, type something else here, in Protractor. We would have something like element, and then we could pass, for instance, a tag A. If I migrate this to Cypress, it would be simply a cy.get or an anchor tag, right? So one thing that you will notice that I find nice is that Cypress syntax is much shorter than Protractor syntax. So you notice that in most of the cases, the command that you have in the right is shorter than the command in the left, meaning that the Cypress commands are shorter than the Protractor commands. If you have arrived late for the workshop, don't worry, the workshop is being recorded and you will have access to the recording afterwards. So this is the application that we're going to test, right? And, actually, I have already created the whole test suite in this URL that I have already shared with you, both on Zoom and also on Discord. And so we'll see here that it has in the package.json scripts to run the tests in interactive mode and interactive mode locally, where I am overwriting the base URL to the base URL of the local environment in case you're running locally. We will be running against a version on internet, and we have also scripts to run the tests headless, both against the production environment and also the local host environment. And finally, this project in the final version, which is the version that I'm showing you at the moment, it's using Cypress 11.0.1, which was recently launched, and it's using also this lib that I have created, which is called Cy-press, which adds one silly custom command, which is the press command. So you can change, for instance, you can do Cy.get, get an input of type Text, type my name, for instance, Valmir,.press, Enter, for instance, this is like it adds this.press command, basically. Other than that, we have the cypress.config.js file, where the only thing I'm configuring here is the base URL to https://migrator.cypress.io, this application here. This is why when I'm running locally, I overwrite the configuration. All right? What else? On the Cypress director, we have three different directors. We have support, where in here and basically importing my library that is defined in the package.json. That when I install the dependencies with npm install, it sends you the node modules directory. For some reason, there were uncouched exceptions in this application, so I have to add this line here as well. And then I'm not going to look into the end-to-end and fixtures for now, because you'll see it in a moment. But basically, here we have one test, migrator.cy.js, and in the fixtures we have test scenarios.js. But I said to you that I'm going to present this project to you in an unconventional way. Let me see, it seems that there's more people waiting to join, so let me unmute them as well. So for you that has arrived late, don't worry, the workshop is being recorded and you have access to it afterwards, so you can watch what you have missed because we have already started for a few minutes ago. And if you have any questions, you don't need to wait until the end of the workshop.
3. Running Tests and Migration Process
In this part, I will run the tests on the Cypress app and show you the migration process. The tests access the migrator.cypress.io page, where Protractor commands are typed on the left side and the migration results are checked on the right side. The tests also perform water checks. After the tests finish, we can use the time travel functionality of Cypress to see the application's state at different points. The tests cover various migrations, such as submit commands and send keys. Cypress provides a productive and easy-to-read API for testing in the browser.
You can ask questions beforehand. I'll be, while showing you things here, I'm going to be looking both through the chat here on Zoom and also on Discord, in case you prefer to use Discord instead of Zoom for chatting. So I'll be in both here. I just put them both in my other screen now so I can watch to both of them at the same time and I don't need to be switching desktops.
Let me see. There's someone else waiting, so let me admit. For you that joined right now, the workshop is being recorded and you have access to the recording afterwards.
So, okay, I was saying I'm going to present you this project where I tested Demigrator in an unconventional way. And what I mean is that I'm going to present you the evolution of these tests. But actually, let's run the tests first. I think it's going to be nice if you see what's going on before you start looking into the code.
So I have Cypress, the app, open here in my screen, and I'm going to run the tests on Electrum, just because then I don't get confused with another Chrome open. I'm going to click in the migrator.cypress.cy.js, which will basically run the tests in what is called interactive mode. So what the tests do is they access the page, migrator.cypress.io, where we have in the left side, where we can type the Protractor commands, and then I click the Migrate button, and I check that the correct thing was migrated in the right side. And I also do some water checks that you'll see in a while.
So we have 31 tests. So we are almost there. I think it's important that you see the tests running before we go and look into the code. So as soon as it finishes, we can use time travel functionality of Cypress. By the way, if you are not very familiar with Cypress, Cypress is a test framework that allows you to test anything that runs in the browser. And it gives you a very good development experience. It allows you to write tests in a very productive way, and it gives you all the tools that you need to be productive and to debug your tests when they are failing. Different than other tools like Selenium-based, for instance, where you would run your tests. And even if you are running them in headed mode, where you can see the browser, right. When the tests finish, what happens usually with this old tools is that the browser closes and you don't have any more evidence other than a stack trace on your command line. So one of the nice things on Cypress is that, after the tests are finished, the browser is kept open and you can see how the application is when the test ended. But not only that as you can also navigate back to each of the tasks.
So for instance in this one here, we are migrating Protractor's submit command into Cypress submit command. And then we can use the time travel functionality to see how was the application when I executed the visit command to slash, which is basically the homepage. And this is how the application was rendered at that time. Then we can see that I got some text areas, two of them were returned and I gave to them an alias called text areas. Then I got the first one and I pressed select all. Then we can see the before and after where we can see here that it selected the content. Then I typed clear and then it was all clear. So I start in a fresh state with everything clear and then I can finally get the left side. And then I give to this first text area an alias called left side editor. And then I get the left side editor and I type something. Here you can see the before and after. So this is how it look before and after I typed. I typed elements by CSS form dot submit. And then I find the button that contains the text migrate to Cypress. I click on it before and after, and then afterwards, after I clicked, I can see here that it transformed this command here into a Cypress command. And I can also see that it pointed me here to the commands that are being used here. So in here I'm using site of gap and dot submit. So it shows me links to the official Cypress documentation, both for the Cypress.gap and for the dot submit command. And these are things that we are testing. As you can see, when I clicked the button, well now it kind of got lost, but when I clicked the button, the viewport was in the button itself, so I had to scroll back, I had to go to the text areas, get the last one, which is now the one on the right side, scroll it into the view to run my assertions that it expected to see a site.getform.submit and to, no, it expected this text here, site.getform.submit to include site.getform.submit. Basically I'm asserting that it's showing what it should be showing. And then afterwards I come to this section here where we have the Cypress commands that were used and I iterate through them. First I just check that it contains site.get. Then I find the first, I find the anchors, it returned two elements. I find the one with the index zero and I check that it has the correct href attribute which is on.cypress.io.get pointing to the correct documentation. Then I do the same for the second one where I get them all. I check that there is the submit here. I find both a's. Now I go to the one with the index of one which is the site.submit and I run another expectation that it has the correct URL, the correct href value for that specific anchor tag. In all the tests they do exactly the same thing. They visit the page. So for instance, in this here, we are seeing that we can migrate from dotsubmit to dotsubmit. In this case it's just the same, but instead of element by CSS, we get site.get. In other ones, for instance, here, we are migrating the send keys into dot type. So if we go here for instance, let's see this one here, we can see that I typed element by CSS, my selector.sendkeys, ABC, and what I have in the right side of getSelector.type, ABC, which I find dot type a lot more clear than send keys. This is something that I find really nice on Cypress. Like, its API is very easy to read and easy to follow.
Okay. So this is what the test does. All the tests, they visit the page, clean the text area in the left side, type a protractor command, click the migrate, and check that the migrated snippet in the right is correct, and that here below we have both the commands that were used here in the right side, so that's Cy.get and.type. So here we have Cy.get and Cy.type. And we not only check that these values are here, but also that they are pointing to the correct URL in the documentation. So if we go, for instance, here we migrated element.byCSS to Cy.get.
4. Introduction to Project Architecture
We'll see here below that we have Cy.get. And if we click on it, we are directed to the.get command in this official Cypress documentation. For this application, we have 42 commits, and I'm going to start with some of the first commits. The initial commit was when I created a project on GitHub and it basically created the license file. I will present the architecture for this task in an unconventional way by showing you the history of commits while I was creating the project.
We'll see here below that we have Cy.get. And if we click on it, we are directed to the.get command in this official Cypress documentation. Okay. Now to the fun part, hopefully. So for this application, we have at the moment 42 commits, and I'm going to start with some of the first commits. So the initial commit was when I created a project on GitHub and it basically created for me the license file. So that's basically it. When I said that I would present you the architecture for this task in an unconventional way, it means that I'm going to show you through the history of commits while I was creating the project.
5. Creating Cypress Project and Writing First Test
I created a Cypress project with various commits. The first commit created the necessary files and installed Cypress. I configured the project, specifying that I would not use fixtures initially. I created a test suite called Cypress migrator and wrote the first test, which migrates Protractor's browser.get command. I cleared the text areas and typed in my personal website. Then I used cy.contains to find and click the 'migrate to Cypress' button.
Again, if you have any questions during the workshop, feel free to shoot them both on Discord or here in the Zoom chat. And let me see here. So then I have a commit that creates a Cypress project where I'm basically creating the Gitignore file, the README file, a Cypress example file, cypress.env.example file, the package log and the package.json where I have already installed Cypress, where in that moment, I was using version 10.4. I actually migrated to 11 today, I think, and I've been upgrading to the latest versions as they come.
Okay, so this is a not very important commit. And for the important commits, I put in the commit message a V and a number. So V1, V2, V3, which is like the versions, let's say, of this test scripts while I was creating them. And the first one is, the V1, is lots of duplications. So this is the name of the commit. I have created some tests with lots of duplicate duplications, and here I have then configured Cypress. When you install Cypress and you open it for the first time, it will bootstrap itself. It will create the Cypress.config file. It will create the Cypress directory with the ensudev. You can even tell it to create some tests for you. It will create the support directory with the ensuev.js file. And then I started configuring my project, where I told Cypress that, in the first place, I would not use fixtures. You saw that in the final version, I am using fixtures, but when I started, I said, you know what, I don't think I'm going to use fixtures, so I'm going to configure Cypress saying fixture folder false. I'm not going to use it, for the moment. And then I created this migrator.cy.js file, where, in here, I have created a test suite called Cypress migrator, so when you create a test suite, you call the describe function, which receives two arguments. The first one is the title of your test suite, and the second argument is a callback function. Inside the callback function is where you, inside the body of the callback function is where you add your tests, and your tests follow the same syntax, but instead of describe, they are if blocks. The first argument of your if block is the title of your test, and the second argument is a callback function. And inside the body of this callback function is where you have the details, the implementation details for that specific test. So the first test is, it migrates Protractor's browser dot get. So I run inside of visit to my greater dot cypress dot io. Then I do a site up yet to the text areas that are inside an element that has both the classes side by side and dot vs. So let me actually do one thing here. So you can understand what's going on. This is a text area, and this as well. But this one in the line in the right is read only, and this one is not. Because this is where we type things to migrate to the other side. Let me see, it seems that there's someone else. Let's unmute this one person. So if I inspect, what's going on? Let's inspect. And let's inspect this one here. We have editor wrapper, and we have a section, and we have a div, and we have another div. Let's see, where is it? Okay. Then we have these two divs here, right? And we have another div. Let's see. How is it? Another one. Another one. Oh, my gosh, it's so much stuff here. Well, it's going to be difficult to find, it seems. But somewhere there is a text area. Let me see if I can maybe try to find the text area from the bottom up. It's quite difficult to find it. Well, anyway, what happens is inside there, there is the text area. This class side by side is because both these elements are, as you can see side by side, right? There is one in the left side and one in the right side. And both of them also have the dot, the, the vs class. So dot in front is just to say that this is a class. So I'm basically using site.gap to get all the text areas that are inside an element we have, which has both the side by side and.vs and the vs classes. Then I, well, I was here actually, I'm sorry. So I get the text areas and I say go to the first one and clear because you remember that when I accessed it for the first time, it already has some text there. So I want to clear it. And then there is something weird, which I, which is I'm clear clearing it again. And that's fine. It's the first version. I'm not going to worry about it. The thing is that when I was testing it, sometimes I would type, would clear the thing and it would still miss some lines like it would clear it like this. So I would clear it again just in case to be sure that everything was correct. Is this the right way to do it? Surely not. But as I said, I'm OK with that because this is just the first version. Ideally, what we should be doing is wait for the application to be in the state we want it to be so we can start interacting with it. Since it was the first version, I just left it this way, clear it twice with the second clear, I'm pretty sure that everything was cleared. And then I typed browser.get and my personal website premiere.dev. Then I use side up contains to get a button that contains the text, migrate to Cypress and I click on it. So I'm coming here, clearing this thing, typing something, finding the button, clicking the button. Right. And then I get again the text areas. But now instead of the first, I'm getting the last, which means the one in the right side.
6. Migration of Commands and Suggestions
I scroll the button into view and change the dot should attribute. The index zero of the last text area should include site dot visit. I migrate other commands like element by CSS and element by class name. I use site dot get with the syntax for class and site dot contains for CSS containing text. The Cypress team adopted my suggestion to use site dot contains with two arguments instead of site dot get dot contains.
And then I scroll it into the view because when I click the button, there was a scroll to the button itself. I had to scroll into the view to go back, to have the text area back in my view so I could run some assertions on it. And then I change a dot should where the dot should basically gets as attribute. The dot should receives as in this case, it's receiving as an argument a function, an arrow function. And this arrow function is receiving as an argument the text area itself. The one in the right, which is the last. And then I expect that the index zero of these text, the value of the index zero of this text area includes site dot visit. HTTPS column slash slash on your that. So I type browser dot get and I got site of visit instead. OK, and then I did the same thing for other commands. So now I'm migrating protector's element by CSS. I visit, I clear twice. I type, I click and they migrate. I go to the last one to the last text area. I scroll into the view and now I check that element by CSS. Selector is the equivalent in the Cypress world would be side of the selector. And then I do the same for protectors element by class name, where in here protractor has a specific command to get elements by their class name, which is adamant by class name sample class, for instance. And in the Cypress world, it's just another site. Got with the syntax for class, which is adopted in front of the value. Then protractor also have a command called CSS containing text. So I can identify an element by its CSS and the text content, right? So here I type adamant by CSS containing text selector and the text that I want this element to have. I click the button and I check that it is transforming to site out GetSelector dot contains sample content. This is how it was when I tested the application for a first time. You see that if we copy this is specific thing here and we type it here and we tell migrate. Nowadays, it's just a site out contains with two arguments. The selector as the first one and the text as the second. This was a suggestion of mine. When I was experimenting, I felt it weird that it would migrate to site dot get dot contains when it could use just site out contains passing two arguments. So, I suggested that and the Cyprus team decided that that was a good idea. So, nowadays, this is not what this migrates to anymore. Instead, it migrates to just site out contains with the selector and the text that we wanted to contain.
7. Code Improvements and Custom Commands
I asked for feedback from the audience and made some improvements to the code. In version two, I moved duplicated code to a before each hook to eliminate duplication. I used aliases to avoid duplicating selectors. In version three, I created a custom command for the migrate action, but later realized it was not necessary. The tests became shorter and more concise. Overall, the code was improved and duplication was reduced.
I will pause for a moment and I will ask you for some feedback. How are you feeling? Are you enjoying it so far? Is there any question, anything that you want me to clarify before I move on? Feel free to shoot your questions or feedback both in the chat here on Zoom or on Discord as well in the workshop under the TaskJS workshops channel.
Okay. So I already got some feedback here that it's very interesting and it's nice. So thanks to both of you that gave me this feedback. If I can make it nice for one person, I'm already happy. If I can make it nice for all of you, even better. So someone else here said, that's good as well.
All right. So this is the first version. And the commit was… has lots of duplications. And as you can see, I am duplicating a lot here. Like, I have one, two, three, four, five tasks, and they are basically doing all the same, visiting the page, typing the command that I want to migrate, clicking the button and checking that it was migrated correctly. So there is a lot of duplication. So let's see how version two is.
On version two, I said move duplicated code to before each hook. So Cypress uses Mocha behind the scenes. And Mocha is a test library for unit testing. And it gives you some very helpful commands like before each, where you can remove some duplicated code for things that every task should start in that same state. So, as you might have imagined, every task needs to visit the page, clear the field before it starts. So, I moved all that duplicated code to a before each rule. So, now, right after the describe, instead of having the test, we first have a before each function, which receives, as an argument, an arrow function. It could be a normal function. It doesn't necessarily need to be an arrow function. I like arrow functions because they are shorter. And then I put here the site of visit, the get of the text areas, where now I am not only getting the text areas, but I'm also using the dot S and passing a string text areas to give an alias to this thing here. So, I don't have to duplicate this selector anymore because now I have an alias. This is something that not many people that use Cypress know that you can give alias to elements. So, you don't need to duplicate the selectors. And then people start putting selectors in variables or in other files and things like that because you don't want to duplicate the selectors. You don't necessarily need to do that if you know how to use Cypress API and, for instance, give alias to elements. You don't need to duplicate this anymore because now you have an alias here, which I'm using right here. Then I get the first text area. I clear it twice. It's okay for now. Then I... Then I give it to the first one. I give an alias of left side editor, so I gave an alias to both the text areas and also to the one in the left. Then now that I have an alias for the text areas, for both of them, I can use the alias again here instead of duplicating this selector here, which is a little bit more complex than just add text areas, and I get the last one, which is the one in the right. And since it's the one in the right, I give to it an alias of right side editor because I'm going to use it afterwards. And then the tests start, where now I don't need to do all these duplications in the test. In the test, I am migrating Protractors browser.get. So I basically get the left side editor, and I type what I want, I click the migrator, the migrate button. And I get the right side editor, I scroll it into the view, and I run my assertion. As you can see here, I have removed lots of lines and I added just one line. Here, I have removed two and I added just one. And here it could be one and one because I just broke the line and it could be all in the same line. But as you can see, the code is shorter. This commit, I deleted 32 lines, and I had it only 19. So I made my test suite a little bit shorter and I don't have that much duplications anymore. But this is just version 2, okay? So on version 3, I decided that I would create a custom command to the migrate action. Looking into it today, I don't think I would do it because I basically am creating... Instead of doing siloed contains button that contains the text, migrate to Cypress and clicking on it, I created a custom command called migrate, side out migrate, which does this thing for me. But as I was saying before, this dot click could, without any problem, be just on line 19. And I would still just have one line. It would be pretty clear what it does. It gets a button that contains that text and clicks on it. But for the sake of doing this on demand refactoring that I mentioned earlier, I decided that I would have some custom commands. So I created the migrate custom command. And now instead of doing this here, I just say side out migrate, side out migrate for all the tests. And in the test file itself is where I created a custom command. I was just playing around, experimenting with things. I didn't know if this would be the way to go. So instead of putting the custom commands in my support file, I just put them directly in here and I use them directly because they are not used in other files. There's no other test file anyway. So I put them directly in here, and this is the definition of the custom command. So I am using the Cypress module from the commands module from the add function. Where I'm adding a migrate command and the migrate command executes this function here, which basically runs Cypress contains button, migrate to Cypress dot click. And the test is a little bit shorter. Every test is one line shorter.
8. Custom Commands and Code Reduction
In version 3, I created a custom command for migrating and decided to create one for the assertion as well. Although I prefer to have assertions in the test file, this case warranted a custom command. I replaced the assertion code in all tests with the custom command, passing the desired code snippet. The custom command definition is in the test file, using Cypress.commands.add. This reduced the code by 27 lines and added 13. The test suite is now shorter and more concise. I also removed unnecessary aliases.
As I said, it could be just the same if the click was in the same line, but in the end I have deleted 10 lines and I added 10 lines because although here I removed 2 and added 1, in here I had to add the custom command, which added a few more lines. So it's even, okay? And this is version 3. Since I created a custom command for migrating, I decided why not to create also for the assertion. I'm not a big fan of creating custom commands for assertions. I do prefer to have the assertions in the test file with the details that they should have. But in this specific case, the assertion was a bit too complex. And then I decided that it would be maybe worth to extract it to a custom command. So the name of the... So I basically replace this in all the tests, as you can see, to side.assertRightSideEditor code snippet, and I pass to it the code snippet that I wanted to have in the right side, okay? And I put the definition of the custom command directly in the test file as I have done with the migrate command. So I have like Cypress.commands.add, the name of my custom command, and the function that it executes, where in this case, it receives as argument a snippet, and it does the side.get, so the right-side editor is curl it into view and chain it should, that receives a function that receives as argument the text area itself and expect the value of the text area to include the snippet that we are passing to it as an argument in here. Okay, this one removed 27 lines of code and added only 13. So my test suite is getting shorter more to the point. Okay, then I have noticed that I was using some alias that was not necessary anymore.
9. Commit Description and Abstractions
For V5, I decided not to have just a title for the commit, but also a description. This is valuable when reviewing code from others. The unnecessary element alias was removed. Abstractions should only be added when needed. Giving context in commit descriptions can help with code reviews.
So notice that until before, we have just the title of the commit. For V, until before, we had just the title of the commit. For V5, I decided not to have just a title for the commit, but also a description. And this is something that I find very valuable, especially when I'm reviewing code from others, that if the title is not specific enough, I can add more information in the description of my commits. So the title of my commit is removed. Unnecessary element alias. And then I gave some more context. Instead, call it directly where it's used, now that it's used only once.
So let's see the code. And in here, if we expand this, in my before each hook, I was giving an alias to both the text areas. I was giving an alias to the first one as left side editor. And I was getting the text areas dot last and giving it an alias to the one to the last, which is the one in the right side editor. But after I have done this refactoring and I removed all the duplication and I moved things to the before each, I wasn't actually using this alias more than once. So I like to add these kind of abstractions when I'm using at least this thing three times or at minimum two, but I prefer when it's like I repeated this three times, then it's time to move it to an alias, for instance. But after I did this refactoring, I was using it only once. And so I felt that it was more complexity to add this alias here, then just do something like that, side out. Get the text errors dot last, which is the one in the right side. So this is a very short commit. And the point here is that we don't need to add abstractions when there's no need, because the abstraction that we created is just been used in one specific place. So there's no real need for the abstraction, which in this case would be just this alias here. And I also mention that you can always give more context to help people review your code. Not only giving a title, but also a description for the commit itself.
10. Version 5: Custom Command for Typing
In version 5, a custom command was added for typing in the left side editor. The custom commands were moved to the support directory, and the test file was reduced to 50 lines of code. Version 8 introduced the use of fixtures to iterate over an array of test scenarios, shortening the spec file. The fixture file contains an array of JavaScript objects with different task cases.
Here we are on version five. So let's go to the newer commits, where we can move on to version six, where I added a type on left side custom commit. Again, if I was to do it today, I would probably not create this custom command, but I already had a custom command for clicking the migrate. I had a custom command for the assertion, so why not create a custom command for the action itself? So action for typing in the right side editor. Oh, that's nice. We have already 28 participants. I hope you are enjoying the workshop. If you have just joined, don't worry. The workshop is being recorded and it will be available to you all in a few days.
Okay, so I was saying here, now, instead of doing psy.get, left side editor,.type, browser.get or.type, element-by-CSS or element-by-class-name, now I have a custom command psy.type on left side editor and here is the text that I want to type into the left side editor. And then I have the definition of my custom command right below here, just above the migrate command. So cypress.commands.add, the name of my command, which is exactly the name you see here, type on left side editor and the function that will be executed, which receives the snippet. It gets the left side editor by its alias and then types on it the snippet that was received here, which, for this task here, the snippet was elementbycssselector.sendkeys, for this one was elementbycsscontainingtext, selector blah, blah, blah. And now we can see that the test file itself, the tests themselves, they were pretty straightforward. Like, in the before each, we have all the parts that is not being duplicated anymore, and the tests, if we look here, it starts on line 12 and it ends on line 18, the first task, it has one, two, three lines. It types on the left side, it migrates, which is basically clicking the button, and it runs the assertion. Same for the second one, types, migrate, assert, type, migrate, assert. So I am making my test suite shorter and more to the point. Okay? Then, we have version 7, where now I decided that it was time to move the custom commands to the right place. So, if you don't know, Cypress when it starts, it when it bootstraps itself, it creates a support directory where you can add your custom commands. So Cypress has its own commands, like site.visit, site.get, dot type, dot click, site.request, site.intercept, lots of commands, you can look through all of them at docs.cypress.io. But you can create your own, as I was doing until now, and I was putting the definition of this custom commands in the test file itself. But Cypress has this support directory where you can put this custom command there, and they will still be available for you in the global site object. So in this commit of version seven, I basically cut all these custom commands that were defined in a Migrator.cy.js file, and I put them into the commands file, which is inside of the support folder inside of the Cypress folder. So I basically put them there, cut from one place, pasted in the other, and then in the end to end.js file inside of the support file, I'm importing the commands basically. So the commands are available in my test, and then my test file now has only 50 lines of code, and it's pretty straightforward as you can see here now. Each task has only three lines in sight. If we exclude the empty lines, that, I prefer to leave just for giving it some breath and you can read it easier. But the tasks, they are pretty straightforward. And when they end, it's done. We have five test cases for this test suite, and they do basically the same. They type, they migrate, and they assert. Okay? But they type different things and so they assert different things. And the B4H has all the steps that all the tasks need, so we don't need to repeat them all around. So this is version 7.
And then we have version 8, where I set shortened spec file. And here we can see that I also have a little bit more details in the description of the commit, so by iterating over an array of test scenarios based on a fixture file. This commit here is one of the most important of this project because this is the commit that made this test suite to become what it is today. Okay? So pay attention to this one. Remember that when I configured Cypress in the first place, I said, you know what? I don't think I'm going to use fixtures. So I configured Cypress with fixtures folder files. Now I have removed, in red here, we have the lines that were removed and in green, the lines that were added. So I have removed this here because I decided, you know what, now I think I'm going to use fixtures and I'm going to use fixtures to shorten the spec file, as my commit message says. In my test file...
So I created some fixtures. So I created the fixtures directory and inside the fixtures directory, I created the testscenarios.js file, which you'll see in a while. And I'm basically importing the test scenarios from this fixture file, okay? In the test. And now I have still my describe, I still have my before each with all the repetitive code. But I have deleted all those tasks. And instead of having all those tasks, which there was still a lot of duplication, right? I was doing this and this and this and the only thing that was changing was actually the arguments of the first and the last custom commands. So instead, I said, you know what? I could have an array, which would have every... These array would have different objects and these objects would be my task cases. So I decided that if I had that, I would be able to do test scenarios, which is the variable that I defined in here from my fixtures. And I am using the for each iterator from the array to iterate over each scenario. And then I'm getting the scenario, which is an object. From this object, I'm destructuring the title, the snippet to be migrated, and demigrated snippet. And then I create my it block. And the it block will have the title as the description off my task. And it will run a side dot type on left side where it will type these snippets to be migrated, which was destructured here from the scenario. It will side out migrate to click the button, and it will run my assertion receiving as argument demigrated snippet, which was destructured in here. And now it's the fixture file. It basically looks like this. So I am basically exporting variable, a Coast variable, which is an array. Okay. Oops. So the array starts here and it starts here and ends here. This array has five elements inside, and these elements are basically JavaScript objects. So the first object has title snippet to be migrated, migrated snip, second, the same, third, the same. The only thing that changes is the title, The value of the properties is what changes. So the first test will be mygrades pro-tracker about browser.get.
11. Migration Process and Code Improvements
In version 8, I stopped using custom commands and moved the implementation details to the test file. This reduced duplication and made the code more concise. In version 10, I added an extra assertion to test the API details element. I also added Cypress commands to the fixture objects for each test scenario. Version 11 removed duplication from the fixture file by using template literals. The changes made the code shorter and eliminated repetitive phrases.
Second one, migrate pro-tracker element by CSS. In the first one, the snippet to be migrated will be browser.get while in the second, it will be elements by CSS. The migrated snippet in this case will be site.visit while in the second is site.get and so on and so forth. But the idea is now that I have done that, I can add as many scenarios as I want and I don't have to change one line of code in the test file. I just add another. I just add a comma, another object with title snippet to be migrated and migrated snippet. And when I run my tests, I have one more test case. And if I add more, I will have more test cases. And that's as easy as it would be. So before, I think, our test file had around 50 lines. Now it only has 21 lines. And it's very true to the point. It's basically this thing here, right? We are iterating over each test scenario. We are creating EAT blocks for each of them. And for all of them, we are doing the same thing, but we are passing different arguments, OK?
Now we are in version 8. So let's go to version 9. There are 42 commits. Not all of them have versions. I'm going to go until version 21, which is the last one that has significant change. And then we can briefly look into what I added later, but it's basically upgrading Cypress and doing some other things. Maybe we can look into the GitHub Action workflow that I have created as well. So I was, until now, until version 8, I was using custom commands. And then on version 9, I decided not to use them anymore, and there is a description for that. And I said, do not use custom commands since they were used only once. They are not needed anymore. So, again, I said that before for the alias. If you are creating an alias, you are using it only once, there is no need for an alias. The same applies for a custom command. If you are creating custom commands, you are adding another layer of abstraction to your task suite. You're moving these commands to the support file. And then you notice that, you know what? Now that they have migrated everything, I'm using it only once because now there is only one block which is executed based on how many fixtures, how many test scenarios I have in my fixture file, right? So instead of having these abstractions of type on left side editor site.migrate and site.assert, I prefer to have the implementation details in the test file itself. Because if I want to understand what's going on, I don't need to go to another file. Everything is in here and there's no duplications, right? I'm not duplicating anything. I created a smart way to create many test cases using the exact same structure based on fixtures, okay? So I basically removed the commands completely. I removed the import from the support end-to-end file and inside I have the get left side editor and type the snippet to migrate, to be implementation details. To migrate, to be migrated, I get the button that contains the migrate to Cyprus and I click on it and I get all the text areas, go to the last one which is the one in the right side, this one here, and I checked that it has the correct value, that the migrated snippet is there, all right? Let's go to version 10. Let me see here if we have any questions. There is some mention from Duke. Nice design with fixtures directory. Easy to add tests later. And you'll see that in a few commits, I'm just basically adding more tasks and it's as easy as adding more entries to that file. So on version 10, I updated tests with an extra assertion. So, so far I was just testing that. If I type something here and I click I'll get something else here. But I wasn't testing this part here below. So this is what I started testing on version 10. So I decided that besides the title, the snippet to be migrated and the migrated snippet, I would also add a Cypress command to my fixture object, to each of my fixtures in the array. And then besides running this assertion here that the last text area would have the content that it should have, I'm also getting the API details element and I am asserting that it should contain the Cypress command, which in the features basically besides the snippet to migrate, I added Cypress command, Cydot visit, Cypress command, Cydot get, Cypress command, Cydot get, Cypress command, Cydot contains, Cypress command, dot type, because when I migrate, I see here a list of the commands. And in here, for instance, there is just one command, but if I was, for instance, doing a dot click on it, click, and I migrate, I would have actually here Cydot contains, select, sample content dot click, and in here, I would have actually two commands. In version 10, I'm just checking the first one. So I'm not passing more than one. I'm just checking that it has the one that I think is the most important. Afterwards, you see that there is a commit that introduced a way to check them all. So that's version 10. Let's see. Then on version... So this is no version, so I'm going to skip it. Remove duplication from fixture file. As you could see in the fixture file was saying migrate protractors and then the command. So it was migrate protractor... It was migrate-protractors-browser.get migrate-protractors-admin-by-css. So the migrate protractors was always repeated. In this case, I decided, you know what? I could simply use template literals. So in here I passed just what changes, which is the command itself. Send keys. By css containing text, by plus name, but the other part doesn't change. And so in here for my it block I use template literals where I interpolate the string my grades, protractors and then the variable, which is the title that I am receiving. And then this way I am not reducing the number of lines. I'm just leaving my feature value property a little bit shorter and without duplications in that sense. So this is a pretty straightforward commit version 11.
12. Version 12: Improved Object Attribute Naming
In version 12, I improved the naming of object attributes by changing 'snippet to be migrated' to 'snippet to my grades'. This made the name shorter, more concise, and still clear in its meaning. I followed the principle of leaving the code cleaner and made the necessary changes in the code and fixtures accordingly.
Version 12, I decided to better name an object attributes. So I had snippet to be migrated and then I changed it to snippet to my grades. So just left it a little bit shorter, but still with a very meaningful name that you look into it and you understand what it is. It's the snippet to my grades, basically. So I like to follow the boys' culture of leaving the campground a little bit cleaner than you found it. So I was, you know, reviewing my own code and looking into it and I thought, you know what? This could be, instead of snippet to be migrated, just snippet to my grade would be shorter. It would be concise. It would still be very clear what it is. So I just changed that and then I changed it when I am destructuring all the info from the scenario, when I am iterating over my scenarios. And then I changed in the fixtures the name of the property as well accordingly. So this is version 12.
13. Migration Process and Code Improvements
In version 8, I improved the migration process by not using custom commands. This made the code easier to understand as all the implementation was in the test file. The inspiration for this approach came from Backstop JS, a tool for visual regression testing. I also made improvements in version 13 by clearing the text areas only when necessary, reducing duplication. In version 14, I allowed testing more than one Cypress command by modifying the fixture to receive an array of commands. The Cypress team added testability to their front-end application by using data test attributes, which I utilized for assertions and actions.
Let's see. I think we have some comments here in the chat. Don't move in test cases to fixture files. Does it make the tests harder to understand? I don't think so. In my opinion, it's actually because all the implementation of the test is in the test file. What the test does is very clear in the test file. So let's go to version 8 here, which is this one. Now let's go to do not use custom commands, this one. This is what the task does. It gets a title and then it runs side of get left side editor and it types this snippet to be migrated, it clicks the button and it runs the assertion. So it's very to the point what the task does. The idea of migrating what I did to the features is that I had lots of duplications still in my task code that were not necessary if I were using this for each function that I am using now. So I would say that in my opinion, no, it's actually it's making things even easier to understand because I don't need to go to different... When I need to add a different task scenario is just adding another entry in my name, an entry in my feature. Actually the inspiration for doing this way is a tool called Backstop JS. Backstop JS, it's a tool for visual regression testing, which I have teached about it, I have worked about it both in Portuguese and English on my Udemy profile and with Backstop JS, you basically do something very similar. Like there is just a JSON file where you define your scenarios as objects inside of an array and they could have the same properties and then when you want to add another scenario, you just add another entry in your JSON file, in this case here, in our case a JavaScript file. But just to give you some context, was the inspiration, let's say, for doing it this way.
Okay, so let's see in which one we were. Okay, so we were in BetterName object attribute V12, so V13, clear twice only when needed. So I did some improvement. Remember that in the beginning we were getting the first element and then typing using the.clear command from Cypress and then clearing it again, which was super weird but it was needed because sometimes the application here wouldn't be in the correct state and when I would clear it would still leave something. So I would have to clear it again, okay. But then I said, you know what? This is still not ideal, but it's a little bit better I think. So I'm going to get the text areas. From the text areas I'm going to get the first, which is the one in the left side, which is the one that is not read only. Before clearing, I'm going to type between brackets, select all, to basically do something like this. I would be selecting all the text and then I would do the.clear to clear it all with that clear, okay. And then I would chain a.then, which can receive as an argument a function. This function can receive as an argument the subject element itself, which in this case is the first text area. So this is why I'm calling the variable text area. And then I'm saying, I'm adding a condition. If there is still some value for this specific text area, then log in the Cypress command logs that you are clearing again, wrap this text area, and then execute the.clear command again. But when I added this, select all and clear, it solved 98, 99% of the cases. In some cases, it was still leaving something there, and then it was reaching this if condition. It was returning true, and it was clearing again. There were two different solutions for the thing. One was selecting all before clearing, and then if there's still some value, which is a text area of index 0,.value,.length, if there is still some value there, then log that you are clearing again, and do clear it again. So it's better. It's not executing the.clear twice if not needed, only if it's really needed, okay? And I fixed the test title which is not relevant.
14, allow to test more than one Cypress command. So remember that I said that before I was only testing the first one here. So I want to test them all because if this command here is actually returning not just site.contains but also a.click, I want to test that both of them are displayed in here. And even inspect here to show you one thing. Let's see. More details. Is it what I'm using? API details. Yeah. As you can see here, this div has this DataTestAttribute, API details, and inside this div, we have an unordered list which has two list items. The first one for the side.contains element text and the second one for the side.blick. So the Cypress team, they added what I like to call testability in their front-end application adding data test attributes that I could use, to make assertions, in this case, or it could be to run actions as well. So basically here, let's see, allow to test more than one Cypress Learner. So before my fixture was receiving a Cypress command, now it's receiving Cypress commands The fixture itself now, instead of Cypress command it's receiving a string. It receives an array. And if there's just one command, it's going to be an array with only one string inside, which in this case is cy.visit. In this case, it's cy.get. But if there is more than one command, like this one that has cy.get and a cy.contains. Then I can put more. And if there were three, four, as many commands I needed, I could put them because now, cypress command is an array. It's not a string anymore. And then in the test file, after I run the assertion, after I run the assertion that the correct snippet was migrated correctly, I also iterate over the cypress commands. And then in the cypress commands, I run for each and I pass a callback function. This callback function receives each of the commands. So in this case, it will receive just cy.visit. But in this case, for the first iteration, it will receive cy.get, and for the second it will receive cy.contains. And then I check that the API details should contain the command. So for the first iteration it would check, does it have cy.get? Yeah, it does. Let's go to the second iteration. Does it have the cy.contains? Yeah, it does. So it would do it like, look if cy.contains is here and if cy.click is here.
14. Migration Process and Code Improvements
I added more assertions to my tests in version 14. In version 15, I added new tests by updating the fixture file. Cypress commands are shorter and easier to understand than Protractor commands. The Cypress project allows for easy addition of new tests by adding entries to the fixture file. The tests cover various scenarios, including element by ID and element by CSS. The Cypress commands are concise and provide a more optimized way of testing. The workshop participants share their thoughts on the approach, and the speaker recommends optimizing tests through HTTP requests. The speaker also suggests visiting their website and taking their Udemy courses for further learning. The chat includes discussions on file organization and the benefits of smaller files. The speaker mentions a tool called Better Code Hub for code quality analysis.
Okay, so now I added a little bit more assertion to my test. I'm not just checking that one command is there, but all of them that should be there are there. So this is version 14. Then, on version 15, I added a bunch of new tests by just updating the fixedroot file. That's the beauty of this project here. Now if I want to add lots of new tests, as I was saying to Olga first, like now you can see that it's pretty easy to add the new tasks. You can just add new entries to your fixture with different values for the same attributes, title or snippets, to migrate, migrated snippet, and Cypress command. So if I want to test how this element by ID works, this is how it looks in protractor syntax, and this is how it would be in Cypress. So instead of element by ID and then your ID in here, you would have site.gat, hash my ID, is displayed. Instead of have expect element by CSS, selector is displayed to be true, you would have site.gat, selectors should be visible. And in this case, since I would have site.gat and should, I would have in the Cypress commands, site.gat and.should, is present, expect element by CSS, the selector is present to be true. It would be transformed into Cypress. Get selector should exist. Again, as you can see what I said in the beginning, most, if not all the Cypress commands are shorter than the protractor ones. This one is shorter than this one. This one is shorter than this one. And the same goes on and on and on. Even browser.getTitle is just site.title, right? And they are still very easy to understand. Actually, in my opinion, a lot easier to understand than what we had with protractor. I'm not going to through all of the scenarios. It's just all the same, just passing different values for the title, for the snippet to migrate, for the migrated snippet, and for the commands. Somewhere we might have, here, for instance, I am migrating expect element by CSS, all the li's inside of the unordered list and getting the count of them and expecting that they should be three. In Cypress, it would be site.get ul, li, its length should deep equal 3. In this case, it's a little bit longer than the one above, but still very easy to understand, very easy to read. You get all the li's inside the unordered list, get its length, and it should be deep equal to 3. And then, because I have site.get, I have its, I have should, in the Cypress commands array, I can have site.get, its, and should in here. I don't have to change anything in the task code itself, just in the fixtures, and I have lots more, a lot of more tasks now.
Let me see here, we have a few more messages in the chat. There is one, I agree with Olga, the first way seemed easier to understand. Well, I hope by the end of the workshop, you can change your mind, and if you don't want, that's okay as well. I'm not here for you to change your mind. I'm just showing you the process of creating this task suite in the way that I found that would be optimal. Here it probably makes sense, since there's only null test files. I think it should be one test file. Yeah, but still, Rosislav, if there were other test files, the other test files would be testing other things, not the same things. So I still think that it still makes sense, because the other things would have other commands and they wouldn't need anything from this test file. Because our tests, they should be independent and we shouldn't be over-testing things, which is something that I see most people coming from other test frameworks doing, over-testing the application. What I mean by that is doing too much through the UI instead of creating more optimized ways to do the things. And Cypress allows you to do things in a more optimized way. If you want to learn more about how to optimize your tests, I would encourage you to come here to my website, from me.dev, and go to my courses on Udemy. And then if you go to the intermediate course, you see what I'm talking about. You can create all the preconditions of your tasks in a very optimized way, for instance, doing HTTP requests. And then when you go to your test, you reach the application with all the data that you need already there, and then you don't need to do as much through the UI, which, in the end, means that you wouldn't need to have all these abstractions and everything because you are doing the things through the UI only once anyway. Let's see what else we have here. Not the case with my app. I use lots of tests, CytoJS files, blah blah blah. Duke seems to be more of a style preference. In the past, my teammates stressed that things needed to be as dry as possible, but I find the testscenarios.js files to be very easy to understand. The objects are basically title, tab, task, expected result. Yes, exactly. Let's see, there's a message from Marcos. I think that this method is amazing. I'm not sure if people are aware that in the test runner, these appear as separate tasks so it is still easy to trace where the bug was found. I will definitely adopt this approach. Can Cybot Sessions be used in your before each? In this case here, I don't think it would really be needed because I'm not doing any kind of authentication or anything. We'll have to experiment, Marcos, to let you know if I could save in the session the state of the application with already everything cleared. That's a good idea, actually. I'll try it out afterwards. Just need to get used to it. Objects are okay. I use the same approach frequently. I just don't like lots and lots of small files. Why not, Olga? Small files, they are easier to read than big ones where you have to have lots of scrolling. I actually do prefer smaller files than big files. I actually recommend you to go to a website called bettercoderhub.com. This is a SaaS that I have used in the past, and they have actually, it's similar to SonarQube, but instead of... It clones your repo, let's say, and it looks into code smells and code patterns and everything to see how good or bad is the quality of your code. But instead of doing something like Sonar does, which is give you lots of information, what Better Code Hub does is it actually gives you 10 guidelines for you to follow. Let's see if we can find the guidelines. I think if we go to docs maybe or not, let's see, about guidelines.
15. Guidelines for Writing Code
The workshop mentions 10 guidelines for writing code. These guidelines cover writing short and simple units of code, writing code once, keeping unit interfaces small, separating concerns in modules, coupling architecture components loosely, keeping architecture components balanced, keeping the code base small, automating tasks, and writing clean code. The guidelines provide context and explain the benefits of following them. They emphasize the importance of small units of code for better understanding, reusability, and testing. The guidelines also offer suggestions for improving projects that do not meet all the criteria.
Yeah, it only mentions here. It gives you 10 guidelines, and one of the guidelines is actually that you have to have short files and all that stuff. So I highly recommend you to, let's see if I can actually sign in with GitHub to give you an example. And let me sip my water here because I've been talking a lot. So yeah, I have here a few projects that I have integrated with this too, and it's out of the context. But since all were mentioned, I wanted to talk about it. Let's have one that is five that has maybe a score of 10. Yeah, this one has a score of 10. So let's look into it. How do I look into it? I can go to View Results. So the 10 guidelines are write short units of code, write simple units of code, write code once, so the dry thing. Keep unit interfaces small. Separate concerns in modules. Couple architecture components loosely. Keep architecture components balanced. Keep your code base small. Automate your tasks and write clean code. The first four, they are about the way you write your code. The next four are about the way you architect your code. And the last two are about the ways you work. And if you click on each of them, you here have tabs, a tab for reading what the guideline is all about. And this one is the one that tells you. Small units are easier to understand, reuse and test. And so it gives you some context. And if your project is not 10 out of 10 as this one, it would give you some info about what could be improved and things like that. So that's my take from all this mentioned before, okay.
16. Versions 15-19: Tasks and Improvements
We are now on version 15 and will continue until version 21. After that, there will be a five-minute break for Q&A. In version 16, more tasks were added to the project. Version 17 allows running tests against a local environment. Version 18 improves task descriptions by including the protractor command in the title. Version 19 introduces the side-press plugin for simulating keyboard key presses.
So we are on version 15. Let's see how we are in terms of time. I think we can go into version 21. And then we can have five minutes' break so you can grab the water, do whatever you want, and then we come back for Q&A. What do you think? Leave your comments on the chat.
Let's go to version 16 because we are almost there. Version 16, I added just another bunch of tasks. I already had a bunch of tasks. You see, like, my task file is very short. My scenarios in the feature is getting very short. The feature is getting big. It has almost 200 lines, but it's still very to the point. It's just a few more entries into my array. They have title, snippet to migrate, migrated snippet, and the Cypress commands that should be available, okay? So very straightforward, just added a few more tasks.
Version 17, allow run tests against a local environment and document how. So this is where I added a session in the documentation. I don't know if I should have added this as a version, but since now you can also, I created this script, CyOpenLocal. Yeah, maybe it makes sense. Then I basically what I did is before I was having the URL inside of the site of visit command, instead I added the URL as a property of my config file in the base URL. Then I can simply visit slash. So the base URL is https://migrator.cypress.io. And then I do site of visit slash, which will basically have this slash in the end here right after the.io. And then I can have this script that runs the Cypress local or in headless mode, where I can overwrite the base URL with dash dash config base URL. Https://localhosts column 4200, okay? So, in case you come here to the Cypress migrator official project, you clone it, you install off the dependencies, you start up your local environment, and then you do the same, you clone the project from my repo, you can then run the task that I wrote against Cypress migrator in your local computer instead of the version that is running in production on migrator.cypress.io. This is what version 17 basically allows you to do. I decided to show you running version that is in the internet because it's much faster than the one when I run it locally, especially now that I am live streaming, streaming and, not streaming. But yeah, I'm sharing my screen, streaming my video and everything.
So let's go to version 18. In version 18 I decided to improve the tasks descriptions. And in here before in my features, I had the property title. But I had the Cypress commands. And I thought, you know what, I could have protractor command that could be used in my title. And then instead of having migrates protractors title, I would have migrates protractors, protractor command. Command, so if it was a side element by CSS, it would be my greats protractors element by CSS command into Cypress. And then the Cypress commands, since they are an array, I would just get the first one, so what would calculate the length of it, subtract one because an array starts on index zero and then say command. So it would be, for instance, my greats protractors element by CSS command into Cypress, cy.get command, something like that. And then I changed in my features, every place where I had title, I instead called it protractor command. And that's basically it all until the end. Okay? Version 19, we're almost there. Improve tests readability by using the side dash press plugin that I have created. Let me show you. NPM, this is the one. So if you go to npm.js, and you search for Cypress Press, you'll find this plugin that I have created, which as its description says, it simply adds a silly cypress.press command that simulates pressing the keyboard keys. You can NPM install it as a dev dependency. Afterwards, you can import it or require it in your support file. And then here is how you use it correctly and how you should not do it. So the idea is you can, for instance, let's say I'm testing Google or DuckDuckGo where I would have an input text, and instead of clicking the magnifying glass button, I would just type and press enter to run my search. This is how I would do using this plugin. I get an input of type text. I type cypress.io and then I press enter. And the available keys are described here. They are the exact same as in the official Cypress documentation. So here are all the available options. You can see backspace, press down arrow and so on and so forth. Plus, I have added also, you can say press ctrl A, upper case or lower case. And the same for comment if you are Mac user, for instance. Here are examples that use this library. I have actually had this one in here because it doesn't use it as well. And I just wanted to show you because this is what this commit does. So this commit, I installed Mylib on version 1.0.2. Because I installed it, I added it to the ntwm.js file inside of the support directory. And now instead of doing type, bracket, select all, I simply say dot press, select all. Which would do this thing here. It would do something like this. Selects everything that is in there so that I can clear afterwards. So just to make it a little bit easier to read. So, what does it mean? Type, select all between curly braces, right? It's kind of weird if you don't... If you don't know exactly what this does in Cypress. But if you say dot press select all, you would understand it better. So this...
17. Versions 20-21: Href Assertion and Code Shortening
In version 20, an extra assertion was added to test the correctness of the href attribute in the API details section. The command is transformed to remove the sci, dot, and parentheses, and the href is checked against the transformed command. In version 21, the test code and variable names were shortened for readability and conciseness. A five-minute break is taken, and a question from Olga is addressed regarding the use of index in iterating over the Cypress commands.
This command, again, the Boy Scout rule. Leave the campground cleaner than you found it. I just left it a little bit easier to read the task itself.
Let's go to version 20, the one before last. Now I added an extra assertion on the API details section. To test that anchors, hrefs are correct. So if you remember when I executed all the tasks, I was not only checking that the side contains here and the site of click is here, but also that it has the correct href. So if we inspect here, we'll see that. Let's see. Where is it? Yeah. Here is the anchor. It has an href to on dot cypress dot IO slash click and this other one here has to on dot cypress dot IO dot contains slash contains, which redirects us to docs dot cypress dot IO slash API slash commands slash contains. Okay? So in this version 20, I decided, you know what, I'm not testing this specific thing and I think this is important, so let's add a test for that. Not add a test, add an extra assertion in the same test that we already have for that. So what am I doing here? Now the only change was in the test file itself, so I'm still iterating over all the cypress commands, which are those arrays inside of my fixture. So I have the test scenario's fixture. Each test scenario is an object which has a protractor command, snippet to migrate, migrated snippet, and the cypress commands. These are the cypress commands, so I'm iterating over each of them, but now instead of getting just the command, because I want to check that the command is displayed in the page, I will also want to check that the href of that specific anchor is correct. So before I was using, my callback function was receiving just one argument and then I can omit the parentheses. Now that my error function is receiving two arguments, the command and the index, I have to put the parentheses in place. And I'm getting the index because I will need the index, right? So, first I am doing a transformation here where I'm getting the command of that specific index. So in the first iteration will be the command in the index 0, which is the first command. In the second iteration, the command in the index 1, which is the second command, and so on and so forth. I'm basically getting this command and I'm replacing. So the command is... Let me open in here. So the command is something like this, right? Sci.get or dot submit. This is a command. This is another command. In some cases, there is the sci in front and some there are not. So I'm basically saying, if you find a sci, replace it for an empty string. If you find a dot, replace it for an empty string. And if you find a parenthesis, replace it with an empty string. In the end, if we replace this, the dot and the parenthesis will get just get. In this one, if we replace the dot and the parentheses, we'll get just submit. And this is exactly what I need because the command itself is sci.contains, sci.click, but the href is just contains or just click. And this is what I need, and this is why I'm doing this transformation. So I'm basically transforming the command to have just the command itself without the sci, without the dot and without the parenthesis. Then I get the API details div. I still assert that it should contain my command, which in this case, it's not the transformed version, is the sci.get, an open and closed parenthesis. But from this element, I find the anchor, so I find the a. Then I will find if there are more than one, for instance like this, it will say, okay, I found two. Which one do you want? Get the one with the index that I am on, which if I am in the first iteration, it will get the one with index zero. If I am in the second, the one with index one. If I am in third iteration, it will get the one with index two, and so on and so forth. And then assert that it should have the href, it should have the attribute href with the value HTTP column slash slash open on.cypress.io slash my transform command, Cypress command which is just the contains or get or visit without the site. And the parenthesis at the end. So, with this one, I added this missing verification that I needed to make sure that the application works as it should. And then we have version 21 where I basically shortened the test code and the variable name a little bit. So, in many places I was breaking the line. Sci.get- left-side editor.type in the next line. And I felt, you know what? It would still be short if it was just in one line and it would make my code a little bit shorter as well because now instead of two lines, I have one. Same for the click. So, I decided to do that. So, have the click just after here and the type just after here in the same line. It's still very easy to read, easy to follow. I like to break it down in many when I'm doing something like that, where I'm changing more than one command. But if I'm changing just one and the first command is still short, it's maybe better just to have it all in line. And I also changed, shortened a little bit the name of my variable here to instead of transformed cypress cmd from command, transformed sci cmd. So, I'm not reducing number of lines here, just making my variable name a little bit shorter, still easy to follow, easy to read. And that's version 21.
As I said I would, I think we could have like a small break of five minutes. I'm just gonna stop sharing for a little bit. I'm gonna mute my mic and mute my camera. Let's go have some water. Some like five minutes rest time, and we'll be back in five minutes. All right? See you soon. So, hope you had some time to grab a water, a soda, or, you know, just to hydrate yourself a little bit or to stretch a little bit. We have a question here from Olga, where she said, you actually... Well, she said, why using index if you can use Sci.Contains? And I think you were mentioning about what is in my screen at the moment where I am iterating over the Cypress commands and getting out only the command, but also the index.
18. Using Index for Assertion and Workshop Takeaway
I explained the need for using the index when asserting on the href attribute of an element. There was a question about the workshop recording, which will be available on the GitNation portal. I discussed the changes made to the code and the GitHub Actions workflow. I also mentioned the upgrade to Cypress 10.11.0.1 and the addition of a badge to show the task status. The takeaway from the workshop is to start simple and add or remove complexity as needed.
And actually, I do need the index, Olga, because I'm not asserting on the content of the HTML tag, and instead I'm running an assertion on a specific attribute of that element, which is the href attribute. And so I need to get the div with the data tests, API details. And then from there I have to find the anchor element. So this is a div. Inside this div, there's lots of elements. Some of them are anchor tags, which are links, basically. Then I have to say, and then when I find the links, I might find, as I showed you here, more than one. So I need to know which one do I want to check for the href attribute. Is it the first? Is it the last? So I do have to say, find the a that has the index equal to one, and then I will say, now I'm looking into this one. See if the href is correct. Second iteration, see if the href of the second one is correct. So that site contains wouldn't work. So it contains just for the content of the tag, not for the attributes of the HTML elements. Okay. And then August said something else. So you said here, I can't understand why there could be several anchors. It's exactly what I said before. Let's do it this way. So I have this selector here, right? Let's come here. Let's go to the console. It's the document, dot query, selector. And let's pass this in here. So I get this element here, okay? If I enter, I get this div, okay? If instead of that I say, inside of this div, get the A, it will get just the first one, but site.get actually works like query selector all. Where if there is more than one element query, so let's see, what did I did wrong? Query selector all. And now it's returned an array where the first item of the array is the first A and the second item of the array is the second A. So it does return an array of elements, and because it does return an array of elements, I need to use the index to know from which element I want to check for the href. I hope that makes it clear for you to understand all.
Let's see, we have a question or a comment, let's see, a question from Fernanda. Hi, Romier, thanks for the workshop. Question, will the workshop recording be shared afterwards? Yes, it's going to be available, as Sasha just mentioned in the chat in the GitNation portal in a few days, okay? So yeah, if you arrived late and you weren't able to get all the content, you have access to all of it afterwards, so don't worry. Let's see, there's someone that just arrived, so welcome to someone that just arrived. Hope you have a good time. We are kind of more close to the end, but still, as I just said, it's being recorded, it's going to be available afterwards, so don't worry.
So after version 21, there are no more versions, it's more like I fixed... when I started coding, if I would change... if I would type Cy.getSelector.ContainsSampleContent, I would get... well actually, if I would type Elements by CSS containing text, the Selector and then the text, I would get before a Cy.getSelector.ContainsSampleContent. But I created an issue. I can actually show you the issue that I have created. So, if you go to Issues, and we go to Close to Issues, it's the only one that was closed. This was open by me, as you can see here. I said, when migrating something like this, so, I want to migrate Elements by CSS containing text, the Selector is.pet or a class pet, and the content is Dog, it generates Cy.get.pet.ContainsDog. And I said what about migrating it as just a Cy.contains that would receive true arguments, the first one the CSS Selector and the second one, the text itself, and Cy.containingText receives true arguments. Cy.contains also can do it. And then someone liked the idea and decided to implement this change. So, there was a PR pull request to change it, and this is why when the fix came, I tested again against the production environment. The tests that were checking Cy.get.contains were failing because now there was just Cy.contains, so I updated them to be just Cy.contains the selector and the text, and then I updated of course the Cypress commands as well because here I wouldn't have anymore Cy.get and the Cy.contains and instead I would just have the Cy.contains. So I updated a few tests here, then I upgraded to version 10.10, and what else? I removed some Cypress config, then I added a GitHub Action workflow where I basically copied the basic workflow from the documentation, and now if we go here, we see that for every push that I do, there is a GitHub Action that runs the tests, and if we click here, we can see the test results in a terminal like on GitHub where we can see here Cypress Migrator migrates protractor browser.getCMD into Cypress SCI.VISIT command. Migrates protractors browser.getTitle command into Cypress SCI.TITLE command and so on and so forth. So now this is not only, I'm not just able to run these tests against a remote environment or a local environment in my machine, but I can also run the tests against the production environment using GitHub Actions in a continuous integration workflow. What else? Let's see here in the commits. After I added the GitHub workflow, I decided to add a badge in the readme to show that the tasks are passing. And I've been upgrading from Cypress 10. We started with Cypress 10.4, then Cypress 10.10, then Cypress 10.11, and today I upgraded it to Cypress 10.11.0.1. And I added this takeaway markdown file. So the badge that I was mentioning is here. So now if the tasks failed, this will turn into red. Luckily, everything is working. Not luckily. It's because it's actually working, right? So we have this nice badge here. And I would love to answer some questions if you have more. But I would like to go this takeaways.markdown file here that I created because I want you to get out of this workshop with this thing here. So start simple and add or remove complexity on demand. I review code of a lot of people. I train people into test automation with Cypress and I did that with other tools as well, with Protractor in the past, with Backstop.js and other frameworks as well. And I see people because they were used to do things in one way, for instance, let's use page objects, which is something that they don't recommend in the Cypress world because there are many other options that we could use. And I don't like page objects also because it over tests your UI. You go through the UI to the same place again and again, when you should actually be creating more optimized tasks, as I like to call them. So start simple, as simple as possible, even if you have to add some duplications in the beginning, as you saw in version one of my commit. And evolve it on demand.
19. Code Complexity, Communication, and Testing
Add complexity when needed. Remove unnecessary complexity. Let the code communicate with you. Abstract when necessary. Write short commits. Use aliases to avoid selector repetition. Make use of JavaScript's power. Be happy and keep testing. Encourage questions. Discuss fixtures. Provide feedback. Decide whether to continue or finish. Explain fixture file structure. Answer questions about code. Run tests again.
Add the complexity when it's needed, not beforehand. Don't try to predict the future, okay? And also, when you notice that you added some complexity and that complexity is not needed anymore, like I showed you in one of the commits, where I had actually added the complexity of adding an alias for an element, because I was reusing that all around. Now that I'm not using it anymore, let's remove that complexity. The same thing for the custom commands. I created custom commands because I was repeating the same thing over and over. Then I decided, you know what, I could have those fixtures and I would iterate over each of them and I would just have one test. Why do I need the custom commands if I'm using the custom commands only once? So remove the complexity as well. Add and remove it on demand. Don't worry about duplicated code when you are starting. You can always refactor it later, as you saw that I've been, you know, evolving the architecture of my task suite over time while I was building it. Let the code communicate with you. If it's time to create an abstraction, it will scream that to you, you know? I think that's very true, and I don't like to try to predict the future. I prefer to look into the code and see what I said in the very beginning. If I am repeating the thing, the same thing, twice, it may be okay. If I am repeating the thing more than twice, then the code is starting to scream to me. Abstract it somewhere else. Create a variable where you can reuse the same variable in different places where it's being repeated. So let the code communicate with you, and then the architecture of the code will be guided by the need of the code itself. Let abstractions as close as possible to where they are used. So this is a good example of this, is like in the beginning I was creating custom commands and instead of putting them in the support file, I was putting them directly in the test file because they were just used there anyway. So there was no real need to have this, oh, I want to understand how this works and I have to go to another file and then come back to the file where I use it. If it's used just there, the same for you were, for instance, repeating the same selector over and over. And then you might think, oh, I could create file somewhere else that I can import in my test file and then I can use the selector. But if the selector is only used in that specific file, why not create a variable inside the same file so you don't need to look to another file, to another abstraction to understand what the thing does or what the thing is. Write short commits that tell a story from the moment the code was born until the current moment. So this is exactly what you saw and what you actually can see if you look to the commits that I left here. You can see the evolution of my code through my commit messages. Add type on the left side Custom Command. And then afterwards, I moved the Custom Commands to the right place and I shortened the spec file and so on and so forth. Write short commits. Well, this one has already gone. Use alias so you don't need to repeat selectors. So as I mentioned before, not everyone knows and they think that they should extract the selectors into a variable or a different file. But you can actually use an alias, like I did for the text areas or for the left side text editor. You can use an alias and then you can with Cypress, when you define an alias, you can do Cy.get at the name of your alias and it will get exactly the same element without the need for repeating the selector. Make use of JavaScript's power. So in this case here, I showed you using 4H iterator operators, template literals, to interpolate strings with dynamic data from variables. So, this is what I'm mentioning here with make use of JavaScript powers. Cypress uses JavaScript in the end and you can make good use of it to create very robust and very reliable test suites. And finally, be happy and keep testing. Like, it's very important that we test our applications in an automated fashion because it's very difficult to run regression tasks in a manual way if you have lots of tasks. So, keep testing. And if you do it with Cypress, I'm pretty sure you are going to be testing in a much happier way than if you were using a different test framework. Let's see if we have more questions. If you do have questions, I encourage you to go to the chat here on Zoom or in the Discord channel. If not, we can leave it here and I would love to get your feedback. I'm going to stop sharing my screen and we still have quite a few people in the room here. I was wondering, since the workshop could be of three hours and we have finished in two hours, more or less, are you okay? Should we finish? Should we continue with some Q&A? We have another question here. Could you please go one more time through the Fixtures session? Oh, for sure. Let's share my screen again. Okay and let me put this in here. You mean in the Fixture file? This is the Fixture file. So the Fixture file is a const variable which I am exporting it so I can use the keyword import in my test file. It is an array and this array starts on line one, it ends line 188 and this array has 31 objects and these objects are defined by the opening curly braces and closing curly braces and every one of these objects have the protractor command which is used in the title with the template literals which in this case is browser.get and others element by CSS. Then we have the snippet to migrate, which is the code that we want to type here in the left. Let me close this and close this too. Then we have the migrated snippet, which is the code in the right and then we have the Cypress commands, which is also an array. In this case here, it's an array of one element, but in other cases, we have arrays of two elements or arrays of three elements like this one. It could be as many items as needed depending on how many items we would have to check in the API section here. Let's see what else. That was great. Thank you. Thank you, Denis. The way you shorten your code. Oh, you mean in the test file, not in the… Let's go to the test file, then. Oh, it says that. So it seems that I have answered the question. So in any case, if you want to have a look, this is the final version. Wow, we can actually look directly into VS Code, which I have it open here. So we can actually, before we finish, go back now to VS Code, look into the code as a whole here, and run the tests once again.
20. Test File Setup and Page Objects
I had a test file of 100 lines with five test cases, but now it's less than 55 lines with 31 test cases. I achieved this by extracting scenarios to fixtures. I use the describe function for the Cypress migrator, with a before each to set up the tests. I get the text areas, clear them, and perform actions. I iterate over scenarios and run specific tests. I check assertions and iterate over Cypress commands. I replace certain characters and check API details. I find anchor tags and check their attributes. Page objects can lead to over-testing, as they duplicate steps already tested.
So in the beginning, I had a test file that was, I think, 100 lines of code, and I had only five test cases. Now I have a test file of less than 55 lines, and I have 31 test cases. I was able to achieve that by extracting my scenarios to fixtures, and because I am exporting them, I can import them in my test file with the import keyword.
Then I have my describe function for the Cypress migrator, which receives a callback function. In the callback function I have the before each with all the steps that are needed before every test start, so every test starts in a clean state and an exact same state as the previous so we don't have dependencies between the tests, where in the before each a visit slash, which will concatenate slash with the URL defined as a base URL on my config file. I get the text areas that are inside a div which has both the vias and the side-by-side classes and I give to these text areas an alias of text areas. Then I get the first one, which is the one in the left, the one that I can interact with, the one that I can clear and type. I press select all to select all the content and I clear it. And in some very rare cases, it might not clean everything. And so I will change a dot then, which will receive a callback function. For this callback function, I'll pass the text area from the left side as an argument. And I'll say, if there is some value for this text area, log in the console that you are clearing it again and do clear it again by wrapping the text area and changing a dot clear. And then finally, I give to it an alias of left side editor.
And then I have, I get my text. I use the text test scenarios that I imported from my fixtures on line one. I get them on line 20 and I iterate over each scenario. Because I have an array, I can iterate over each element of that array, basically each object in that array in this specific case. And then for the for each function, I pass a callback function as an argument. This callback function receives as arguments each of the scenarios, meaning each of the objects. Each object will have a protractor command, a snippet to migrate attribute, a migrated snippet, and a cypress commands property, which in this case will be another array. I'm basically this structuring all these properties from the scenario as variables so I can access them inside of this for each context. Context. Alright, and then I am creating a new block inside of this for each, meaning that for each scenario defined in my feature, I will have a specific test, specific test, which I use here to play with others, to print in the Cypress console, my grades protractors, protractor command, command into Cypress, and then I'll get the first command from the list of commands in my array. And then I do a sylab get for the left side editor, and I type the snippet that I got in here. I do a sylab contains for a button that contains the text, Migrate to Cyprus, and I click on it. I get all the text areas. And now instead of getting the one in the right, I want to get… instead of getting the one in the left, I want to get the one in the right. So I say.last to get the last one which is the one in the right. I scroll it into the view so I can see what is in there and I run my assertion that it should have the migrated snippet as the value of that text area. And I also, besides this verification here, I also iterate over each of the Cyprus commands which are in my fixtures, and because I need not only the command, but also the index. In my arrow function I encapsulate them into... I have to wrap them into parentheses. I transform the Cyprus command of that specific index. So in the first iteration, the one on index zero, in the second iteration, the one with index one, and so on and so forth. I replace si by nothing by an empty string. I replace dot by an empty string and I replace parentheses by an empty string. And then I get the API details div. I check that it should contain the command, not the transformed one. So, si.get, si.visit, and blah, blah, blah. And then from these elements here I find all the anchor tags. It might find more than one, so I say, find the one with index 0, or 1, or 2, and so forth, and check that it should have the attribute href on.cypress.io, slash, the transformed command, which is the command without the site, without the dot, and without the parentheses. Let's run the tests once again, so you can see them running. And let me see if there is another question here. Let's see. Okay. Okay, so that was great. Thank you. Thank you, Dennis. The way you shortened code, it was very interesting. Thank you. Could you advise something about pros and cons of page objects? I still like them in large projects since they allow IntelliSense. I surely can't give my opinion, Olga, on page objects. I kind of gave it before, but I'm going to say it again. What's my impression of page objects? Page objects, they were a pattern used in test automation for you to abstract some ideas, so abstract the definition of elements and how you interact with the elements in another module, which could be a class, for instance, that you could instantiate new instances of that class, and then if you have many different tasks or even many different test files that need to reuse those same elements and actions, you wouldn't need to duplicate them all. What is the problem? That's okay, then you are following the drive, don't repeat yourself, right? But there is one very big problem on doing this, and the very big problem is that you are over-testing your application. I'll give you an example. Let's say we want to test, I don't know. Let's say we want to test Typeform, for instance, which is a product that I worked for, for this company, right? So it's a product where you can create very nice forms so you don't collect leads, for instance. I want to test that I can log in. So I would have a test case that would visit the login page of Typeform, it would type my user, it would type my password, it would click in the Login button, and it would wait for the login to happen and assert that I was successfully logged in. Great. Now I need to create another test case, and this test case is that I can create a Typeform. But as a precondition of creating a Typeform, I need to log in. I need to be signed up, I need to be logged in, basically, right? I cannot create a form if I'm not logged in before. So I would have to repeat the steps of logging in so I reach the state where I can now create a form. And then I would click Visit the page that allows me to create a form, or click a button that will direct me to a page. And then I will interact with the fields in that form to create and check that the form was created. But I have already tested logging through the graphical user interface once. So doing all the same steps of signing up, of logging in through the UI for the test of creating a form is a waste. I'm over-testing and passing again through the same, like the application is executing the same lines of code again and again, when they actually have already tested that thing.
21. Optimizing Tasks and Refactoring
If the front end is broken, you can still authenticate through an API call and test other scenarios that don't depend on the front end. Abstractions like page objects are not needed in this case. There are many options for optimizing tasks, such as using app actions or mocking API calls with static files. Cypress also allows running tasks from the command line using the side up exec command. The goals of refactoring include simplicity, maintenance, and code understandability. Understanding HTML, CSS selectors, and the programming language used in the framework is crucial for writing robust code. Component testing is also covered in the speaker's YouTube content.
And if it's passing, you know that it works. If it's not passing, then it's broken. And if it's not working, all your other tests will break because no other test will be able to execute because logging is broken. But let's say that actually what's broken is just something in the front end. Maybe after you fill your username and password with valid information, the button doesn't get enabled, which means that you can't click on it. But the backend logic is just still working, okay? So this means that all your tests would be failing when actually the only feature that is broken is logging. And you are not able to test that you can create a form anymore because you can't pass through the logging page. But let me tell you, if and when what if you had an option to still authenticate and reach the state that you want without going over the UI, creating a mechanism for signing in through an API call and setting some cookies in the browser that would let the browser would think that you are authenticated. And then if the logging is failing because the button is not getting enabled, you would have only the logging test failing and you would still have all the other tests that require the user to be logged in passing if everything else is working. And because you were testing, you are authenticating for the tests where logging in is just a prerequisite. Because you are authenticating via an API call, for instance, instead of going through the UI, there is no need to abstract all the element definitions and actions for the logging page in a page object because they are only used in the logging test file, you know, only one place. And that's just an example. You could have, for instance, in my intermediate course, for instance of Cypress, which is available on Udemy, I teach you how to test a very complex application, GitLab. So we set up a local version of GitLab using Docker. And then besides logging in with GitLab, you can, for instance, I can create an issue or I can close an issue that is already created. Okay? So let's say I want to test, that I want to close an issue that is already created. There are two ways I could do it. There are surely more than two ways, but let's think about it as two ways. One way is I can log in to the UI and there are some preconditions before and after logging in. I have to create a project and then for this project I have to create an issue and then with an issue created I can go there, visit the issue and click close button and check that it was closed. So you can see that there is a lot of steps that I have to go through to reach the state that I want so I can test what I want. My target, which is I can close an issue. I already have a test for logging in so there's no reason I should be logging into the UI again. I already tested creating a project through the UI so I'm over-testing it if I'm doing that again through the UI. I already have a test for creating an issue. I just want to test that I can close it So in my intermediate course of Cypress what I teach you is the side of request command and how you can mix API testing with graphical user interface testing where you can create all the preconditions via API calls which are much faster than doing things through the UI and they give you this, they separate the things. If it's only the front end that is broken but the logic in the back end is still working you can still test other scenarios that don't depend really on the front end. And when you reach that point you don't need all these abstractions like page objects because in the end you're only using these specific selectors and actions in one file and you don't need to extract them through page objects. That's my opinion. And using a request is just one option of optimizing your tasks. There are lots of other options like using app actions, like things that the application exposes to you through the browser API that you can manipulate and create state in your application without going through the UI. In my advanced course, I also teach you how to test the frontend decoupled from the backend where you test from end to end just once to ensure that the connections are well-connected, let's say, but then for the rest, you mock all the API calls with static files and you test all the different scenarios using static files. And you can even then test things that would be difficult to pass them to end, like what happens if the server is down? You can test that using CIDR intercept, for instance, which is something I teach on my advanced course, but you would not be able to test that in an end-to-end way. You cannot say, you know, now put down the API here because I want to test a false scenario, you know? And there are many other ways. Like, I saw once a presentation, a webinar where these guys, they had a web application. Their web application consumed data from a server that was written in Python. So they had a Python file that they could execute to create the whole state in the application so you would, when you start the application, always ready for you to start testing and your tests are very objective. They're very to the point. They don't do everything through the UI. They just go to where they have to go and they do what needs to be done just once and they ensure that everything is working. And Cypress has the side up exec command that allows you to run things in the, as if you were running things in a common line. So you could just run a Python file to do everything for you. It would wait for it to finish and then you would get all these advantages of test optimizations that I'm saying here. We have another question from Olga here, which is what are the goals of refactoring? You say a lot about shortening of code, but nothing, for example, about test maintenance, code simplicity, code should be easy to understand. Well, actually, Olga, I think I have said a lot about simplicity, maintenance. As you can see here, now maintaining this test here is just looking into one specific file. If I have to do some maintenance, I don't have to go anywhere else. If there's something wrong with the test, it's only here. On the other hand, if I want to create a new test case, I just have to add a new entry in my fixture file. So it's very simple. This thing here might not be easiest to understand if you're not really into Cypress and JavaScript, but then my advice is, get deeper into Cypress and into JavaScript, because test automation is writing code and we need to understand how to make good use of the tools that we are using, not only the frameworks, but also the languages so we can write very robust code. So I see a lot of people coming from manual testing into automation without having code programming background. And I highly suggest that you do understand three things before you start automating your tests. The first one is understand HTML, the basic tags that are used in most of the applications that we see on the web. The second one is CSS selectors. You have to understand how to create good selectors to identify uniquely your elements. And the third one is learn the programming language that the framework that you use uses. If you don't know these things and you try to move from manual testing into task automation, you will write poor quality tasks. But if you do know how to use the tools and the language, you make good use of them. So that's my take here. Really great demo. Thanks. There's a lot to learn here. Thank you. Thanks. Do you have any experience with Cypress component testing? If so, does this architecture also apply there or only end-to-end tasks? I do have not a lot of experience with component testing. But let's see here. If we go to my website, and you go to YouTube, you'll be directed to the English content playlist of my YouTube. In the English content playlist of my YouTube, I have this talk here, Cypress component testing, from the task automation talks. This is a content that I have in English about component testing, where I'm testing a React application.
22. Cypress Courses and Documentation
Feel free to visit my website and check out my courses on Cypress. I offer courses in both English and Portuguese, covering different levels and topics. The Cypress documentation is comprehensive and worth exploring. It's important to understand that Cypress has a different architecture and approach compared to other test frameworks. Page objects may not be the best fit for Cypress. When it comes to logging in through the UI, consider using the session command to cache the session and avoid repetitive logins. The Cypress documentation provides more details on this feature.
And yeah, feel free to take a look at this one. My website is Valmir.dev, this one here. I'm going to send it in the chat so you can have access to it. Then in the header, you can go to YouTube. You'll be directed to the English content chat, and then you can find this one about component testing. If you speak Portuguese, you can find in another playlist also one in Portuguese, which is kind of the same as this one, but in Portuguese.
Let's see what else. Why do you use side of gets data? Something should contain command find a equal index and outside of contains. Data something command. Let me see if I understood it. So in here index. data tab command. It's okay I got it. So you are saying that in here instead of doing find a index I should do data test blah blah blah. Comma. And then d command. It's because I'm running two assertions in just one chain of command. The only thing I'm doing here is just checking that the API that they use have the command which is PSY.VISIT open and close parentheses. But from there I can still when I'm in this should I still have access to the PSY.get. So from this one I can chain it to find all the anchors from them I can get the one in the first index, the one in the second index and then verify that the hrf attribute has the correct value. This is why.
Project Olga, downloading your computer. Try what you think it will work. And you see that it will not work because I'm writing two assertions in one chain of commands actually. I hope that makes it clear. About page objects, your login page sample is about misusage of page objects. Not sure. Not sure. The point with page objects is that, that's my impression. My impression is that people learned how to do test automation of graphical user interfaces in a way before Cypress. And then they tried to apply the same concepts to Cypress. When they should not, and they should not because I present very, very, very different than the other test frameworks that we had before. So, what I like to say is that when you are coming to the Cypress world, to the Cypress side of the force, you should actually forget some of the concepts that you have learned, and apply the new ones with the new architecture and with the new power that Cypress gives to you. If you try, it's the same as simply trying to translate to protecting from Cypress. It's not how it should be. There are new things in Cypress land that didn't exist in Selenium, for instance, and if we try to apply the same concepts, they won't work greatly in the Cypress land because Cypress is completely different. It has a completely different architecture and a completely different way of writing tasks. That's my take here. I would disagree with this example of Page Logic is valid. It's okay to disagree. Don't worry, gitlab's testing sounds interesting.
Feel free to go to my website here and go to courses. In courses, you find my Udemy profile. I have courses of Cypress in Portuguese, but I also have the intermediate course in English. The course, the basic one in English here, and the one of visual regression testing with Cypress and Percy. The ones with the US flag are in English. The ones with the Brazilian flag are in Portuguese. So in English here, we see that I have the Advanced, Intermediate, and Basic. In Portuguese, I have more than three. Besides the Basic, Intermediate, and Advanced, I have also Good Practice, Test, and so on. And also the visual regression testing, which is available in both languages. Let's see. What else? Logging page sample is about don't do test preconditions by AUI. There are backend requests and DB requests for this. Yeah, actually, I can deploy UI without backend to some environment just to test that's happening if backend isn't available. Correct. I log in through the UI because the login is with third-party app, WordPress. Do you think this could be possible without the UI? It depends on the APIs that WordPress will provide you with. So it depends really on the authentication provider that you were using. There are, for instance, ways to authenticate with Google without going through the UI if you are using Google as an authentication provider. I don't know about WordPress, but even if you need to go through the UI, then I would recommend you to learn about a session command, which is a functionality that was introduced not very long ago which allows you to cache the session and you can even share this session across the spec files, and this allows you to log in through the UI only once and then cache the session. And when you try to execute the site of logging again, it will ask Cypress, do I already have a session in the cache? Oh yeah, you already have. So, restore the session for me and then you reach the application already logged in without having to go through the UI more than once. So, I would highly recommend going through that. Cypress documentation is great. When I first discovered Cypress in 2018, I kind of, I read it all. Nowadays it would be very difficult to read it all because it's much more dense than it was. But it was already dense. But I felt so impressed about the way you can write tasks with Cypress that I decided to read all I could. So, I spent a few nights reading the documentation and this is why I say that Cypress is very different because I really read all the documentation and I know its details and I know that you can do things differently than you were used to.
23. Cypress for UI and API Testing
I recommend using Cypress for both end-to-end testing and API testing. You can also mix them to create optimized tests. I suggest reading the blog post by Gleb on using app actions instead of page objects. Understanding HTML, CSS, and JavaScript is important before diving deeper into task automation. Codecademy.com offers free courses on these topics. Consider joining the ambassadors group on Discord for assistance. Cypress runs in the same browser as the application, providing access to all its features. Check out the YouTube channel for more resources.
August, Cythout contains... Well, feel free to do what you're mentioning there, Augh, and work my project, create a PR and let's see if it works. I would be glad to review your code and have it merged if it works great and if it's simpler than what I showed you.
Marcos, would you recommend doing API tasks with UI tasks or should they be separate? I recommend both. I recommend you run your graphical user interface tasks with Cypress. You can also run your API tasks with Cypress with the site that requests. There's even a nice plugin that was created by another Cypress ambassador called Philipp, which is the Cypress plugin API, which allows you to see what is going on in the API. When you are running a site request, you don't see anything here on the right. This plugin allows you to see which verb was used, what was the URL, what was the status of the request, and you can see the response, the headers and cookies and everything. I actually have done two live codings on my YouTube channel in Portuguese unfortunately for you that don't speak Portuguese about this plugin here. So I recommend using Cypress both for end-to-end testing, for API testing. You can use it for component testing as well. And if you do use for API testing and you use for UI testing, you can also mix them both to create what I call optimized tests, where you do all the preconditions through API calls. So you don't waste time doing everything through the UI. You don't waste time over testing your app. And then you do through the UI only what you need to do through the UI. So I highly recommend doing that, Marcus. Hope that answered your question. Any more questions? We still have 25 minutes. So if you have more questions, we can keep going for a little bit more. If not, we can wrap it up. Up to you. What do you say?
Thank you. Thank you, Clary. Thank you, everyone who attended the workshop. I hope it was helpful information. And if you didn't understand it all, don't worry. The recording will be available soon so you can watch it again. And because it's been recorded, you not only will be able to watch it again, but you will be able to pause if you are in doubts of something, and then you can go back and forth. So if you want to watch with a faster speed, you can do lower speed as well. So wherever you prefer when the recording is available.
Let's see, there is another one from Marcus. Do you know any good materials regarding app actions? I know this can be good alternatives to page reviews. There is the famous blog post from Gleb about Do not use page objects with Cyprus, which he wrote in stop using page objects and start using app actions. I think this one is great. It's dense, very dense. I have read this document here a while ago, and I even had helped one of my mentees to translate it to Portuguese. So there is even a Portuguese version of this one here. I highly recommend reading this. And when you read this, you will understand why I do understand that we don't need page objects anymore when you use Cyprus, because we can make use of all the power that Cyprus gives you out of the box without you having to install any extra thing. And even if you do, Cyprus has a huge ecosystem of plugins that you can add to it to do more stuff. So, yeah, I highly recommend reading this one. I'll always say Labs Post is really hard to understand. Then I think it really depends on maybe about the amount of knowledge you have both about Cyprus and about JavaScript. Again, as I said before, before trying to dive deeper into task automation, we have to know the basics. We have to know HTML, we have to know selectors. And we have to know the language, the framework we are using. So if you do know these three things, I would say that understanding this kind of thing wouldn't be that complex. This is why in my YouTube channel, I created playlists called JavaScript for QA's and CSS Selectors for QA's, because I think they are very important concepts that people that are moving from manual task automation, they don't know. So I say to them, go to these videos, they are free first, before trying to automate, because there is where I give you the basics, the foundations. Unfortunately, the content is all in Portuguese. So if you don't speak Portuguese, then I would recommend you finding a very good source that I would recommend is Codecademy. Codecademy.com, they have lots of free courses and they have courses about HTML, CSS, JavaScript, everything that you might imagine. Many of their courses are free, so I highly recommend. Understand the technologies that your framework uses before trying to use the framework itself. That's my recommendation. Is there something easier? I would recommend follow the ambassadors group on Discord. You can ask questions there, there are many more people that could help you out. When I read this blog post, this is when I understood it, so to me it's not that difficult. It's dense, it takes time, but it's not that difficult. I highly recommend looking for other resources as well. I also struggle to understand his explanation of this, even though I love his content, thanks for the recommendation. The point is that sometimes you can make your application expose some things to the browser, and that's one of the powers of Cypress. In Cypress, it runs in the same browser as your application is running, different than Selenium, that has a web driver server in the middle. So you have to write tasks that send commands to the web driver that sends commands to the browser to automate things. Cypress is running together with the application in the browser, which means that you have access to everything that the application itself has. And so you can, for instance, grab the window object of the… Well, actually I have… I have content on my YouTube, also in the English channel. Let me see if I can… Let me just hide this floating panel here. So in here I have also, as I said, some interviews. One of them is with Filip, another Cypress ambassador.
24. Tester Experiences and Recommendations
Filip and I discussed the need for Page Objects in Cypress. I recommend watching Filip's talk on Full Circle Testing With Cypress. Cypress ambassador Philip offers a free course on Advanced Cypress. Cypress officially recommends not using page objects, but it depends on your needs. Over-testing your application can be a smell. Consider better ways of testing without repeating UI interactions. Maria found an article on ApliTools blog. Thank you all for attending the workshop. The recording will be available soon. I enjoyed giving the workshop and hope you learned something new. See you next year at Test.js Summit.
Stories and experiences of a tester and Cypress ambassador. I highly recommend you watching this video here, where in a moment, in a specific moment in the video, Filip and I, we discussed this, the need of Page Objects and how you can still achieve great things with Cypress. And recently I watched a talk, actually, it was on Test.js Summit, I watched history, I watched this talk here from Filip, Full Circle Testing With Cypress, Filip, create Test.js Summit 2022. He doesn't mention necessarily Page Objects, but it's a content that I highly recommend because you understand the kinds of things you can do with Cypress that maybe would help you understand why Page Objects would not be that necessary when we are talking about Cypress Tests.
Let's see what else. Have you used Actions in any of your projects? Yes, I did in some projects, especially at work, and so they are not like public repositories. I can show you them. But there is one course in Test Automation University. Automation University. Automation. Automation University. There's a course called Advanced Cypress, something. Let's see. Cypress. This course here, it's for free from Philip, Cypress ambassador, learn advanced concepts in Cypress. He teaches very briefly actions, if I'm not mistaken, in this course, highly recommended as well.
Maria, from what I read, it's just official Cypress recommendations to not use page objects, but it just depends on what you want to do and how you write tasks. People do use page objects with Cypress. I'm not telling you you should not use it. If I don't use it, I don't recommend using it because I think there are different ways and better ways of writing Cypress tasks. But I don't discourage you. If you think that works for you, it's fine. What I recommend is that you don't overuse, over test your application. If you do over test, then it's a smell that, let's say it's a smell that you could improve something in your test suite. You could optimize some state creation in your application somehow. And then I think you should try to address those. Also, from my understanding, when they are in one place, maybe I'm doing something wrong, of course. Yeah, again, Maria, it's what I said. The thing is that, if you are having to store selectors and actions for your page in a different file because you are using the same selectors over and over in different test files or in different test cases, there is a smell that you are over-testing your application. So you should think about better ways of testing your application without having to go through the user interface again and again for things that you have already tested once. That's the point. Yeah, Maria also found an article on ApliTools blog. I like the article about the question. So thanks for sharing that, Maria. Is there any other question before we finish this? I'm going to stop sharing so we can see some and see what's going on in here. We put this in here. Let me open the chat once again. It seems that we have no more questions so I want to thank you everyone for being here today. If you haven't had the chance to watch it all, don't worry. It's been recorded. It's going to be available soon to you. I pretty much liked giving these workshops to all of you. I hope you liked it too and I hope you learned at least one new thing that will improve the way you write your tasks. And yeah, thanks a lot. See you next year, probably on Test.js Summit. I'll be there for sure and I hope you are there as well.