Lessons from E2E Testing Web Applications with Avindra Fernando

This video features Avindra Fernando at DjangoCon US 2024 in Durham, North Carolina, USA.

Lessons from E2E Testing Web Applications with Avindra Fernando
0:43:22
Published December 6, 2024
167 views

Lots of companies are investing in end to end testing to release high quality software and remain competitive in today’s market. Now with libraries like Cypress and Playwright, end to end testing web applications have become very intuitive and a whole lot of fun.

Over the years, after working with these tools, there are many lessons that I have learned the hard way. These valuable lessons taught me how to write robust and reliable tests that vastly improved the quality of my applications.

In this session, let's discuss best practices that I found useful to write E2E tests. These guidelines will enable you to release high quality software to your clients.

This talk was presented at: https://2024.djangocon.us/talks/lessons-from-e2e-testing-web-applications/

LINKS:
Follow Avindra Fernando 👇
On X: https://x.com/avindrafernando
Website: https://taprobaneconsulting.tech/

Follow DjangoCon US 👇
https://fosstodon.org/@djangocon
https://x.com/djangocon

Follow DEFNA 👇
https://www.defna.org/

Video production by Confreaks
Follow Confreaks 👇
https://confreaks.com
https://x.com/confreaks

Transcript

7,034 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:20

Thank you for being here and uh thank you for attending so you can gather a lot of knowledge from here and take it back to your teams. And uh help spread the knowledge. Lessons from end-to-end testing web applications. I first want all of you to meet this uh team right here. They seem like they're very happy. So looking looking at a screen, very happy. So turns out they got the requirements for a brand new project. Isn't that one of the most exciting times? Right? Not only they got the requirements, they have the freedom to select the technologies, their architecture, that's like the best place to be at, right? So they were super uh happy and what did they do? They started delivering value to their customers from day one. So this small app

1:05

started growing, growing and growing, and there were a bunch of very happy developers. Time passed by, more and more feature requests started coming in, they started adding value, but at one point they started hitting a scenario like this. Where a couple of bugs got logged, they fixed it, a bug they fixed two weeks ago appeared again. They fixed two more, another one appeared again. Does this sound familiar? Have we all been here? Because I have. If you haven't, well wait a few more years. Alright, so the same team who were very happy now start feeling like this. Start feeling a little frustrated once they couldn't keep up with the volume of the bugs coming in, and when they start fixing an old bug, that old bug

1:58

reappearing again after a couple tries. Who am I? My name is Avindra Fernando. I'm a software consultant based in Kansas City. I help clients transform their web applications by architecting their web applications. I help with testing strategies and I also help migrate their older tech stacks into more modern tech stacks. In addition to all of this, I conduct sessions like this. I conduct workshops at companies. I also do mentoring sessions directly through like one-on-one mentoring, or I do like code reviews. and provide guidance to teams for their projects that they have going on.

2:44

So if any of you have any projects that you would like to discuss with me, I would love to have a chat after our talk here. And that's my handle on X , formerly known as Twitter. So I'll be posting these slides over there, and also we can follow along. as um I would I would be happy if y'all can start sharing the the session the contents of the talk on X as well. So why is it important for us to fix those bugs right away? Many years ago, when we developed products, when we developed software, our clients were located right next to us. So it was not very challenging for us to deliver a great experience to our clients because we were located in one city.

3:34

Most of the clients were just around us. However, today that's not the case. We could be located here in North Carolina, but our clients could be all the way in Australia. all the way in Japan. So it's very important for us to deliver a great experience to all of our clients across the world so they can start using our software and be happy with it. However, one of the challenges is latency, right? How do we deliver our sites faster to our clients? Unfortunately, with the cloud providers, this problem has been solved for the most part. We have now been able to co-locate these cloud providers across the world so that our site can now be delivered to these different clients of ours at different times.

4:20

