# 14: Testing Web UIs ###### tags: `Tag(sp22)` ## Logistics ### Travel Several of the staff are experiencing travel problems. This is also true of a few of you! Please exercise patience as we get back to campus. ### Sprint 4 Sprint 4 goes out today! Some details (e.g., handin instructions) will be filled in later. We've kept the handouts relatively short. More info will be in the gear-up, coming soon. If your group did not complete all of Sprint 3, speak with your mentor---e.g., if there is no starting screen-reader, you need to arrive at an appropriate target (the full Sprint 4 screenreader functionality is not appropriate for a single sprint). By default, one group member will be working on an enhanced screenreader that navigates within tables. The others will be building an improved table view and a static Kanban visualization in React, and _testing_ them. Note that the UI testing parts are _entirely new_, and so TAs may be more unfamiliar with issues that arise. We've tried to design the deliverables to account for this. ### Mid-Semester Feedback Do this! It helps---but it also gives you points. ### Mentor Meetings There is no Sprint 4 meeting this week. Instead, have your "sketch" check-in. ### "Stencil Code" Think of what we'd previously called "stencil" as "starter" code---you're allowed to modify it. It presents a possible way of getting started, and there are often other options. ### Looking Ahead This Thursday we'll talk about security, the GDPR, and threat modeling. We'll expect some of this for your term projects. To give you time on term-project specs, design, etc. We won't have a class meeting _next_ Tuesday. _Next_ Thursday will be devoted to cross-group feedback on preliminary specs, design etc. Make sure every group is represented in class! On the 19th and 21st, we'll have two guest lectures from former students! Alberta Devor (Google) will speak on the 19th, and Anjali Pal will speak on the 21st. **Don't miss these!** We're likely to have one more guest lecture, so stay tuned. I've created a Google calendar with 0320 and 1710 guest lectures combined, [here](https://calendar.google.com/calendar/embed?src=c_npj8brm6vkp1bjj2747tajk2i8%40group.calendar.google.com&ctz=America%2FNew_York). As the semester wraps up, we'll (exact dates TBD): * have a lecture on what we can do __beyond testing__ to build confidence in high-impact software; and * close with discussion of 0320 development, and Tim will give some parting advice. ## Testing Web UIs Many of you have wondered how you can possibly _test_ your web UI. Indeed, if your primary interface is a web UI: * a true _system test_ should actually test inputs and outputs given by the interface; and * you might also wish to test some portions of the UI behavior itself, like making sure buttons can be clicked etc. While this isn't unit-testing in the classical sense, we'll call it this in 0320. But unlike a REPL, where you can paste in commands (or script them!) and parse the output, a web-based UI is tougher to automate interaction with. Thus, we told you that for Sprint 3 we didn't expect you to test your _UI_ at all. That changes now! ## Selenium Today we'll introduce a commonly used tool for scripting browser interactions: Selenium. There are other such tools, but this is the one we'll be using this year. Selenium isn't just used for testing---it's (probably primarily) a tool for build smart web scrapers. The thing that makes it good for both these applications is that it works by spawning and controlling a real browser window. The general shape of this guide is inspired by [Selenium's getting-started page](https://www.selenium.dev/documentation/webdriver/getting_started/), but we're going to adapt the tutorial flow to our own needs. Concretely, we'll be testing the React app from leture---this way you'll see the process and various pitfalls of going beyond Selenium's "hello world" example. **Let's start by setting Selenium up.** The first part of this class is meant to help you be ready for using Selenium on Sprint 4. ### Step 1: Get the Selenium library for Java Add this dependency to Maven and reload. ```java= <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>4.0.0</version> </dependency> ``` ### Get the Browser Agent You Want Since Selenium scripts interaction with a real browser, you need to install the Selenium driver for the browser(s) you want to test on. Note that, because the drivers are separate, you can indeed write tests using multiple drivers and thus check compatability with different browsers. **NOTE**: make sure you have the browser installed! The driver will make sure Selenium can run the browser---but you need the browser installed. This means if you're a MacOS user, you either need to install Firefox (or Chrome, or...) or run Safari. If you want to run Safari, you'll need to enable an option in the web-dev menu. See the exception message for the exact option name. (The driver is built into Safari---but you need to enable remote control.) #### Option 1: Driver Manager Before writing these notes, I'd always manually downloaded the driver I wanted. But recently I discovered an open-source project called [WebDriverManager](https://bonigarcia.dev/webdrivermanager/). This is now my preferred startup method. Just navigate to the project page, scroll down to "Setup" and install the Maven dependency given. #### Option 2: Manual Install Drivers are available for most browsers. The driver for Safari should come built in on MacOS, but today I'll be using the [Firefox driver](https://github.com/mozilla/geckodriver/releases). Go to the above page; you should see a screen like this: ![](https://i.imgur.com/PV832in.png) Scroll down... ![](https://i.imgur.com/lVqhRf2.png) ...and get the tar.gz for your OS. This is a compressed archive. Extract it to a folder. Inside is a geckodriver executable. If you're not using the driver manager, you'll need to register this driver's location as a system property, e.g., ```java String geckoURL = "C:\Users\Tim\repos\cs32\selenium\geckodriver.exe" System.setProperty("webdriver.gecko.driver", geckoURL); ``` **NOTE**: If you're using the manual example, make sure to replace the above directory with the location of your driver! Otherwise it will try to look in Tim's `User` folder for the file. And that's unlikely to succeed. But this is error prone; you need to make sure the driver is either given as an absolute path, or available to Java. #### A Possible Error If you get an error like: ``` SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation ``` Note your dependencies in `pom.xml`; 0320 installs SLF4J by default, so this problem shouldn't occur if you're using a 0320 `pom.xml`. Make sure your project is a Maven project! ### Let's try it! I've prepared a live-code example for us to follow [here](https://github.com/tnelson/cs32-livecode/tree/main/S22/src/main/java/edu/brown/cs32/spring22/livecode). Depending on whether you want to try the driver manager or manual download, pick either the `apr05Starter` or `apr05StarterManual` package. If you haven't used the livecode repo before, I suggest cloning it locally, so you have everything: all the starter code, the full example, and (vitally) the `pom.xml` file with dependencies. Make sure that you're importing `S22` as a Maven project, **NOT** `F21` or the parent directory. The code we write today will be very rough; we just want to explore the package and demonstrate how you could use it in your tests. The in-class demo doesn't use JUnit, but you can use Selenium to power JUnit tests just like everything else you use. ### Exceptions The [StaleElementReferenceException](https://www.selenium.dev/exceptions/) is common when you're writing your first Selenium tests. You'll get this if you try to refer to an [element](https://www.selenium.dev/documentation/webdriver/elements/) that no longer exists, and so can no longer be interacted with. ### Timing Timeouts are subtle. Remember that the page doesn't all load at once, and sometimes you need Selenium to _wait_ before continuing, so the page can be in the proper state. In simple examples we can do something like this: ``` driver.manage().timeouts().implicitlyWait(Duration.ofMillis(500)); ``` Which makes the driver wait implicitly for half a second. You won't need anything more complex than that for Sprint 4. But there are better alternatives, like telling Selenium to [wait _until_ something has happened](https://www.selenium.dev/documentation/webdriver/waits/). ### Final Demo Code You can find the completed demo (at least, the demo as planned, and not including any pivots or questions) in the [apr04 package](https://github.com/tnelson/cs32-livecode/tree/main/S22/src/main/java/edu/brown/cs32/spring22/livecode/apr05). You can find a (possibly stale version of it) within the spoiler tags here: <details> <summary>Full version</summary> ```java public class Main { public static void main(String[] args) { // There will be various messages in the console... // (By default, Selenium prints a lot of info) WebDriverManager.firefoxdriver().setup(); runExample("https://tnelson.github.io/reactNYT/"); runExample("file:///Users/tim/repos/reactNYT/vanilla-app/index.html"); } static void runExample(String path) { FirefoxOptions options = new FirefoxOptions(); FirefoxDriver driver = new FirefoxDriver(options); driver.manage().timeouts().implicitlyWait(Duration.ofMillis(500)); driver.get(path); System.out.println(driver.getTitle()); // lots of ways to "get an element": WebElement new_round = driver.findElement(By.className("new-round")); System.out.println(new_round); // Don't get _all_ inputs, just the inputs in the new-round div List<WebElement> inputs = new_round.findElements(By.tagName("input")); WebElement submit = new_round.findElement(By.tagName("button")); System.out.println(inputs); int val = 1; for(WebElement in : inputs) { in.sendKeys(String.valueOf(val)); val++; } submit.click(); // Beware: what happens if we re-use the same WebElement values? for(WebElement in : inputs) { in.sendKeys(String.valueOf(val)); val++; } submit.click(); // We're actually OK! But this isn't guaranteed. // Selenium uses locators to define where elements live; // these can become stale, etc. // Ok, now let's actually go /test/ something // We've submitted twice, so we should expect to see 2 guesses // In fact, they ought to be both correct, based on the numbers we used. // Remember to use "className" not "tagName", since these are divs List<WebElement> correct = driver.findElements(By.className("correct-guess")); System.out.println("This better be 2: "+correct.size()); // ^ In practice, that would be a JUnit assert // But wait, this isn't working for both versions of the app... driver.quit(); } } ``` </details> ### Important Caveats There's a lot more to using Selenium than we can cover in a half-lecture block. Our goal today is to get you set up and able to use Selenium for very basic UI tests. For more information on how to use Selenium, see their [Best Practices](https://www.selenium.dev/documentation/test_practices/encouraged/) guide. One best practice we urge you to follow in Sprint 4 and beyond is: creating a fresh browser for _every single test_. This avoids potential issues caused by forgotten browser state being shared between tests. ## Ethical Concerns Use Selenium ethically. The same power that lets you automate your front-end tests allows you to scrape websites for data---which is prohibited by many, especially if they also provide a web API. ## Term Project and Selenium-Setup Time (The remainder of this class meeting will be Q&A about the term project and help getting Selenium set up.)