Transcript
This transcript was autogenerated. To make changes, submit a PR.
Jamaica real
time feedback into the behavior of your distributed systems and observing
changes exceptions errors in real time
allows you to not only experiment with confidence, but respond
instantly to get things working again.
Those hi,
welcome to Comp 42 Javascript 2022.
Today we're going to talk about getting started with Cypress and in particular
gaining confidence in our websites with Cypress tests. Here's the part where
I tell you I am definitely going to post those slides on my site tonight.
I've been chasing similar speakers and that's why the
slides are available right now. Let's head off to roberich.org
and robert.org and we'll click
on presentations. And here's gaining confidence with Cypress tests
the slides and the code up on GitHub are online
right now. Achievements unlocked. While we're here
on robrich.org, let's click on about me and we'll see some of the things that
I've done recently. I'm a shoreline developer advocate. Shoreline makes
it easy to debug and analyze cloud native infrastructure
in the same way that you might analyze other infrastructure.
I've gotten some awards from both Microsoft and Docker, and I'm a
friend of Redgate. AZ Givecamp is really fun AZ Givecamp brings
volunteer developers together with charities to build free software.
We start building software Friday after work. Sunday afternoon,
we deliver completed software to charities. Sleep is optional, caffeine provided.
If you're in Phoenix, come join us for the next AZ give camp.
Or if you'd like a give camp closer to you, hit me up on email
or Twitter or find me here at the event and let's get a give camp
in your neighborhood too. Some of the other things that I've done, I do
a lot with JavaScript, so I got to work on the Gulp project in version
two and version three. That was a lot of fun and one of the things
I'm particularly proud of. I replied to a Net Rocks podcast episode.
They read my comment on the air and they sent me a mug.
Woohoo. So there's my claim to fame, my coveted
net rocks mug. So let's dig into Cypress.
Now, Cypress is great for testing a website, but I'm not
quite sure if my website's going to work. So let me fire it up
and see.
I don't know. Will this talk go well?
Let's see. It looks
like it's going pretty well. I think my site might be behaving and
it's nice that I get to learn this before my customers start emailing me
telling me there's bad things happening on my website. Yep,
I think this talk is going to go great.
Awesome.
Cypress Cypress is a great way to do browsers based functional
testing. Or said differently, browser is a great way to do end to
end testing of websites in the same way that a user would.
It's built on top of mocha and chai, so if you're familiar with mocha
or jest or jasmine, you'll feel right at home.
Now, it's two parts. One part is the test runner,
the electron app, and one part is a browser plugin that makes
it work really well with other systems. And so,
like many good stories, we started in the middle. Let's back up a little bit
as we look at browser testing, there's lots of different categories.
We might test a specific function.
We might test a component. We might test a service,
a web service. We might hit an API. We might
test from here down. Now, each of these are various
types of tests, and they'll need different tools.
As we start looking at the tools, here's a good list.
Now, as you grab these slides from robertsch.org, you can click
on each of these blue links to learn more about that particular type
of tool. Now, in this test, we're going to focus in this
talk, we're going to focus on end to end tests. But as I'm
testing different parts of my system, I will want to use different components
to be able to get there. Double clicking into end to end tests.
Selenium is the one that kind of invented this industry,
and it's a great system for being able to test websites.
Selenium uses Webdriver under the hood, and Webdriver has all
but become a web standard. But selenium tests
are kind of slow and kind of brittle. Selenium is
just moving the mouse and waiting for the
browser to update. So if the API isn't
done yet, well, then I might increase the timeouts in my
test that makes my test slower or I just rerun them,
which means they're fragile, they're brittle.
By comparison, Cypress has a browser plugin so it can
watch the dom, and whenever those DOm is done updating, it can continue on
with the tests. Cypress is deeply integrated
into the browser, which means it works with specific browsers that they've already
chosen to support. So if I need support for Safari
or ie, yeah, Cypress really can't help us
there. But for webkit based browsers, Chrome, Firefox,
it works out great. Cypress feels like a jquery
API, and that's a little dated, but we can kind of look through
that. Test cafe is another
tool here that might work out well. If you use Devexpress for other needs,
then you might feel right at home. Devexpress uses CSS
selectors, which works out really well. It feels really native.
But Test Cafe has a browser context and a
test context, so you'll end up shimming content between them.
Similarly, you'll shim a lot of content across that marshalled boundary
with playwright. Playwright is built on top of puppeteer. So if you've
ever used puppeteer to remote control a chrome browser,
then you'll feel right at home with playwright. The assertion syntax is
not using mocha with chai or jest
or jasmine, so it feels a little bit odd.
But playwright has excellent support in lots of languages.
Now, ultimately, which tool you choose to do end to end test,
pardon me, is definitely up to you, but let's use Cypress here.
How do we get started with Cypress? Well, the first step is we NPM
install Cypress and then NPX Cypress Open. That will open
up the ide. And we saw the ide a little bit before. It'll also
scaffold but a bunch of example tests if we opt into doing that.
So let's take a look at that IDE. Now, I've already done a
NPX Cypress open, and so I have those Cypress IDE.
I can choose which browser I want to run my tests in. In this case,
I'll those chrome. It automatically discovers the other browsers I have
installed, including electron, which is built in. It's a
webkit based browser built into this ide.
Now, I could choose to run all specs, or I could choose to run a
specific spec. Here's the examples that it
automatically scaffolded for me that show a lot of different capabilities
of Cypress built into their kitchen sink website.
But let's pop open this Todo MVC and we
can take a look at how this test runs. Now, notice that it's running
in a new edition of Chrome, not the edition of Chrome that I was running
before. So any local cookies or session storage or browser
plugins don't apply. And it can run really seamlessly inside this
web experience.
Now we can see that it is running the website as users would.
That's great. As we click into each step,
we can see the various tasks that it did and we can take
a look at the details at that particular task.
So it went looking for a new to do button and then
it did it. And we can find the oh,
there's the spot in the ide where it chose to click.
Now, if we pop open the web developer tools, we can see that,
well, it's just Chrome and so we can
set breakpoints inside of our code and we can
be able to see our functions running just fine inside
this iDe. We can also use this to be able to help us pick stuff.
We'll dig in more to how we can use best practices picking things,
but that can help us form that Cy get command
and we can choose to rerun our test that shows us that eight
tests passed, no test failed, and how long it took to run these tests.
But because it's just a regular browsers, if we pull up the
console, we can see the console output at each
step. So as we go between the steps it will actually update
the console, showing us the details associated with that step.
Excellent. So we saw in this case that all of our tests passed.
And so I think our website is behaving as we expect.
Let's take a look at the code of what produced this.
So here's that website and we start out with Cypress JSOn.
Cypress JSon identifies the plugins file.
So let's go look at that plugins file. And this plugin
file defines where all of the rest of the files are.
In later versions of cypress, these two files are combined.
Now I've chosen to put my results in a results folder so that I can
specifically get ignore it. I have a fixtures folder,
all of my tests, and that's inside test cypress
fixtures. I have an integration folder and a
support file.
So here's all of my tests inside my integration folder
and these are typescript tests. So in
my root directory I have a TS config that tells me all
the details of how I'm going to build my application. But I also have a
TS config here in the cypress folder. Now this says it
descends from the TS config above. So I don't need to redo all the
settings, but I am going to set the types including cypress
and include the cypress folder to be able to
get out the typescript type functions.
Now I chose in my package Json to do the cypress
open and cypress run just so that I can have some really easy
commands to run it. But we can also run in headless mode
passing in the particular browsers that we want to run. So here's chrome,
Firefox, and edge tests, and I'll run them all together
to be able to run my integration test across all of the browsers that
I need to run. So let's take a look at the test that we ran.
Now starting at this test,
this test is using Todo MVC. Now to do MVC
is a little bit wanted now, but we can see how we can test sites
that we own or sites that we don't own. The purpose of todo
MVC is to create a client side MVC application
in each framework. So there's examples in lots of different frameworks.
Now I chose to implement the angularjs one. In this case
we could choose to uncomment particular frameworks and run the
tests in those frameworks instead. I'll go grab the site name,
just that I can output that in my test block. And here's
our tests. It should visit a page cy URL
should equal the site URL. Now I chose
to do this before each to go visit the URL
so that I make sure that by the time my page loads,
by the time my test starts, I already have the page loaded.
Now that may not make sense in some tests when I want to be able
to intercept things, but in this case it's perfect. Now I
don't need to repeat that. At the top of every test we
can see that we're grabbing site URL. So I just want to make sure that
I've landed on the page that I expect. Now as I was first
building this talk, they had the site based in HTTP and then
they flipped it over to HTTPs. And so I'm really glad I had
this test to validate that it was the page I was looking at.
If ever you refactor your website and the pages move around, you want to
make sure that you're hitting the actual page that you want to test. Next,
let's start interacting with the page. So let me go get this
to do list. Let me go grab all the list items in it,
all the to do items and it should not exist. We can see that kind
of jquery like experience where we have not
exist. All else being equal, I wish there was just a
not exist method like in mocha,
like in chai or jest. But nope, they're strings.
Okay, so now that we've selected and noticed that they
don't exist, let's level up again and start interacting with the page.
So I want to create a new to do. I'm going to create this write
cypress test. So let me go find that new to do box.
I will start typing in it and hit enter. Now we can see that
those one grabbed that replacement expression to set that in
place, but this enter doesn't have the dollar sign
in front of it. This is just a magic word where
we can specify the specific keys.
So we could specify function keys, shift control,
alt, any of the special keys that we need. We can use this syntax to
get at. So I get that and I type in that box the
new to do and I push enter. Notice that we're not awaiting anything.
We didn't await the page cleaning either. Now once all
the Dom events have finished, let's go grab that to do list and validate that
it contains that new to do text and that our new to do box
is blank. Let's level up again and start to
type in a bunch of stuff. Let's create a new to do, create an
irrelevant to do and then go click
on the new to do to toggle it as completed.
Notice that we reach into that to do to grab
the children toggle and click it. That's really intuitive.
Now let's go look for the to do list because we completed that,
then we should have a class of completed so we can
validate the CSS class as completed and we're ready to go.
Should delete a to do. Let's go create two to
dos. We will go find the new to do and we will click
on that button. But this destroy button only shows up
if we mouse over it. We're not running mouse events,
we're running Javascript events. So there is no
mouse over per se. So we're going to say force is true, we're going to
click it even though it's not visible.
And now our list should only have length one and it should be
the irrelevant to do. We want to make sure that we clicked on the correct
one. It should only have active tasks. So we've been
doing a whole lot of this creating the to do,
and that's, well, kind of messy. Wouldn't it be
better if we could say to do add? This is
a cypress command. So let's open up our commands folder
and we'll build up a bunch of commands. Here's this to do
add command. I'm going to pass in some text. I'm going to go find that
to do button. I'll type in the text and push enter.
And while we're there let's just validate that the box is
now empty. Excellent.
So now that we have this command we can just call to do add.
Now because this is typescript, we also need to give it a typescript
declaration file saying what this new command is. We can
head out to the cypress docs and we can see how we can build
this typescript declaration file for our new commands,
but that makes our tests a whole lot more legible.
If ever you've used page objects inside of selenium,
this is exactly the same. So we'll add this to do, we'll add this to
do. We'll add the new to do that we care about, and then we'll complete
the new to do. Now, because we should only show active
tasks, we should have two left. So let's
click on the filter and then validate that we have two left.
We should only show completed tasks. We'll do a similar thing,
creating our to dos and validate that when we click the completed
box, we only have length one, only the one that was completed.
And for the sake of completeness, let's show clear
tests. Well, let's clear the tests and validate that we don't have
any left in our thing. So firing up this tests, we can
see how we run each of those tests and start to level up through our
experience as we go. We'll start by just validating the URL.
Oh, here's the typescript build that it does, because it did
a build. It actually refreshed the page and you can see how it rewrote
the URL as well.
So we visit the page, we have no to dos,
we complete a task and all of our tests pass.
Excellent. So let's level up a
little bit and talk about this Hackernews PWA site.
Now, this is kind of the spiritual successor to Todo NBC, the site
we just saw. And this creates a hacker news client.
Now, we want to be able to test this. We want to validate that,
if we render this correctly. But we don't know what today's hacker news stories
are, so it may not make sense to
just do this. Let's take a look at how we might intercept
requests. So here's a hacker news site. You can flip
between the various instances. We'll start off by visiting the page straight away
and validate that our site is as expected.
Next, let's intercept a web request. We don't want
it to reply with the actual hacker news content. We want to reply
with our fixture. Now, we could use strings here.
I chose to use regular expressions, but we want to very specifically
use this hacker news fixture. Let's come into this fixture
and we can see this hacker news fixture. Now, I've got some fake data
here, and so I know that the title of this first article
is, this is the first story. So as I go to render
this component. I know that that should contain those
specific story. I've taken control of that external resource
and made it behave in a very predictable way. This is really helpful
when validating if we can render our components correctly.
Now, we could lie to ourselves and presume that the API
always returns that way. But no, we want to be
able to validate. It actually returns correctly too.
So I know that it will return 30 stories on the
page. So let me go run this and validate
with the real API that it works as expected.
If we only mock out all of our API requests,
we won't be able to validate that our site works, only that
our site works if we assume the API behaves that way.
Now if this takes a really long time, we could name that thing
and then wait for it. But in this case I think it'll run fast
enough. So let's fire up this hacker news test and
validate that we can hit our content and that we
can replace content to be able to validate our controls,
render successfully, and hit the real API to validate that
our site still works even with real content.
Yep, our site works just fine.
That was cool. We were able to dig into our
cypress tests and be able to run them in a really elegant way.
Now, some best practices around cypress tests.
It would be easy for us to just build up that XPath down into
the particular element, but well, if we refactor our website then
that will break. We want to make these tests a lot more durable.
Look for an id or a class and hook
onto those or even better, create a data
datacy. And then in the test look for
the attribute data cy that matches that element.
The other benefit here is that we've very specifically detailed that
our test that we have a test dependent
on this code. If we were refactoring this code, then we may need
to also refactor a test. Now does that mean that we might
end up with test code in production? Why yes, but we
can also run some cypress tests in production as well. Let's run
a standard mechanism that hits our home page, goes to
our shopping page, puts something in our cart and tries to check out.
We can do that in production maybe once an hour, maybe once every 15 minutes.
Now we're not going to complete those purchase, but we can get pretty close.
That will ensure that if our site goes down, we know before our customers
start calling and complaining. So maybe having our test content
in production is actually a good thing.
Another best practice, use these commands
for frequent tasks. If you've ever used page methods inside
selenium. It works really similarly and it can give you an extra layer of
comfort there. It helps you focus on the execution
of our test rather than all of the mayhem of how to get there.
And if you ever refactor how you do it, you refactor it in one place
and it'll apply to all the tests. Excellent.
Now another best practice is to log in only once.
If we start every test by logging into the website, our tests might be slow.
Instead, let's log in once, save a token, and then
use that token before each in all of the rest of our tests. Or even
better, hit a test API that is able to grab a token and then save
that for use in all of the tests. We do want
to validate that our login works as expected, but not at the
beginning of every test. That could make our tests low.
Another best practice? Use fixtures for mocking data.
If you have a consistent username and password or a consistent result
set, put that in a fixture so that then it's not
cluttering up the majority of your test and your test becomes a whole lot
more legible and terse. Now we used a fixture in our
application to reply to a web request. Instead of hitting
that API, we got our fixture so that we could validate our control rendered
correctly even if hacker news content changed.
Now, Cypress is a great mechanism for being able to run
end to end tests on our website, and I would invite you to get started.
You can find these slides on robrich.org and hit
me up in that spot where the conference is designated for live Q
and A. Or if you're watching this on demand, hit me up@robrich.org
or on Twitter at robrich. Thanks for joining
us for comp 42 Javascript.