So that problem is solved. However, we still spend a lot of time trying to figure out how do we efficiently address the bugs coming into our software. Because the more time we spent on fixing issues, the less time we get to fix uh l the less time we get to address new features or re-architect our software. So we can make the systems better, right? And the reason why it's very important to companies is the more time you spent on bug fixing or fixing issues, You're gonna be spending a lot of dollars. So every company knows this. This is a given in our industry, being in the software development world That you're gonna have to spend a lot of money to maintain your software.

5:06

However, the question is how much and what's the return on investment so that you can keep delivering a great value to your customers while maintaining a great product and while keeping all of your team very satisfied so you can deliver that great experience. So a solution that we added to the mix is to say, let's just start doing end-to-end testing. So full-blown testing of our application. From our UI to our middle tier to all the way to the database, right? This is tests our software like an it like an end user would. So that's what we traditionally know end-to-end testing as. However, the solutions given to us were pretty challenging.

5:53

Right? They were harder to set up. They took a long time to get give you feedback. I've seen cases many years ago I would work on a feature and I would not hear back from the results of the end-to-end testing even after a week. Have any of you been in that boat? Where you you would have moved on to a different feature and now you have to context switch and figure out, okay, where was I? Because you only got the feedback after a week. So these were the tools that we were using in the past. Fortunately, this has gotten better. When we thought this was a problem we were not able to solve, Thankfully, two software today that I highly recommend came along and said, don't worry, we got your back. Namely

6:38

Cyprus and Playwright. How many of you have heard of these two tools? Okay, a lot of the room. That's awesome. How many of you are using these, either one of these? A good chunk. That's that's that's awesome. Alright, so then this talk's gonna be perfect uh for the entire room. On the left, we have Cyprus. Their motto is automated testing by providing a seamless software, and a similar idea Is what Playwright recommends as well. So Playwright was formerly known as Puppeteer, and they formally renamed and rebranded as Playwright, as we know it today. So here's an example of how Cypress

7:25

works. So once you fire up the Cypress GUI tool, we can see on our right hand side We got our URL, that's gonna be your page, and then we have what browser you're gonna be firing up, and then we also have the resolution. And then the experience of what happens during a test is what's going to be shown there. So once you are debugging your test, you can go to the panel on the left hand side, you can hover over each step. The first step could say visit the page. The second step could be, you know, click a button, enter some information, and you can see at each step what happens at what level. So it's a it's an amazing tool that we can interact with to get feedback. Playwright has a similar UI, a very similar

8:12

You have your site loaded up on your right hand side, and on your left hand side, you have all the tests you're going to be running. And for each test, they're going to show you all the steps. that you can hover over at each step. And then finally, Playwright also gives you the ability to see the source code while you're running these tests. While working with these two software, you know, I got super excited many years ago to get started with them and I've worked extensively with Cypress and Playwright. However, I wish someone told me, hey, don't try these patterns, because you're going to be spending a lot of time. debugging the test, right? So my goal here is to share with all of you the problems

8:57

or the issues that I ran into that I wish someone else told me. So on a more positive note, I like to think of these as lessons learned, right? How do what are the some of the things that I ran into while interacting with Products like Cypress or Playwright that I knew I wish I knew before I got started. Alright, who's ready to dive in? All right, awesome. The first thing is the first lesson is time-based weights. So let's look at a code here. So we uh one thing I want to point out is if you look at whenever you're looking at a Cypress example, you'll see the Psi logo. on your top right and whenever you're looking at a playwright example uh

9:42

you'll see the playwright logo on the top right so that's who uh how you'll know which example that I'm talking about. So what I'm going to be doing is I'll present the same concept on how you do it in Cyprus and how you do it in Playwright for comparison. So here, uh what do we see? We have two tests here The first test is going to be talking about you know displaying a list of to-dos. The second test is talking about loading up a bunch of records. Now, if you notice here, we see These statements right here. So the first one line number three says psi dot wait for 1000 milliseconds and then line number nine is another 5000 milliseconds and

10:28

