This article will dive deeper into how to handle popups with selenium webdriver, and how to automate them. This is a blog post provided by Mark Collin on EuroSTAR Huddle, our online community for free software testing resources.
Demystifying popups with Selenium
After you have been working with Selenium for a while you will eventually be given a task to automate something that has some sort of popup functionality. Popups can be a sticky subject in Selenium because the definition of what a popup is can be unclear and different drivers exhibit different default behaviour. In this article I will try and provide some clear definitions of different popups, show you how to handle Popup in Selenium Webdriver and show you how to automate them, and a few gotchas as well.
First of all we need to define what we mean by a popup; it’s a nebulous term that people use to describe multiple different types of functionality that is out in the wild. I like to put popups into three main categories:
OS level popups
The first popup listed is an OS level dialogue; these are the things you will see when the browser hands over control to the operating system to do a task for it (although browsers like chrome are blurring the lines here a little bit by making it look like the browser is doing it). You will commonly see these if you do something that the browser cannot do, at this point the browser will hand over to the OS to find another program to perform the required task. Some examples of these things are:
- Printing a page you are viewing in the browser.
- Sending a link as an email.
- Downloading a file.
The best option to deal with these OS level popups, is to never do anything that will trigger them in your test. Selenium tutorial is only designed to interact with websites rendered in
the browser, controlling the OS is totally out of scope. If you did want to try and interact with these OS level popups you would need to write some code that can control the
OS itself (e.g. something using the Java Robot classes). There are tools out there that will do this, but it’s a complex bit of work and you really don’t need to test OS level functionality to check that your website works.
HTML popups (Lightboxes)
A HTML popup can look like a JavaScript popup, or a OS level popup, but in reality it’s just more HTML layered over the top of existing HTML on the website. Due to the fact that these popups are HTML website designers can use CSS to make these things look like something they are not, which can cause confusion. You don’t need to do anything special to work with these; after all they are just HTML and CSS. You just interact with them in same way that you would interact with any other element on screen and then do what you want.
JavaScript popups
These are blocking popups; they will pause JavaScript execution until they have been interacted with. Historically, these were the default type of Selenium popup, although nowadays they are not quite as common because website designers tend to prefer using non-blocking HTML popups instead. These are things that Selenium can interact with, but you will need to know what actions are going to trigger these popups in advance.
We should now have a clear idea in our mind of the various types of popup. Next, let’s have a look at some code.
Working with alerts
First of all, we are going to look at how to automate things alerts. To start with, we will need some HTML to automate. Save the following html as ‘alert.html’:
private String alertFileLocation = “file://<PATH_TO>/alert.html”;
<!doctype html> <html lang="en"> <head> <title>Alerts in Selenium</title> <link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script> function triggerAlert() { $('#alert').dialog(); } </script> </head> <body style="background-color: #6cb5ff"> <div id="buttons"> <button id="js_alert" onclick="alert('This is a JavaScript Alert');">Click me for a basic JavaScript alert</button> <button id="html_alert" onclick="triggerAlert()">Click me for a basic jQuery alert</button> </div> <div id="alert" title="Dialog" style="display: none"> <p>This is an alert box triggered by JavaScript, but rendered in rendered in HTML</p> </div> </body> </html>
This is a simple page that has a couple of buttons. One will trigger a HTML alert, and one will trigger a JavaScript alert. Load it up in the browser, and try it out to see how it works.
We are now going to write some code to automate this page. First of all, we need to define the location where you saved the above HTML. Just replace in the below code with the path to the file you saved above.
private String alertFileLocation = “file://<PATH_TO>/alert.html”;
Now on to our first basic piece of automation:
@Test public void triggerBothPopups() throws Exception { driver.get(alertFileLocation); WebElement generateAlertOne = driver.findElement(By.id("js_alert")); WebElement generateAlertTwo = driver.findElement(By.id("html_alert")); generateAlertOne.click(); generateAlertTwo.click(); }
This code will load up the web page: click on the button to trigger the JavaScript alert, and then click on the button to trigger the HTML alert. If you now run this test it should fail, and you should see an error that looks like this:
org.openqa.selenium.UnhandledAlertException: unexpected alert open: {Alert text : This is a JavaScript Alert}
However you may not have seen an error…
This is because different drivers are implemented differently; if you are running Firefox v53.0.3 and GeckoDriver v0.16.1 it will have ignored the fact that there is a popup there and tried to carry on regardless. We will come back and look at this later on. For now, let’s make sure we are using chromedriver.
Now that we are using chromedriver, let’s try and fix our automation code. Selenium provides an API that enables you to interact with alerts, so let’s use it.
@Test public void triggerBothPopups() throws Exception { driver.get(alertFileLocation); WebElement generateAlertOne = driver.findElement(By.id("js_alert")); WebElement generateAlertTwo = driver.findElement(By.id("html_alert")); generateAlertOne.click(); driver.switchTo().alert().accept(); generateAlertTwo.click(); driver.switchTo().alert().accept(); }
As you can see, we are now telling Selenium to switch to the alert once it has popped up. Once we have switched to it, we are telling Selenium to accept it. Let’s run the test again and see what happens now.
You should now see the following error:
org.openqa.selenium.NoAlertPresentException: no alert open
This is because our first alert is a JavaScript popup, but our second one is a HTML popup (or a Lightbox). The functionality to switch to an alert and accept it will only work with JavaScript popups, not HTML popups. To interact with the HTML popup we will need to look at the markup, and deal with it like we would any other HTML element.
Here is our final bit of test code.
@Test public void triggerBothPopups() throws Exception { driver.get(alertFileLocation); WebElement generateAlertOne = driver.findElement(By.id("js_alert")); WebElement generateAlertTwo = driver.findElement(By.id("html_alert")); generateAlertOne.click(); driver.switchTo().alert().accept(); generateAlertTwo.click(); WebElement closeHtmlAlertButton = driver.findElement(By.cssSelector(".ui-button")); closeHtmlAlertButton.click(); }
We are now treating each type of alert correctly, and we have one happy piece of automation.
Why the test didn’t fail in Firefox
Continuing with our Selenium tutorial, let’s go back to the problem with Firefox that we mentioned when we wrote our initial test. Firefox was not throwing an exception when an unexpected alert was present, so we weren’t seeing an error. If something like this happens it can be very confusing. The main reason our test was not failing was because we were not performing any checks after
interacting with the elements. Let’s take the final test that we wrote and delete the code that switches to the alert, and see what happens when we run it in Firefox v53.0.3 and GeckoDriver v0.16.1.
@Test public void triggerBothPopups() throws Exception { driver.get(alertFileLocation); WebElement generateAlertOne = driver.findElement(By.id("js_alert")); WebElement generateAlertTwo = driver.findElement(By.id("html_alert")); generateAlertOne.click(); generateAlertTwo.click(); WebElement closeHtmlAlertButton = driver.findElement(By.cssSelector(".ui-button")); closeHtmlAlertButton.click(); }
Now this time the test will fail, but you will see the following error:
org.openqa.selenium.NoSuchElementException: Unable to locate element: .ui-button
It has failed because we can’t find the button that is used to close the HTML popup; the implication here is that clicking on the second generate alert button did not work. Now we know that this is not the case, this test is failing because we have removed the code that switches to the alert and accepts it. Can we make this clearer by writing a screenshot of what the test is seeing to file?
@Test public void triggerBothPopups() throws Exception { driver.get(alertFileLocation); WebElement generateAlertOne = driver.findElement(By.id("js_alert")); WebElement generateAlertTwo = driver.findElement(By.id("html_alert")); generateAlertOne.click(); generateAlertTwo.click(); File screenshot = new File("screenshot.png"); FileOutputStream screenshotStream = new FileOutputStream(screenshot); screenshotStream.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES)); screenshotStream.close(); System.out.println("Screenshot saved at: " + screenshot.getAbsolutePath()); WebElement closeHtmlAlertButton = driver.findElement(By.cssSelector(".ui-button")); closeHtmlAlertButton.click(); }
Let’s run the test again; this time a screenshot will be generated and the file location will be shown in the console log. This should show us that JavaScript alert that is causing all of the problems.
If you now open up the screenshot and look at it, it’s probably not going to show you what you expected. The screenshot shows the rendered website with no JavaScript or HTML alerts. What’s going on?
JavaScript popups are not part of the website that is being rendered, they are instead rendered by the browser. As a result they don’t show up in screenshots taken by selenium. To all intents and purposes, it looks like we have found a bug with the website implementation. It looks like clicking on the second button does not trigger an HTML alert to be shown. However, if you go back and test manually you will see that it works.
We know what the problem is, but imagine how confusing this would be to somebody who didn’t understand that we had a mixture of different types of popups. You would probably raise a bug stating that the second button didn’t work and pass it back to a dev who would not be able to reproduce the problem (because the only problem that exists is a problem with the test code).
Working with other types of JavaScript popup
So far we have looked at only one type of popup, the alert. There are two other types of JavaScript popups that you may come across, the prompt popup and the confirmation popup. Let’s create the last bit of HTML we are going to automate, save the following as ‘popup.html’:
<!doctype html> <html lang="en"> <head> <title>Prompts in Selenium</title> </head> <body style="background-color: #6cb5ff"> <div id="buttons"> <button id="js_confirm" onclick="confirm('This is a JavaScript confirmation box, you can select OK or Cancel.');">Click me for a basic JavaScript confirmation box</button> <button id="js_prompt" onclick="prompt('This is a JavaScript Prompt, it wants to get some information.');">Click me for a basic JavaScript prompt</button> </div> </body> </html>
As before, we need to define the location where you saved the above HTML. Just replace in the below code with the path to the file you saved above.
private String popupFileLocation = “file:///popup.html”;
First of all, we will look at how we can interact with a confirmation popup. Confirmation popups will ask you a question that you can either agree to, or cancel out of. They will always have two options, ‘OK’ or ‘Cancel’. Popups like this are usually used to block code execution until you agree to something.
Let us assume that we are going to agree with the confirmation popup first of all.
@Test public void triggerJavaScriptConfirmation() throws Exception { driver.get(popupFileLocation); WebElement generateConfirmationPopup = driver.findElement(By.id("js_confirm")); generateConfirmationPopup.click(); driver.switchTo().alert().accept(); }
The code is very simple, in fact it’s exactly the same as the code we used to automate the alert popup. If we don’t want to agree with the confirmation popup the code is just as simple:
@Test public void triggerJavaScriptConfirmation() throws Exception { driver.get(popupFileLocation); WebElement generateConfirmationPopup = driver.findElement(By.id("js_confirm")); generateConfirmationPopup.click(); driver.switchTo().alert().dismiss(); }
The last type of popup we are going to look at is the JavaScript prompt. These are blocking popups that are used to get some information from you. When you use these types of popup you are probably going to be asking the user a question that they need to respond to, so we will have a look at how we can check that the right question is being asked as well as entering the required information. Here is our code:
@Test public void triggerJavaScriptPrompt() throws Exception { driver.get(popupFileLocation); WebElement generatePromptPopup = driver.findElement(By.id("js_prompt")); generatePromptPopup.click(); Alert prompt = driver.switchTo().alert(); assert(prompt.getText().equals("This is a JavaScript Prompt, it wants to get some information.")); prompt.dismiss(); }
This time, after triggering our JavaScript prompt, we have decided to define an Alert object. We are going to be interacting with the alert more than once so it makes sense to do so for code clarity and cleanliness. We have then used the getText() command to get the text that is being displayed to the user and made an assertion to check that it matches our expected text. We have then decided that we don’t want to provide any further information and dismissed the alert.
What about if we decided that we did want to send over some information? In that case we would have written the following code:
@Test public void triggerJavaScriptPrompt() throws Exception { driver.get(popupFileLocation); WebElement generatePromptPopup = driver.findElement(By.id("js_prompt")); generatePromptPopup.click(); Alert prompt = driver.switchTo().alert(); assert(prompt.getText().equals("This is a JavaScript Prompt, it wants to get some information.")); prompt.sendKeys("Some information"); prompt.accept(); }
As you can see this time we have sent some information over using the sendKeys() command and then accepted the prompt. We could of course have still decided to dismiss the prompt after entering some information, but that’s a scenario we haven’t written. You could always try that one yourself.
It is worth mentioning once more that different driver implementations can act in many different ways, and different driver implementations can support different parts of the WebDriver API. If you are having problems it’s always worth trying a different type of driver to see if your code can work.