Do your automated tests include a11y checks? This workshop will cover how to get started with jest-axe to detect code-based accessibility violations, and Lighthouse CI to validate the accessibility of fully rendered pages. No amount of automated tests can replace manual accessibility testing, but these checks will make sure that your manual testers aren't doing more work than they need to.
Automated accessibility testing with jest-axe and Lighthouse CI
Transcription
So, this is going to be a workshop on automated accessibility testing. And I'll talk a little bit more about that in my introductory notes here. First, just a little bit about me. So, my name is Bonnie Shulkin. And I've been in the software industry since 2001. So, I've done a lot in the software industry. My current role is I am a full-time developer and trainer. Self-employed. You can find me at Bonnie.dev, which also is on the lower right of all of the slides in case you forget that. Twitter, I'm at Bonnie.dev. And GitHub, I am very proud to be at Bonnie. If you go to Bonnie.dev and you go to the talks tab, you can download these slides, which I would recommend doing. There's some links that it will be easier to click from the slides than it will be to actually type in. Okay. So, Lars, thank you. I'm just looking at the chat. If I'm looking over this way, it means I'm looking at my other monitor, just seeing what's going on. And I saw that the chat came in. So, yeah. Applications available to all citizens and organizations in Denmark and accessibility is a concern. So, yeah. Automating quality checks is good. Let's talk a little bit more about what accessibility is. So, accessibility is making your app, your website, usable for people with a diverse range of hearing, movement detection, sight, and cognitive ability. So, some examples of making a website more accessible are providing alternate text for images. So, if somebody is using a screen reader to view the screen, then they are going to be able to hear a description of what the image is. Also, keyboard alternatives. Because some people are not able to use the mouse in precise ways. And using the keyboard is more accessible. So, being able to have keyboard alternatives to any mouse clicks or going from section to section of your website. And I've added a couple of resources here. The W3 Consortium has a page on accessibility and they have a lot of guidelines as well. And then MDN, whose documentation I really like. They also have a page on accessibility. Thank you very much for posting the link to the slides in the chat. So, you may have seen this A11Y abbreviation. Does anybody know what it is? Well, I mean, it's an abbreviation for accessibility. Does anybody know where it comes from? Yeah, some people do. You can unmute yourself. Again, we're a pretty small group. So, feel free to chime in. So, it abbreviates accessibility. And, yeah, it's an abbreviation for accessibility. And the A comes from the A in accessibility. And there are 11 letters in between. And then there's a Y. So, that's where the A and then 11 and then Y comes in. And you can pronounce it A11Y. Or it's also sometimes pronounced ally. Now, as far as automated accessibility testing goes, and that's the subject of this workshop today, the goal is to make the manual testing less tedious. But it doesn't replace manual testing. So, there was a study. And the study found that only roughly a third of issues are actually found by automated testing. So, the automated testing will catch things that will make it easier for your manual testers. So, they don't have to look for all of these things that can be caught by automated testing. But it's not meant to replace manual testing. So, if you really want to make sure that your site is accessible to screen readers, for example, you're going to want to have somebody test the site using a screen reader. All right. So, in this workshop, we have an app, and it's a react app. But the tools that I'm going to introduce, they can work for other frameworks, too. The react app is built with vite, which is an alternative, sort of a lightweight alternative to create react app. And really, the app is just a very simple html and css about page for this fake healthcare company. So, there are two versions. There's html... There's one that's just html and css. And there's another which uses the design system, Chakra UI. And I use the design system, or I have two different versions of this for a reason. There's some things that you can catch when it's just really bare bones html, css. That's a lot harder to catch when you're using design systems, as we'll see. This is the GitHub repo. If somebody can copy and paste that into the chat as well, that would be lovely. If you haven't already, thank you very much. If you haven't already, please clone or fork this repo and run npm install in the html, css, and Chakra UI. So, I'm going to just take a look at that. I'm going to get out of slide mode for a moment. And let's go... I'm just going to go to a terminal where I have this... here I am in the repo. So, you can see that I have Chakra UI directory, an html, css directory, and then a directory where you can copy and paste stuff from files, or you can... Will be moving files from this directory just to reduce the amount of typing that you have to do. So, if I go into html, css, for example, it's a react app, so the code is in source. And I can do... Oh, and let me actually open up VS Code here. So, here I am, and I want to look at the package.json. I have a bunch of scripts here. This came with vite. The top three came with vite, and then the bottom three I added. But I'm going to be running this using npm run dev. So, now it's running at localhost 3000. And just go to Chrome. If we look at localhost 3000, you can see that page. And the background color is... Oh, I know why it's so terrible. It's because I have this dark mode on here. So, it has some problems. And some of the problems you can't really see. For example, this image doesn't have an alternate text tag. And then this one... This contrast is not very high. So, people who have difficulty seeing contrast might have trouble reading this. So, that's another issue. There's also an issue around this label for region. And I've sprinkled a couple other issues in here as well. So, that's going to be the backdrop app. Now, I'm going to control C out of this. And I'll go into Chakra UI. This one is very similar. And if you look at the package JSON, you've got the same scripts. It's got some different installs, because it's using the Chakra UI design system. But I can run this one as well. And I can look here. And if I refresh, it's going to look pretty similar. There's a little bit of difference in the response of this. And you can see that we have some difference here. And actually, interestingly, in the dark mode that I'm using here, the contrast actually looks okay on this one. Whereas it doesn't in light mode. Let me turn off... I'm going to turn off the dark reader for a moment. Actually, you know what I think I'm going to do is let's look at our sites in Safari instead. The Zoom screen share is getting in the way. Okay. So, this is what the site looks like. And you can see that that contrast is very poor. Okay. So any questions about the apps that we're going to be working with? All right. Let's go back to the slides. Okay. So I'm going to talk about three different ways we can do automated testing here. And the first way, automated accessibility testing. And the first way is linting. So linting is checking while you're writing code or I'm using ESLint. So you can do this with an ESLint command. And you might have noticed that I had an npm script for linting. There's npm run lint. Now if I... Let's actually go back to... Let's go back here. I'm going to go back, we're going to be working with the html css one for this linting to begin with. So if I run npm run lint, it's running this ESLint command. And notice that we're getting some errors. We're getting some errors about this alt attribute, ARIA roles, and the form label. And actually, I was afraid this might happen. I've been working with, like, both the solution and the initial ones. And so this one, if we go back to code, I actually committed part of a solution. So let's go into source and app. And let's actually take out this alt tag on line 14. Because that's meant to be part of the problem here. Okay. So now if I do an npm run lint, you can see I'm getting four errors. And that's what I expected. So the image doesn't have an alt prop on line 14. There is a redundant alt attribute. There's a nonabstract or an abstract ARIA role that it doesn't like. And there's a form label issue that it doesn't like. Notice it's not catching that contrast issue. The contrast issue is not getting caught by linting. So we can and the linting tool that I'm using here is ESLint plugin JSX ally. And so it's already installed. And it's actually included with the Airbnb ESLint plugin, if that's something that you use. And then you need to add the rules to your ESLint config. And then finally, here we have a place where you can look up the failures. So if you're not sure what a failure means, yeah, vite is very fast. This isn't even my new fancy I'm just responding to the chat. This isn't even my new fancy M1 MacBook. This is my old one. And yeah, it's a lot faster than create react app. And it sounds like faster than angular. So let's get out of here for a moment. And if you're not sure, for example, what this ARIA role means or this label has associated role, you can go to the ESLint plugin page for the JSA11Y. And there are rule details. Let's see. Oh, this one, I'm actually in the alt text rule details. So if we go back, here are all the rule details. And so if you want to know what one looks in particular, you can click on it. If you're actually in VS Code and you mouse over, you can click on the rule detail just from that from the VS Code pop up. And it'll show you it here as well. So I can put an alt here. And I'm going to do something slightly not so great with the alt so that it'll get caught later. I'm going to say health for life logo. And really logo is not a word that you want to put in your alt text. You don't want to put like photo or image or logo because it just is redundant for screen readers. But you'll notice that now that linting error is gone. I don't have the angry red squiggles anymore. And if I do the npm run lint, now I only have three errors. Okay. So I'm going to give everyone a moment to run npm lint on your own machine and fix the remaining errors. Now let's go into this. Let's go back to VS Code. All right. So for this one, it tells you don't use the word photo in the alt. So we'll just remove the photo of. And that lint error went away. This one, this role equals hidden. So this is the divider that you can see. That's this divider here. This long line that I decided screen readers didn't need to deal with. And aria hidden equals true is the correct way to do that. The role equals hidden. That's just not a legitimate role. And then the final one. So some people were saying, well, can you just wrap the input in the label? And you can do that. And you'll notice that made it go away. You can also use the HTML4. And use the ID here. Ah, because my label is actually not surrounding any label, I believe. Okay. There we go. Okay. So let's look at the chat. Oh, they need to have the same parent. Yes, thank you. Okay. So great. And so I was also chatting with some people who came back a little early. And if we look at the chakra project, and we run the lint, linter on the chakra project, the chakra project has the same four errors. But it only finds one of them. So if I look at chakra in VS code, and I look at that app, in that app file, you'll notice that we have that logo, and it does not have an alt tag the way, for example, this splash image does. And I do have an ARIA rule hidden in here somewhere that, yeah, so I have this role equals hidden. So that's the one that it found. And this one only has the divider once. Let's see. Do I have? No, of course, I don't have that still in here. So I can update that. It has another issue with this label. This label is not within the form control for the region. But none of those are being found. I fixed that ARIA role. And now we can see it with no angry red squiggles. And we can see it here. The linter thinks this is totally fine. So linting doesn't work so well when you're using a design system, because it's looking specifically, here I am back in the html css, it's looking specifically for standard html elements like image. And if I go back to Chakra, I'm back in Chakra here, you can see I'm not using the html image element. I'm using the Chakra image element that I've imported from Chakra UI react. And the linter is not familiar with that element. So we're going to bring out a different tool here. We're going to bring out just axe. Let me get back to my slides. Okay. So this is just saying what I just said. So it's looking for standard html elements. And so any design system that you use that has custom react components is going to have this problem. Just axe helps with this. It helps with this because it renders the code. And it checks the rendered code, which has the standard html elements. Next in this case, axe standards, and I have a link here, axe is it's this deck university or this company deck here made these standards. So here's a list of all of their rules. And it's not exactly the same as the JSX ally linter. But it's very similar. Okay, yeah, so because the rendered code has those standard elements, then it's going to be able to catch the errors. So I've already installed jest to these projects, but I have a link in case you want to install jest to future projects. That's a disadvantage of with create react app. Create react app is in some ways more bloated, but it's also more complete and includes jest. And I've also installed testing library. Then we'll have to install jest axe, which has not been installed. So let's go here to jest axe. And this is where I got that only 30% of issues are found with by automated testing. Alright, so first, we'll install jest axe. And I'm in the chakra UI project now. Just paste there. And for typescript, we can also install types. It's not necessary for this project because I'm not using typescript on the tests. And then you'll see that the usage, we actually have to import axe and to have no violations. And then we run axe on some html, and we expect the output to have no violations. Let's go back to the slides for a moment. With testing library, which is what we'll be using today, we use this container for testing library. And that's where we get the html from. So we're going to make a test file in that chakra UI project. And we're just going to copy the code from the workshop repo. So we're going to copy it from the copy paste files. And the code actually comes from the jest axe docs. So if we go here, you'll notice that it basically gives us everything we need for react testing library. So here, I'm in the chakra UI directory, and I'm going to copy from that copy paste directory, which is one level up. And everything starts with a so app test JSX. I'm going to copy that to my source directory. And we can look at what we just copied. So now that we have this test file, and it doesn't like the my linter didn't like the order of the import. So I just saved it to reorder the imports. So I'm getting render from testing library. I'm getting axe and to have no violations from jest axe. And then TSX likes to have react in there. And then I'm getting my app because that's what I'm going to render. I extend expect to be able to use this to have no violations. And if you were running this in a project where you had more than one test file, you might want to put this in your setup tests so that every file could use it. Then I'm rendering app. And I'm destructuring container from what comes off of it. So this is just the suggested way of jest axe. This is the suggested way of getting html to pass to axe. So that I can take that results running axe on this html and I'm expecting that results to have no violations. So as you'll recall now, if I do an npm run lint, I'm not getting anything. And now if I do an npm test, this is running jest in watch mode. Running that test file we just made. And now you can see it found some issues. It only found two issues. And we know that there were three remaining. We'll address that in a moment. But for now, I'm going to put you back in your breakout rooms to work on these issues. Now there's something that is less convenient about jest axe than the linter in that it doesn't tell you what line on your code it is. Because it's working on rendered html. So you're going to have to look at this code and see if you can find it. And it's also the classes are not ones that you typed. You did not actually create or you can't find those classes in the app.js file. Because these are from the rendered code from Chakra. So you have to do a little bit more work to find the issues here. I'm going to break you out into your breakout rooms again. So that you can find the issues and fix them. And then we'll come back and take a look at that third issue that wasn't caught. So let me. Okay. All right. Goodbye. Sorry that you have to leave. Bye bye. But I was talking to Maria about she wanted to be able to see what the actual entire html looked like. Just because it was so difficult to actually figure out what was going on. So we were talking about I did a try and catch here. And so you're probably aware the way jest records errors or registers an error in a test is if there is an error that's thrown. And so if this line throws an error, before it actually before we actually throw the error to fail the test, I'm running a screen.debug. And screen comes from testing library react. And if I do that, then you actually get a whole picture of what the html is, which might make it easier to actually figure out what it's talking about here. So we can fix the errors in app.tsx here. We can add an alt for the logo. And we can fix the label by putting the label within the form control. And now if we go back here, my test should be passing. So that's great. The tests are passing. It did not catch that photo of if we go back to the Health for Life CEO, it did not catch that photo of because that's not one of the X rules. That's a rule in the JSXA11Y linter. But it's not a rule in jest X. So we can actually add rules to jest X. All right. So we can add a custom rule by creating our own axe object and importing axe from that from the axe object that we created with the custom rule rather than just axe. So let's take a look at that. And I have in copy paste files, I have that axe config. It actually took me a while to figure out how to do this. There's not great documentation for this. So if we look at copy paste files, we can see I have this axe config. So I'm just going to copy that. I'm going to copy that just straight to the top level of the Chakra UI project. And then if I go back into the code, I can look and see what it is. So I just imported configure axe from jest axe. And then I configured it with this rule that, again, like, I'm actually thinking of writing a blog post on this because it took me so long to figure out how to actually add this custom rule. But basically, the test is if this is an image, if this is an image element, because it only looks at image elements, and it has an alt attribute, and so it needs to both have an alt attribute and the alt attribute must not have photo, image, or logo in the attribute. So that's just a regular expression thing here. So now and then I export this axe here. So if we go into the test file, instead of importing axe from jest axe, I'm going to import it from that file that has the new configuration. So I'll import axe from axe config. This is where it's really useful for you to have your cameras on because I can see, like, when people are typing and doing stuff. So now if we run that npm test again... It's going to be running with that new rule. And now it failed. Now it failed because it found something that has a redundant word. So if we go back to app.tsx and we remove that photo of, just make sure I'm in the shockware UI because it's easy to get these two projects mixed up. The watch mode will rerun the tests. Oh, that logo that I hid in my alt text for the logo, that's getting caught now, too. So we'll have to fix that as well. All right. So give you a minute to get your tests passing as well. Go ahead and do that setup in the html css myself. So I'm going to do the npm install. That's for the types. Do the one for the actual package as well. Then I'm going to copy that copy paste file, the app.test.jsx into the source directory. I'll do an npm test, which should run it. And yes, everything passed. No accessibility errors caught by just axe. And the reason it's not catching the logo is because I haven't done the custom rule yet. So I'm going to copy that axe config. And then I will update that test file to import axe from that config file instead. So now it should be using that new rule where it's testing to see whether it has photo image or logo in it. Wrong up arrow. And now it's finding that logo issue in the health for life logo. So I can go back and remove that. And now all the tests are passing. Okay. So this is not going to take the entire three hours for the workshop. But let's take a ten-minute break and we'll be back at 20 minutes past the hour, 920 a.m. my time. And then we'll do the final part, which includes cypress, where we'll actually tackle that contrast issue that the code-based tests just can't detect. So I'll see you in ten minutes. Thanks. Are we on a break? Yep. For another ten minutes or so. Thank you. Thanks, everyone. All right. Hello again. Let's take a look at cypress audit. All right. So errors beyond code. So for example, those contrast errors that we were seeing. If I go back and let's go into the Chakra UI and open. So here. This contrast error, where when I'm not mousing over, it's pretty hard to read that text. And if I were somebody who had trouble seeing contrast, I probably couldn't read it at all. So we're going to use cypress, which is a way that actually renders the page in the browser. It's a test framework that renders the page in a browser. And cypress audit, which is going to do some accessibility tests. So cypress is an end to end tester that actually renders the page in a browser. And then cypress audit provides tools to use either Lighthouse, which is a Google based tool for accessibility and a bunch of other web page standards, or Pally. And we're going to be using Lighthouse. They do very similar things. cypress has been installed in both of the projects. And there is a there's an npm script to run cypress. So you can do npm run cypress colon open. So if we do that, for example, in our shopper UI, it's opening cypress. And then there's some integration tests that it just comes with by default that we're going to ignore. Alright, so I'm going to control C that for now. So cypress audit, we can install it just the way you'd install any other npm dependency. So you install and do a save dev of cypress audit. And then it needs some setup files. So we're going to keep clicking and it thinks that I'm clicking on. There we go. So there's an index.js file in the plugins directory that we're going to use. And that is that is part of we're going to get the text for that from the documentation. And then we're also going to have to add some commands for cypress. So let's do all of that together. We'll go here and shock where you I and we'll do an npm install. Save dev of cypress audit. Alright, we'll let that do its thing. And in let's see, I want to be in my chakra UI directory. In cypress at the top level. In plugins, I have an index.js and I'm actually going to just delete it. And I'm going to replace it with the index.js that I get from cypress audit. So I'm just going to copy this whole thing here or I could click on the copied. This is the cypress audit and it's from the link from the slides. And I'll paste here. And then I'm going to comment out the Pali line because I'm not interested in that. So I'll paste this in the chat just in case people need that. Notice that we're using require and module.exports. With cypress you can't use the ES6 import, at least not out of the box. And then my linter has some problems with unused variables that I'm just going to ignore. Alright, so we have this. Now in the support commands file, I'm just going to import the commands from cypress audit. So I can use those in my cypress test file. And then finally I have a cypress test file and I have that in my copy paste files. And that's the A11y spec.js. So I'm going to copy that into cypress and integration. So this has some lighthouse thresholds that we'll get to in a minute. But basically all it's doing is it's visiting my local host page and then it's running that lighthouse command, which I set up with that index in my plugins and the commands.js. Alright now in order for it to visit, let's go back to that spec file. In order for it to visit this page, I need to actually have my server running. So I'm going to open up a new tab and I'm going to have my server running in the background. And then I'm going to open cypress again. And I'm back to my original tab. And now I have this A11y spec. Now I have Chrome here. If we try and run this on Electron, so cypress actually loads the page. It would have a problem if I didn't have my server running on 3000. And notice it says Electron is not supported skipping. So you need to be using Chrome. I actually haven't tried it on Firefox. But so now I'm going to try Chrome. Seems to be doing more here. Okay so I got some errors. And I got some errors on performance and accessibility, best practices, search engine optimization, and progressive web app. Right now I'm not interested in anything but accessibility. So let's go back to that spec file. And I'm going to just use those lighthouse thresholds. So basically my threshold for everything is zero except for accessibility where it's 100. When I save that, cypress detected the change and now it's going back through its thing. So now I've got that the accessibility record is 98. And it's under the 100 threshold. See why lighthouse is not a function. So for that, you probably don't have this in commands.js. Sorry, can you open the spec file? Okay. Just wanted to make sure I wrote it down correctly. Okay. Thank you. No worries. Lars, did you have you got it working? Now it's asking for those thresholds. Okay. So it sounds like you got past the lighthouse is not a function. So you may notice that this is not works as expected. Yes. And I'm surprised that I'm getting 98. Because I was getting an 83 in all of my practice runs. But at any rate, yeah. It's failing. So I'll take it. So this is not I mean, it's a useful error in that it's telling me I'm failing. But it doesn't give me any indication about where I'm failing. If we go back to the documentation, it says, okay, well, you can get a report by adding this function as an argument to lighthouse in the plugins index.js file. Fine, let's go back to the plugins index.js file. And I'm going to add that function. You don't have to worry about following along with me here, because this is not going to be our final step. Of course, you can if you want. All right. So I did that. And at this point, sometimes cypress will give an error. See if we go into Chrome. And we try and rerun this. Yeah, so this cannot destructure property errors of object null as it is null is a false error that happens when you change the configuration from underneath cypress. So I'm just going to I closed Chrome, I stopped this, and I'm going to run this again. Okay, so I got the error. And that report is back in my console. And the report gives a lot more information than you necessarily need. And honestly, is not all that useful in finding the error. So back in the copy paste files, I'm going to control C out of here for a moment. Back in the copy paste files, I've made a lighthouse report that parses this enormous object and prints the report to a file in a way that I find more useful. So let's copy that lighthouse report. We're going to copy that into cypress plugins. And again, basically all this is doing is it's taking the part of that object that I'm interested in. So you'll remember in the index.js file here, the function here takes that lighthouse report. And all I did was just I console logged it and just spit out that huge object. So this function takes that report. And if there are no violations, so I dig into where it actually prints the violations. If there's no violations, I'm just going to console log that there's no accessible errors and it's not going to make a report. Otherwise I'm going to parse the violations and make them look pretty. And if I couldn't write the file, then I won't write it. Otherwise I'll say I wrote the file. And then I also did some error management here because when I went from my main computer to my studio computer, like it had some errors with the way I was formatting these multiline things. And so anyway, I just I didn't want it to fall over. So I just did a I wrapped the whole thing in a tri block. All right, so so and again, because this is cypress, we need to use require everywhere and module exports. We can't use the ES6 import export. So let's go back here. And now I'm going to say const lighthouse actually, I think there's I'm not destructuring this lighthouse report equals require lighthouse report. And then I'm just going to pass that function along here. So let's open cypress again. And we'll run our A11y spec. And now we can watch this and there's a lot of information about what's going on with the server. So we're waiting for that info or error. So I got an info and it wrote it to this reports lighthouse A11y. And then I just put an ISO timestamp on it so that it'll make a new report every time. So if we look at reports, we look at this. Okay, so now I'm getting the actual issue, which is it's just more useful than that entire object. So I have a it's that because this is rendered, it has some similar problems to just axe and that is not going to be able to tell me the actual line of code. But it does tell me that it's a button type equals button, it has a label sign up for mailing list. And it gave the foreground color and the background color and it told me what the issue was. So if I go back to app.jsx, I can solve this in a pretty blunt force way by finding that button. accessibility record is zero. That's interesting. So for the button, I'm going to give it a background. Oh, no such directory. Okay, so you might have to make a reports directory in the no such in the no such directory, you might have to make a reports directory in the cypress file. I tried to make sure that that was there that the reports directory was there with the by making a readme. Oh, no, actually your path is oh, your path is using you have okay, I'll work with you on that in a moment. Lars, thank you. I haven't tried this on a Windows machine. So I'm just going to make the background be so here I have a theme for Chakra UI. I don't know if you've used Chakra UI before, but I have a theme that has brand colors. So I'm going to make the background the darkest color and I'll make the foreground the lightest color. So the background is going to be brand 900. And the foreground or the text color is going to be brand 100. And so that should be rerunning the test. Maybe not let's rerun the test here. So notice now my accessibility record is 100 and it found no accessibility errors. But it wrote the report anyway. So there's some issue there. See the report that it wrote. Oh, okay. Well, my length detector is having an issue. I can I can work on that later. Right now I want to look at Lars. So you are getting so it looks like it looks like Lars maybe on Windows you need to create the file first with FS. It's trying to write the file and it can't find it. So you might want to and I'm actually not an expert in this, but in the other Google Chrome anyway. We're using FS write file. So FS open. If the file does not exist, an empty file is created. So Lars, you might try doing an FS open for writing before you actually before it actually opens it. So if we do so if we do FS open and file name for writing and then say open error here and then yeah, I don't love this this nested callbacks here, but say if so you might try that Lars and see if we get if I mean I'm my fear is that we're going to get the same the same error that is just going to be like can open it. See how we doing? Yeah, so for everybody else, you can you can try running or installing and running the cypress audit on the html css folder and see if you can get that working there and if not, let us know what questions you have. That's supposed to be consistent. I probably should. The error is the same, but it works if you create the report file first manually and okay. All right, so let me I'm going to just I'm just going to make a note that. Okay, so. Is anybody else using Windows? I'm just curious. Okay, so. Okay, so we can get this working on. On html css as well, I'm going to control C out of here. And I'm also going to control C out of my server because we want to make sure that the server that we're running is the html css server. Otherwise we would just be testing against the chakra UI server. If that's the one if that's the one that was serving on localhost 3000. So now the html css server is serving on localhost 3000. So if we look here, we have that poor contrast here. I'm going to go back into html css. And we'll do the same things over again. So the first thing I'm just going to close out the chakra UI in cypress. And oh, so once you run cypress and add some folders so I can run that. So it added my integration and plugins folder. I'm going to remove everything from the plugins folder. And I'm going to add the text from the cypress audit documentation. Just going to comment out the Pali since we're not using that. Then in support and commands, I need to add the cypress audit commands so that it will recognize the lighthouse commands in that spec file. So I'm going to copy from my copy paste files that spec file into cypress integration. So now I have that spec file here. And that lighthouse command is now coming from the commands that I've added here. Oh, thank you, winter. I forgot to actually install cypress audit. So let's do that. Oh, it wasn't done. Okay. So now the winter is happy. And it's going to run this without the thresholds. So let's go. We need to open cypress again. So we can see, well, if it gave us enough time, we could see that this is the html css one. And, again, we are getting some thresholds that we don't care about. So we'll pass that lighthouse thresholds that I've written out, which is basically I don't care about anything. Anything except for accessibility can have a threshold of zero. And so now I'm getting the error, but no indication of what's actually causing the error. And that's where I'm going to quit cypress again, I'm going to copy that lighthouse report from my copy and paste into cypress plugins, because that's where I'm going to actually use that report, that report function. And now so now we have this report. I'm going to require it since we can't use the ES6 import from cypress out of the box. And then I'm going to pass that function to the lighthouse function here. And this lighthouse function, what it does is its argument is a function that takes that raw report and then does something with it. And we did a console log of the raw report, found that the raw report had way too much information to be useful. So now instead we're going to be processing it and writing a report to file. So let's run this again. So now it wrote this report. And on Windows, you're going to have to do the same thing where you're going to create the report and have it overwrite, which again is not ideal. And I'll look into that and think about it. And back here, we can look at the reports. And it's telling us it has insufficient contrast. And it has this foreground color and this background color. So if we go into app.tsx, and we'll take that button. And here I'm using a class of button. This one is the one that's really just html css. And so I'm going to go into this about.css and this button class. It's not in about. It's in brand.css. And so for the button class, I'm going to, again, I'm just going to really brute force it here. I'm going to make the background color black and the foreground color white. Just for maximum contrast. So I'm going to run this test again. And now everything's passing. And if you look, yes, that's really great contrast. Now it's possible that the mouseover contrast is not good enough. And for that, you would have to in cypress, you would have to move the mouse over this in order to check the mouseover contrast. But that's not something that I'll do here. That's more of a cypress thing.