another 7,500 milliseconds. What's going on over here? Have we seen this pattern before? Have we seen it with this software? Can anyone tell me what this is? Selenium. So a lot of people have used it. Yep. So when we came from the Selenium world, that was the only way to do it. We had to make sure our system goes to sleep, or we had to wait for an arbitrary amount of seconds for an action to complete. Because what you would do is you would have selenium open up the browser and you don't know when it will complete. So you have to wait for a few seconds to give it time for it to open the browser. You have to then type the address that you're going to go to to visit your application.

11:16

You still have to give time because you don't know, right, when things would complete. And that's due to the nature of Selenium. Selenium was essentially an external application talking to your application through a browser. So it had no knowledge of when your application loaded, when your application completed uh data call, we have no idea, right? So there was no other way than saying, hey, I'm gonna wait for a few seconds. I know when I click this button, It's going to make a call to an API to fetch some data and the data will potentially come back in a few seconds. So I'm just going to wait for a couple of seconds. But what happened with this approach is we were wasting a lot of time. What if there was a better way? What if there was a better way for me to know?

12:03

Okay, I'm gonna make a request. I'm gonna click this button, it's gonna make a request to fetch a list of to-dos. And The moment the list of to-dos come back, what if that testing software could notify me? So this was a a pattern that we kind of carried over from our Selenium days. on to even when we were working with Cyprus. And I've been guilty of that. Right? So who else is has tried it in in the Cyprus world? Alright, so we have a better way. Here's what we could do instead. So the reason why we can do this is Cyprus is not exactly an external application. Firing up your application. What Cyprus is doing, Cyprus has a browser built in.

12:48

So when you fire up Cyprus, it fires up its own browser, and through that browser, it can visit your product. So what it means is Cypress knows exactly when something completes within your application. For example, in the to-dos example, that if I visit my a site and it's gonna load up a list of to-dos I can I know that it made a call to arrest endpoint call to-dos. So what I can tell Cypress is, hey, keep track of this request. And when it completes, you can resume. So here you instruct Cypress by saying, you know, psi dot intercept, it's going to be a GET request to an endpoint call to dos. And then I'm going to give it an alias so I can refer to that later.

13:34

Right. So that's the step of me instructing Cyprus to say, keep track of it I know when I visit my homepage, this request is going to fire, so just keep track of it. And then in line number four, now I can be more specific. I don't have to say just wait for an arbitrary amount of seconds. Right? Wait for that exact call that you're tracking to complete. So now you can say sci. wait and you alias it You refer to it using the at symbol. So you can say at to-dos. So now Cypress knows, okay, a call was made to an endpoint call to dos. The result came back. I can resume with the data. Right. So it's going to take exactly the amount of seconds. So in one time it could take a couple of seconds, the other time it could take maybe three, four seconds because there was a delay.

14:19

But regardless, you're not going to be wasting a whole lot of time. You can proceed the moment you get the results back. If you have multiple requests, you can track of them, you can keep track of them in batches. So you can say, you know, sci-out intercept, let's track the to-do 's endpoint, let's track the user's endpoint, let's track the comments endpoint. And then you can have all of them in an array and say, hey, keep track of the to-dos, keep track of the users, keep track of the comments, before you can proceed. In playwright, we have a similar pattern. Uh we don't have in playwright we don't have a specific weight function. That can you can instruct and say wait for

15:05

this amount of seconds. Instead, it provides you with these utils. The first one's called wait for URL. So that essentially says, let's say you have a scenario where you're clicking on a button And it takes you, it navigates your way to a different page. And what you want to validate is, did it navigate away and did the new page load So you can use a playwright util wait for URL in this scenario to say, okay, I am now navigating away to a new site and I'm getting the data back. from that new site. So you can use wait for URL. If you're tracking REST endpoints, you can use wait for request. You can say hey notify me when this request is made. If you're tracking a response, you can say

15:52

