 All right, it's not actually time to start, but I figured I'd do some vamping before we go on. So, firstly, they didn't tell me that I was gonna have to follow Sarah May when I agreed to speak at this conference. And like, usually, when Sarah and I are speaking at a conference together, she's the closing keynote, and I'm right before that. And so like, I don't have to be the disappointing follow-up to her amazingness. So could we just like get a big round of applause for Sarah and the great work she did this morning? Like, we were literally at dinner last night, and Abby, the woman who sort of does most of the top-level organization of this conference, was having a massive freak out because the speaker's flight was kind of broken. And like, I travel a lot for conferences, and so I was like pulling up flying websites on my phone, checking out, I was like, yeah, that connection is almost impossible to make. And it was pretty tense in the room. So Sarah, stepping in at the last moment like that is really huge, and yeah, thanks once again. Hello, Ralph. It's been a little while. I guess I did make it last year. How's everyone doing? Cool, all right. As people filter in, please sit in the middle. People on the edges, I would encourage you to also sit in the middle. I promise you you'll be able to see the slides. I've been sitting at the front middle of the entire conference, and I can see them, and like, it makes the room look a lot more full if you all sit in the middle. You can stand up and move, that's allowed. All right, we have like 30 seconds to go. So as people are filtering in, I'm just gonna go ahead and get started. The first thing is that when I submitted this talk, quick and easy browser testing using RSpec and Rails 5.1, Rails 5.2 wasn't anywhere close to done yet, but it is now, and so I kind of have to make this adjustment. Oh my God, it's a clicker not working. Let's just try unplugging this and plugging this back in. Yeah, Eileen, if you could maybe bring yours up, that would be great. Yeah, that's not, oh actually no, it looks like Kino has maybe crashed. Oh no, we're good, we're good, we're good. Thank you, Eileen. Cool, that's why you start early so that when the clicker doesn't work you can keep going. So basically like, then Rails 5.2 came out and like I kind of had to make this adjustment except that like much of the content in here is quasi 5.1 specific, so I will note adjustments to the slides that I have written, but for the most part, this will all stay the same. It's really cool when Rails versions get released like just around the conference so that we're all able to talk about the upgrades and what stuff is available to you and all the shiny new features in our Rails minor, but unfortunately, RSpec as of today does not officially support Rails 5.2, but I will tell you that fixing it required me to change one internal test and so unless you have a wildly specific use case of active storage that almost nobody has, RSpec will work with you on Rails 5.2 today. This has been confirmed in a number of production applications so you should be all good. It's kind of interesting for me to be up here because I've been doing this for quite a while now and like Rails has changed so dramatically since I started doing this, but that's not all that's changed. The community, the people in the room who's participating actively, what's getting committed has changed and also perhaps more importantly than that, I've changed a lot as well over the past several years. So I've been speaking at Ruby Central Conferences since RubyConf San Diego in 2014 and I just wanted to reflect a little bit on some of the memories I've been able to create through those conferences because you see when you give a tour, Confreaks records it and it goes up onto the internet forever and so your sort of unfortunate looks from the past get well and truly captured and for me this is great because I love this community, I've been working in it for such a long time and I've sort of grown up as an engineer with it so I get these like real time snapshots of who I was over several years including the one time I spontaneously transformed into Justin Searle's JK, that was not a fun experience. But so like the point I'm trying to make is that if you're new here, if this is your first time at a RailsConf, this is a great and incredible thing that we do. It's been literally one of the most life-changing things I've ever done and I would love it if you came back to RubyConf in LA or RailsConf in a year's time or so on and so on. Give a talk, participate, start a user group, go to a user group, this is an incredible community and I'm so proud to be a part of it. So without further ado, you all are probably in this room because you care a little bit about testing and I wanted to start by talking about the testing pyramid. So this is our testing pyramid, it basically defines one model of what a really well-designed test suite looks like and the idea of the testing pyramid is it captures two things. The first is how integrated or isolated any particular test is. So when you're writing unit tests, perhaps a model test, those are typically more isolated. They don't touch the entire system, they touch individual pieces of behavior and at the opposite end we have integrated tests, these are the ones that spin up a browser, hit your database, maybe send emails, hit Stripe, do third-party interactions, make phone calls, all of that stuff. They're as close to testing in the real world as you can possibly have. And basically what the testing pyramid tells us is that you should have larger numbers of isolated tests or unit tests and very, very few integrated tests. And there's a reason for this, right? If we come to the top of the testing pyramid and we talk about the most integrated of tests that we can write, well, we're probably spinning up a browser, probably making all kinds of fan-out requests, hitting the database, caching layers, third-party APIs and all of that stuff. And those tests are necessarily really slow. Like just by virtue of the fact they're doing all of this work across the system, those tests are really slow. And so what the testing pyramid basically advises is you should write as few of them as necessary to have confidence that your application is working. And the way that you do that usually is by testing happy paths in your integration suite and really not caring too much about edge cases. And that's a little bit of a generalization, but that's where I'm sort of gonna start. And then if we come to the opposite end, the isolated tests, well, these tend to be really, really fast. If I run bundle exec Rspec in the Rspec repo itself, you can see that the tests for Rspec basically all execute really quickly. And the reason for this is that most of what the tests in Rspec are doing are just testing pure pieces of Ruby functionality on their own. They're not going out to the file system. They're not doing any IO or anything like that. And so the overwhelming majority of them are really quick. And the point of this is that these, because these tests are fast, you can use them to test all of the edge cases in your system quickly and easily without having to worry about the details of everything that's going on. And you can see here that suite executed in just under 30 seconds. And so that's our testing pyramid and that's like a little bit of sort of high speed testing advice, but really today we're here to talk about the very most integrated part of our stack doing browser testing against our Rails applications. And I would just like to say that this used to be so awful. Like if anyone has ever tried to get a JavaScript Kappy Barra set up going in a Rails 4.2 app, you'll know this pain. But for those of you that haven't, I'd like to show you. So I went away and Googled Rails browser testing. And the first result that comes up is a link to Kappy Barra. And this is the point where I have to pause and say I am in no way trying to dunk on Kappy Barra. Kappy Barra is an excellent piece of tooling. It does everything that it's supposed to. It's just that the documentation and guides for this specific use case aren't that great. So I go to the Kappy Barra website. I follow the gem command. I add the require. I do a little bit of setup for a thing called Selenium. Selenium allows you to drive a browser programmatically from your Rails application and Kappy Barra knows how to integrate with it. So I do that. I literally copy their instructions exactly and I write this test. So I do all of the things they've asked and I run it and I get this blow up. Kappy Barra Selenium drivers unable to load gem blow up and I'm like, cool, I'll slap that gem in my gem file, run bundle install, run my test again. Then I get this error. Please add Puma to your application. This isn't gonna work. And I'm like, this is a Rails 4.2 application. I'm using Unicorn in product. Like Puma wasn't a default in Rails for a long time and I'm like, certainly you should be able to get a test response from an application without having this gem present. And I'm like, Kappy Barra, I have no idea why you're doing this. Please, could you not? Like this actually made me really angry when I saw this error. I was like, this should just work. It doesn't. So I took a deep breath and I went back to Google and the second result is this thing. This gem by FNando called browser, which does browser detection. Well, that's not really what I want. I'm not trying to detect a browser. I'm trying to test with a browser. And so I go back and then I get this. Testing a wrap in the browser with Kappy Barra, parentheses, Rails, backend, medium.com. Well, it's on medium, so it has to be accurate, right? So I click through and literally the first code sample is the exact error that I got. And I'm like, perfect. I'm probably onto a winner. So I copy the author's gem setup. I copy the author's Kappy Barra setup. I run my tests and everything passes. Easy, right? Well, let's just take a quick look at the code sample that that author gave us. Let's do a little bit of investigation. Firstly, the author is passing hyphen hyphen, ignore hyphen SSL errors equals yes to the phantom js configuration. And I don't know about you, but if I'm hitting the stripe sandbox or sending email in my integration tests, I would like to have reasonable confidence that SSL is working all the way through. That seems like a really strange default to me. But more importantly, this is using Poltergeist. And Poltergeist is like the sort of headless webkit with a JavaScript engine bolted onto the side thing, which means it's not a real browser. I suspect most of you don't browse the web on a command line by pausing responses out of an invisible browser that's running somewhere on your system. You probably use Chrome or Firefox. And I don't know about you, but the idea of being able to test correctly to me implies as realistic a scenario as possible. And I'd much rather have a real browser if I can. So I go back to Google and I'm scrolling and I'm scrolling and I'm scrolling and I eventually get to this. Headless Kappy Barra feature specs with Chrome robots.thoughtbot.com. Whenever I see a robots.thoughtbot.com link for something I'm frustrated by, it's usually the answer. The great folks at Thoughtbot have done a lot of writing over the years to make sure that we have really good resources as a community on how to solve our problems. And I click through and the author of the post is Derek Pryor who is sat right there. And I'm like, cool, my buddy Derek, he's a really great Rails consultant. I'm sure this will solve my problem. And this is what he wrote. Like here's all the setup you need to do to get a Google Chrome up and running against a Rails 4.2 application. And so I paste this in and I run my tests in low and behold browser window. Doesn't matter that there's an internal server error, by the way, this was a fresh app with no roots at all or routes, this is America. And so it's working and we've won and we can be calm now and go forth and test our applications happily. But I don't know about you, that sure seems like way too much work to be able to accurately test an application. Like Eileen in her keynote made this rallying cry for us to stop doing specialized things in order to solve these problems. And I'm not just building up to this for no reason, it turns out this problem is solved now. And in Rails 5.1 and 2 and aspect version 3.7, this problem is totally solved. And this all has to really start with a commendation to Eileen. So she gave a talk last year, building the new Rails system tests. System tests solve exactly this problem. It is a well-tuned, Rails optimized, Capybara browser configuration that just lets us run this test. And it's so easy that I don't really even have to talk about it too much because I can just play this video that starts with newing up a Rails application and literally we will be done faster than it would take for you to read a blog post on how to do this. Basically what's going on here is that we're setting up a Rails application, adding RSpec and the necessary configs in order to boot a browser test. And at this point I should say that in Rails 5.2 I'm about to make a configuration change to our Puma file. That is not actually necessary anymore in Rails 5.2. The Rails Puma config file out of the box does the right thing. So we've added RSpec to our Rails app, created the default RSpec install. And here's where I'm changing the Puma config to basically set the worker threads up to do the right thing. There's like a weird nurse where if the browser doesn't boot the Puma configuration correctly the system tests don't work, but like this is well documented if you're on Rails 5.1 and solved if you're on Rails 5.2. I should also say that this video is not sped up at all. This is just me working in real time. So then we open our spec file, load our Rails helper, do our standard describe. Here I initially typed foo and then I was like we should do something real here. It works. I also have the snowman as my trailing white space character. I think it's really nice. And then you add type system to your test. This is the magic that allows RSpec to pull in Rails' stuff. And then you bundle exact RSpec and you'll see at the very bottom there a little Chrome window pops up and like it's actually doesn't steal focus. I get a capybara error here instead of a parse test because I haven't added a root to my Rails router and like instead of the test parsing but it failing in the browser Rails system tests have it set up so the exception will actually get bubbled up back to you which I think is a much better interface. So anyway, I add a controller, add a root to home index from my tests one more time and you can see that now the tests all parse and there's a pause there while Chrome is boosting but it's not a long one. And then finally just to show you this is real I do the best programming thing in the world add a sleep statement, then Chrome boots. And you can see here that's it. Like literally that video sequence took less than two minutes to create and that's all you have to do to add system tests to RSpec in Rails 5.1. So that Puma configuration that I mentioned looks like this the important parts of the threads 0,1 and the work is zero. This basically tells Puma that the test should boot up the server thread instead of one being booted already for it. This is important so that the active record test setup something something can do the right stuff. I obviously don't understand it that well and work is zero. So that basically tells Puma not to boot any additional processes in the web server so processes being entirely memory separate to threads that would also cause our test break. In terms of the structure of the actual test itself this is all you need to do RSpec.describe and then some description for your test. You tag it as type system. I still use the hash rocket syntax because fight me and then you say it and some meaningful description and then you just have all the normal capybara commands available to you. Visit expect page to contain, tag selectors, JavaScript evaluation and all of that stuff and here I have that sleep statement copied in because I wanted to grab a screenshot of the browser. If you don't want a full web browser if you just want to assert on HTML instead of being able to execute JavaScript you can call this method called driven by and swap Selenium out for rack test and what rack test does is it basically fakes out the communication between the real browser with a fake one that capybara can then use to parse the responses without making a web request at all. It's pretty useful for just only testing simple responses. You can make assertions about what the page contains and that's again just showing you that all the capybara methods are included by default. That will fail and one of the things that I wanted to show you, this is really cool, is if you use item two and your test fails, the system testing will actually take a screenshot out of the page when the test fails and render it in your terminal for you. This is like amazing because this is like an extremely useful debugging utility and you'll also notice that it actually tells you what the path to the screenshot is so if you need to make it bigger you can go to temp screenshots, failures, aspect example groups, who fails and then some random number, I'll show you how that name gets generated in just a bit but this is an extremely useful debugging tool because usually when capybara blows up it's because it can't see an element or some text is misaligned or whatever and having that screenshot I found is really helpful. And so this is fire, right? Like look how easy that was. You just have browser testing now. There's no painful Googling, digging through blog posts, having to worry about like exactly which artisanal browser configuration you're doing. It just works and this is an amazing piece of tech that is developed inside Rails and I'm really glad that it's available in Aspect now. So like that's all the explanation of the feature I have. It's pretty well documented and you can go do further discovery on your own but it wouldn't be an Aspect talk without me digging a little bit into the framework and the implementation itself. So let's talk about how this actually got implemented. The first thing is that like I had known this was coming ahead of RailsConf last year but like really had no time to prep for it. So this integration within Aspect actually got started getting written at RailsConf last year when I was like well clearly Aspect needs to support this. The pull request actually landed just a handful of days after the conference and while like it wasn't completely done at that time I was really happy that I was able to get something that worked at all out as quickly as I did. The driven by method is provided to you by Action Dispatch itself and what people do when they're testing their applications with many tests is they write an application system test case that you sort of inherit from that has all your browser configuration. That implementation didn't really make sense in Aspect so we just added the driven by method to all of your system tests and then just called it to dispatch straight onto Action Dispatch to do the right thing. We then actually call that method in a before block for you within your test. So if you don't specify a preferred browser our before block gets evaluated and it defaults to Selenium which is aligned with Rails as defaults. And so what I'm trying to show you here is that Rails is really doing the heavy lifting. One of the things that was really great about this is that the API's Rails provided was sufficiently clean that it was very easy for me to just go ahead and add this into Aspect. The actual integration work was mostly dealing with Aspect's protocols not really having issues with Rails's. Let's talk about those screenshots. The way those work are actually really interesting because what's happening under the hood is the screenshot is being printed to standard out as a series of cleverly designed pixel characters. Like, it's bizarre magic. So just like we added a before hook to your test we also add an after hook. What we do is we grab standard out and then basically rewrite it to be a string IO. For those of you not familiar with string IO it's basically a thing that you can write to and it will build up an internal string and then you can read from it to get that string back. So it looks just like an IO object but it's sort of like an in memory string. One of the things that's like kind of weird is this original after tear down bind self call. So let's take a quick look at that. What we're actually doing here is grabbing the instance method after tear down off the action dispatch system testing helper and the reason that we do this is the action dispatch hook is set to execute when the test finishes but that's not when Aspect prints failure output. Like, when you look at an Aspect failure trace where the output is is right at the end of the test suite run. It's not in line as tests are failing and so we have to sort of re-order the way in which these things get printed and this is how we do it. The instance method method allows you to pull instance method objects off other objects and then put them on your object at a later time and that's what that call to bind self does. It says take this method, rebind it to me and then invoke it. The way that we actually get those failure lines written into the Aspect output is through this protocol. So it turns out that Aspect already had a protocol to add failure lines into the output. So I actually didn't have to even go into Aspect core and rebuild that piece of the framework. It was already working which was kind of great. And then the last thing that we have to do is make sure that the rails after tear down hook never gets called. So we have this method blow away off of this module blow away after tear down hook which literally just defines after tear down to do nothing and then we include it when we get included. It's not quite monkey patching but it's really close. And then finally the one thing we have to do is tell action dispatch whether or not our test has passed. While it's really easy for a mini test test to know if it has passed or failed as you can see it's a little bit more complicated in Aspect. But again, like rails is making all of this really easy. I'm not having to do all that much work at all to get these rails features integrated with Aspect and the point that I kind of wanted to make is this. I think one of the things the rails team does really well is design a sort of inner layer of API underneath the layer of framework that you as a Rails user call. And this makes my job as an external library developer really easy because Rails is providing these great rich APIs and they're just the right amount of pluggable. I'm able to integrate them into Aspect without too much difficulty at all. And I think that's a really valuable thing. So as much as I would like to be able to say that this was a clean and easy integration that's not really the case. With any major feature release like this there are going to be bugs. And so one of the very first things I did after I got that pull request up is I went to my network and to like social media and I was like, hey folks this is available please give it a try. And as someone who had demonstrated himself in being interested in browser testing Derek was immediately like here are all the problems. We use feature instead of describe please fix that turned out to be impossible. I was not able to set the driver in a global before hook. Now we know why. I'm calling before in every single test for you so a global hook can't really override that. I could probably do some magic with like a before all and some global variables and stuff but never got around to fixing that. And then we have this like screenshot thing. And originally the screenshots just got called screenshots failures underscore and nothing. And the reason for that is really interesting and I actually fixed this one so I'd like to talk a little bit about how that worked. Basically what mini test relies on to know what your test is called is the method name that's currently being invoked because in mini test all tests are methods and so it can just call method name to get the right answer. And so in that way that we love to do monkey patching in Ruby I just went ahead and over road method name on an aspect test to do this thing. This is actually a surprisingly good human readable description of your test. So it takes the class name which will usually be like aspect example groups some anonymous or like a description of your test and then the actual string description but replacing all the spaces with underscores and also doing a bunch of natural language scrubbing and then random thousand because if you run the same test multiple times you may want screenshots from different runs and so this random thousand is kind of a poor man's way of making those unique. So fixed, the next one is this which is like ultimate no no for RSpec is Puma printing stuff in your green dots. I don't know about you but I cannot have stuff in my green dots it's unacceptable. And fixing this required actually going into Rails because Rails does the Puma configuration for us and so while implementing this feature I actually landed a poor request in Rails in order to make this possible and this is like one of those great Ruby open source moments that I'd sort of like to highlight, right? I'm a third party maintainer I don't really have any ability to dictate to Rails team what they should be doing with their time but I went ahead and sort of motivated this poor request wrote a simple patch I would like to say that there was some discussion but I just sent it to Sean and he merged it straight away because YOLO I have a contract with Sean that says I have to have at least one picture of his baby in all my talks because I'm his baby's godfather. That's great. Anyway, so fix that because this was released later in the series than when we released compatibility we put the check behind a respond to if Puma responds to it we call the true and then like no more crap in your green dots because oh my God I can't be crap in your green dots and like cool merging core requests so then then the actual worst thing that you can do when you release a new feature in a gem like aspect happened which is we broke people's test suites for people who weren't on rails 5.1 and weren't using this feature and the reason is that there was like a load order issue where we were assuming you would have a capybara present but people didn't and then their entire test suite would stop working which is like really bad so you know it's fine to rescue load error and abort with some like useful error message and the point that I might make here is that this open source software is wildly imperfect we are people working our spare time we are unpaid it's evenings and weekends last spec maintainers do not as a rule get paid to do what they do and you know the MIT license gives us a get out clause for that. The software is provided as is without warranty of any kind express or implied including but not limited to the warranties of merchant ability fitness for a particular purpose and non infringement in no event shall the authors or coffee right holders be liable for any claim damages or other liability I can break everything you do and you can't sue me it's what that says so that's all I got thank you so much just as a quick shout out my cool team from digital ocean is here it's a funny story actually so I had I was a developer on my own for about nine months I didn't have a team because we're trying to recruit them we came to RubyConf and I just like bam immediately had two developers who wanted to join my team it was wonderful I love hiring from this community because you are my people and while we don't do rails as actively at digital ocean as we used to it's still a very heavy component of our stack and I think it's so cool that after being in this community for so many years I was able to sort of hire people I really like working with out of it so shout out to Chris and Kinsey if they're in the room and Dan who is not with us but I'm sure we'll watch this talk at some point we have a booth check it out there's swag there's a demo with a giant button it's really cool yeah and I will actually be down there after this happy to take your questions and have a chat with you for sure and that's all I got thank you so much I'm Sam Phippen on Twitter you can email me as Phippen at digital ocean cheers