wait for response. So you can use that util as well. And there's plenty of other utils that you can use when it comes to tracking Network requests which are firing from your application and coming into your application. Here's a scenario where in s in Playwright you have A click event. So you're gonna find a link and you're gonna be clicking on that, and then you're gonna be checking, hey, did this link trigger a new page to load? And when it loaded, I want to assert that it loaded So what you can do in Playwright is you can say page. wait for URL, and then you give the path of the URL that you were expecting your test. So you can track, hey, I visit

16:38

I visited this new URL. Similarly, you can track your requests and responses. So here, if you are building REST endpoints using the Django framework, you can track Those requests as well in your end-to-end test. You can say, I do see in this page, I'm expecting requests to these resence points to proceed, and I I get back those responses over there. Alright, so the second thing is understanding the async nature of JavaScript. How many of you have touched JavaScript or are using JavaScript on a daily basis? Quite a bit. If you haven't, you're fortunate. JavaScript's tricky, right? There's a reason why people

17:25

either love it or don't like it as much, right? It's because you have to understand the async nature of JavaScript. And in in addition to that complexity, you have to also understand the async nature of Cyprus. So if you read this code here, so let's say what I in Cyprus, what I want to do is I want to track a title. So I want to verify that a title attribute is present in my site, and then I'm going to be expecting that title to contain a certain value. It's a perfectly reasonable test. So I'm going to be writing the first two lines. And then later on I can check do I also have I'm going to track some links and then I check can I click on the first link that I distract?

18:11

Right? So when you read this sequentially, it makes sense. However, it does not make sense for Cyprus. It will not work. Okay. So what about async awaits, right? Because JavaScript is asynchronous. Can we add async await keywords onto the commands that I showed so it can make sure that it waits for the right time? Unfortunately not. Just like how Kevin's disappointed, I was disappointed too. So let's look at a more uh deep example here. So I'm going to want to uh display a list of to-dos and make sure it got displayed. So the first thing I'm gonna do is I'm gonna have a temporary variable

18:57

set to undefined and then I'm going to visit my page and I know when I visit my page I'm expecting a request to go out and populate the list of to-dos. So then once the list got populated, then I can say, you know, psi. get li to dos, and I have a promise. And then I whatever to-dos that came back, I'm going to set it to that variable. And then later on, I'm going to check if that variable has a value. Is it equal to the value I'm looking for? But if I run this code in Cyprus, can anyone take a guess on what I will what I will get? Assuming that you know the the call to the to-do 's endpoint completed, it gave me back good data, all of that's good.

19:45

Any any guesses? Undefined. Yeah, so because of undefined, what you're gonna be getting is no to do's phone every single time. And the reason is Cypress doesn't run your code as it reads it. It re it reads all of it and then compiles it and then runs it uh in a different way. Right. So that's why it's very important to understand the async nature of Cypress. So here, this example is not gonna work, is because line 10 through 14 is going to run before lines six through eight every single time and this is why some people do not like JavaScript.

20:34

All right, so how do you make it work? You just have to move all that code that I showed in the if statement inside the promise, right? That's how Cypress is gonna know, okay, now this is an action which is gonna complete happen later. Asynchronously, so I want to make sure that I track it once it's complete. So take that code, you move it inside your dot then, and now it's gonna start working because it has context on what the value of the to-dos were when the results came back. Another easier way in Cyprus to make this happen is by using aliases that Cyprus provides So instead of declaring these variables, what you would do is you can give aliases to your selectors. So there's a selector in line number one, psi

21:21

dot get my selector, and I've given it an alias called myElement Now this way, if you do it this way, Cypress is gonna know right away, even if you refer to my element later, that it knows, okay, this is something that I need to keep track of, so I'm gonna pay attention to it right so this this way it's gonna know even if you try to access it like that so in line number seven once you access the alias there's not gonna be any problems So I encourage if you're trying to get into Cyprus, understand the nat the async nature of it. And once you get a hang of it, it should be smooth sailing from there. But it's going to take a couple of tries to get it right. recommend highly recommend aliases when using cypress.

22:06

Now what about async awaits in player i Well fortunately it's gonna work. So now it playwright code is gonna look more like JavaScript. It's not written in JavaScript, but it's not gonna work like JavaScript. It's playwright code is going to work exactly like JavaScript. So it's a little more readable in my opinion. I know it's it seems like a lot more code. But you can follow it sequentially because what you have to do is you have to visualize when whenever there's a wait command, that request will have to be completed before it moves on to the next step. So here your test is going to have a function which is an async callback, and then you can await.

22:52

You can say, hey, await page to-dos So that will make sure it'll wait until everything's loaded and then you can check if your page has a title call to-dos. In it. And sequentially, it'll playwright will root uh read all of these sequentially, so you'll get the results that you're expecting. The second test here is showing you're visiting a to-dos page And then you're finding a button which is of the name logout. And then you're expecting, once you log out, then your expectation is, hey, now do I see the login button? And then you can assert it that way. All you have to do is make sure to add those async away keywords so you're gonna know how

23:38

to uh address that. Alright, so custom commands. So we have a login logic where what we want to do when we do end-to-end testing is to ensure Can you visit the page? You're gonna be logging into the page and running the tests. And you're gonna be doing this a lot over and over again, right? So it's gonna be painful to repeat these logic in every single one of your tests. It's not very effective. In Cyprus, we can use something called Cypress commands. So here, what you can do is you can add a custom command and think of it as a function or a method that you're essentially saying I'm going to be systematically tracking the username and the password, and then I'm going to be logging in through an API, get

24:24

the cookies back, right? So you can retrieve the CSRF token. uh from Django, you can retrieve any other cookies that you're using for authentication, and then you can store it in the Cypress session. Um so when you use it, you can use login by API as a moderator. But I would caution Commands are great, but just don't overuse them, right? So here is a command example, log in and visit profile, log in and do something else. that breaks isolation. What you want to do is something like this. You have a command to log in and then you can visit the page. Keep unrelated logic separate in your end-to-end test

25:10

as well So here is an example in playwright, the same logic. What you can do is you can have a before each hook Essentially, it adds a before each method, and then you can visit the page, you can find the login button, you can click and fill in the information for your and You log in email and you log in password and then uh you can wait for the URL that you're looking for Also, you can use fixtures in Playwright. Essentially the same idea. These are think of them as Cypress commands. It's a custom command that you can configure. Which will run before every single test.

25:55

So once you want to use it, you can get that in context and start using it in each one of your tests. Alright, so the next one is only using. So when you're using your end-to-end testing framework, if you only rely on the UI. Things are gonna be slow. Because what we know is we know the UI is gonna be calling some rest endpoint to log in and then getting the cookies back and all you have to do is set those cookies, right? So in theory All you really have to do is use a UI to log in the very first time and then validate that workflow. But for every other test, what if you can programmatically log in and save your cookies? And you should be able to do that.

26:42

So here is a controlled example in Cyprus. You have login by API. You're gonna be getting your you can get your email and the password that you're gonna be using to log into the application for your tests. And then you can throw errors essentially if they're not found. And then you can wrap it in uh a side dot session. By doing this, when your test runs repeatedly, Cypress is not going to try to hit your APIs over and over again and bring the system down, right? So you can protect your j uh the REST APIs written in Django. What you do is you for the very first time when the tests are run, it's gonna hit it, it's gonna get the tokens that it needs, it's going to save it in its session. So every other time it runs, it's going to return as long as the username and the password are the same, it knows it's the same user.

27:32

It's not going to overwhelm your rest endpoint So which is which is great. Keeps it in in cash. Same concept when it comes to playwright You can make a request to your login endpoints by passing in your email and your password, and then you can get the cookies back and you set the cookie information. To the storage state. So every time Playwright runs, it's able to capture this information and store it and use it in its tests. You also have the same concept. You can use it for logging out of the application. You can use it to create a user.

28:17

You can use it to delete a user. You can use it to like give permissions based on the role. Some some workflows could be admin roles, right? So we know in Django applications we have an admin site and a site for our regular users. So we would want to validate each one individually. And finally, once you're done with it, you can clean up your database too for your test in instances. The selectors. When it comes to selectors, Cyprus recommends being very specific. And there's a reason for that. Because if you give very generic selectors, it's Possible that you're gonna be running into collisions and when you run into collisions it's not gonna work well

29:03

But my recommendation is to use an extension to Cyprus called testing library slash type Cyprus And the reason why I recommend this library is because one of the philosophies behind the library. The library's one of the core philosophies is to ensure you test your application like an end user. So for example, if we go back to the selectors that Cypress recommended, the last one, although it's very specific, the flaw there is that a user would never see someone call something called a data psi attribute. That's not accessible. That's not something is going to be visible to your users. What the users are going to be seeing is accessible element, right? They're going to be seeing roles. They're going to be seeing placeholders. That's what they can interact with.

29:49

So why not have your testing application also do the testing the same way? So this library is great for that because it provides all of these utilities behind the scenes. So you can have find by role, find by label text, find by place roller text, find by text. So look for things that the users can see and interact with when you write your tests. Finally, if all else fails as as a backup, you can use test ID, but personal recommendation, once you start using the top few, and if you're writing accessible code. You're not gonna want to you're not gonna have the need to use test IDs anymore because you should be covering a lot of scenarios using your accessible selectors that you've done previously

30:38

Yeah, so now your code in Cypress is gonna look much much pretty. It's gonna look uh side find by roll a button. I'm looking for a button. The name of the button is save comment. And the button should be enabled, and if it's enabled, I'm going to click on it. The next step is I'm going to look for a dialogue, and within that dialogue, I'm going to look for another button, and the button's name is confirm. And then I can also look for a text, just a text on the entire page called heroes. And I'm just gonna check here, does that text exist, right? So here's another great tool called Testing Playground. What you can do is you can visit your site and it's gonna fire it up.

31:24

And here, you know, it's gonna show you the HTML of the site and then how it's gonna look as you render. But then when you hover over each of these elements, it's gonna give you the the recommended selector. So I'm hovering over this email field right now and you can see right here, um pay attention to the password right below the password field. it's going to show you what the recommended selector would be. So it's saying get by roll text box and the name is email address. So pretty cool tool that I used to use when it comes to selecting what selectors should you be writing to interact with your elements. Now playwright said that sounds brilliant, right? Of way of writing it.

32:09

So we're not gonna want you to bring in a third-party library to integrate with our systems. We're gonna make that type of selectors by default. So Playwright worked very closely with the authors of the testing library, and they were able to provide out of the box selectors which look very accessibility uh accessible friendly, right? So playwright will have page. getby roll and you're looking for a button. And name is submit, and then there's another example here. You're gonna look in for a list item, you check within that list item, am I getting the second item in that list? So you can you can do a filter and you can check, you know, do I have the text text call product too? And then I'm gonna check

32:56

There's a button, I'm gonna add that item to my cart, right? So you can simulate this workflow based on the selectors that playwright's gonna give you. So the problem with the type of locators mentioned in the very top is that they're very brittle. Meaning it's looking for a specific structure, right? You can see here I'm looking for a button, which has a button icon, which has a my button icon. Now if the developers go and refactor the code, which they should be able to do without impacting the end end user's workflow, which we do all the time, right? We have a lot of tech depth, we go and refactor code. All of those selectors are gonna break and all of your tests is gonna start failing. Instead, rely on things that people can interact with.

33:43

Think of it when you're writing your tests, approach them from The perspective of what the users can see and interact with. Here's a fun one. When you're writing your tests, so here there's a test which is you know visiting a Add to-do form. So first test is going to visit a a to-do. The second test is going to be adding an item. The third test is going to be adding a priority. The fourth test is going to be submitting it. Once you look at this, it seems like it's great. But what happens is if I comment out three of those tests and only try to rely on one of them, it's not going to run. Because it relies on the context of the previous tests. So what we have not done here is we've not isolated our tests.

34:31

The tests failed. When you approach end-to-end tests, there's no reason to think of them as unit tests. We don't have to think that granular when it comes to end-to-end tests. What we have to do is think of them as you're testing an entire workflow, just like a user would do. So in the end-to-end test, it's recommended that we do all those steps that I showed previously in one one big giant step. So I visit the page. I go to my add form and I'm going to start typing the first item. I'm going to add a priority I'm gonna fill the form and then click the form submit. So now this test is all in one go and it can be run over and over again in isolation. It's going to either fail or pass, right?

35:17

It's not going to be determining the destiny of another test If you do have shared code that you're going to be wanting to run over and over again in different tests, you can use a hook called before each. So here, what I can do is I can visit the add-to-do form and I can add the to-do item And then I can add the priority because I know either I'm going to want to submit that information or I'm going to want to display an error when the information is missing. So in my second test, what I'm doing is I'm going to the prior priority field and I'm clearing it out and I want to test, hey Can I still submit if the information is missing?

36:03

So in that case, I shouldn't be able to submit. So that's why when I run line number 10, I'm going to want to expect an error saying, Hey, you can't submit the form without a priority there, right? So I can then assert and say I can look for the errors on the screen. I can then assert and say, do I have the errors that I'm looking for? Alright, so similar example in playwrights, same exact idea. The difference is you'll see the before each hook at the very top here. Same idea. You can visit the page, you can add a to-do, you can add a priority, and then you can have your other tests, which are either going to be going and deleting something, and trying to submit or modifying the values and trying to submit

36:48

or just submit as it is. You can split everything based on how you want to do it. But the idea here is shared code. goes on to hooks called before each and after each in playwright or either Cypress All right, let's talk about network fluctuations. We we briefly talked about how to track network requests. Using the intercept function. So in this example, you can track all of that in line number two. We're gonna be tracking um psi. intercept the get request to get the to-dos back. But what happens if I don't have internet? Right? Are all my tests

37:34

gonna fail? And that's a very valid scenario. In this audience here, I met people who work for different domains. There is legitimately use cases to have your apps work when there's little to no internet. And how are you going to do that? It's a big challenge. So there's a couple things you can do in Cypress or Playwright. You can create essentially mocks. The terms that they give in Cypress is called stubs. So the only difference here is that you'll see I'm still tracking the request, but I have a third argument to the function. What it's gonna do is gonna say it's gonna track the to-do 's endpoint, and then it's not gonna make the real call to the endpoint. It's gonna say, all right, when I encounter this. Call to this rest

38:19

endpoint, I'm going to return back an empty object in this example. You can return back the mock data that you desire for that test. So you can configure uh all of that that way. So here uh it's more saying where do I go find the mock data? So you can use fixtures in Cypress. The third argument here is a reference to where the fixtures file can be found. The fixtures file is essentially a copy of mock data pertaining to that specific test Similar concept can be applied in play right in playwright. You can keep track of requests through page. route and then you can essentially feed in any of

39:07

a mocked response through route. fulfill and you pass in the JSON object Now with Playwright you can do something really really cool. You can mix and match. You can get data which is already mocked, and then you can modify them on the fly. So for example, this one line number Up until line number six, we're getting the mock data. And then what we're doing is we're inserting dynamic data, dynamic mock data. In line number seven. I'm just pushing a new element to the array and then now I'm gonna check, you know, is everything gonna be available? So you can you can do something really cool with Playwright by modifying the API responses. that you want to use.

39:55

Alright, so here's my two cents A lot of people ask me that what should we use? Should we hit real endpoint? Should we hit mocked data? And there's valid scenarios for both. My recommendation is if you're doing smoke tests, if you're doing critical workflows, ensure you're hitting live endpoints. Because that's how you're going to guarantee availability. That's how you're going to guarantee correctness. When it comes to your critical workflows. But all the other workflows surrounding that, you can you should be able to use mock data. Maybe you have endpoints that are Almost static or they rarely change, right? So there is no reason to over and over again hit those endpoints so you can let your back end breathe a little bit. So try to use a mix and match approach when you're designing your testing strategies.

40:42

This one, only histing, happy path. Alright, let's go write tests. You know, I can log in successfully. Great, I'm gonna go home, right? No, that's not enough. We know that. If you're gonna test your application with valid credentials, you should also have a test. Which checks what happens when the user enters invalid information. It should fail. Another key recommendation is keep your dependencies up to date. So in Cyprus, we are right now at uh version 13. 14. x. And in Playwright, it's 1. 47. x. So try to keep these software up to date if you're using them, and you can get the best features and the best uh experience out of

41:30

all of that. There's also pages on how to best use, along with the lessons that I shared, they recommend more lessons. So you can visit the Cyprus best practices page or the Playwright Best Practices page and get a lot more information. And most importantly, I want to tell all of you, this should be a fun experience. I promise you, it's not the Selenium Days. It's gotten much better, so everyone should be excited to get it back into writing into NTES and having a great experience with it. I want to leave everyone with a quote that's dear to my heart. Always be learning, always be uh looking to gain more knowledge. So stay hungry, stay foolish by Steve Jobs.

42:17

And again, my name is Avindra Fernando. I'm a software consultant based in Kansas City. I help clients transform their web applications by migrating their older tech stacks into more modern tech stacks and I help set up their testing strategies. I also conduct workshops. and mentoring sessions to their team. So if anyone wants to have a chat, I would love to. And here's my site. And if anyone has any questions, I would love to take questions. I think we have a a minute minute or so

Questions this talk answers

How do I replace fixed waits in Cypress and Playwright tests?

In Cypress, intercept the relevant request, give it an alias, and wait for that request to finish. In Playwright, use utilities such as `waitForURL`, `waitForRequest`, or `waitForResponse` so the test proceeds when the expected event occurs instead of after an arbitrary delay.

Discussed at 12:48

How should I handle asynchronous code in Cypress and Playwright tests?

Cypress commands are queued, so put dependent work inside `.then()` or use aliases rather than expecting ordinary sequential variables or `async`/`await` to work. Playwright supports `async`/`await`, letting you await each step before continuing.

Discussed at 20:34

How can I reuse login setup in end-to-end tests without logging in through the UI every time?

Keep reusable setup in Cypress custom commands or Playwright hooks and fixtures, and use the login API to obtain and save authentication state. Cypress sessions and Playwright storage state can reuse cookies across tests instead of repeatedly exercising the UI login flow.

Discussed at 23:38

What selectors should I use in Cypress or Playwright end-to-end tests?

Prefer selectors based on what users can access and interact with, such as roles, labels, and visible text; these are less brittle than selectors tied to implementation details. Cypress can use Testing Library selectors, while Playwright provides accessible locators such as `getByRole` built in.

Discussed at 29:03

How do I keep end-to-end tests isolated and runnable independently?

Treat each test as a complete user workflow rather than making one test depend on another. Put shared setup in a `beforeEach` hook, then let each test run and pass or fail on its own.

Discussed at 34:31

When should I use mocked data versus real endpoints in end-to-end tests?

Use real endpoints for smoke tests and critical workflows to check availability and correctness. For surrounding or rarely changing workflows, use mocked responses; a mix of real and mocked data can reduce unnecessary backend load.

Discussed at 39:55

Should end-to-end tests cover invalid input as well as the happy path?

Yes. Alongside tests that confirm valid credentials or inputs succeed, test invalid information and verify that the workflow fails as expected.

Discussed at 40:42

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos from DjangoCon US