Currently practicing running TestNG tests on the Lord & Taylor online site and I have an issue where a overlaying pop up from Lord and Taylor pops up on the screen at least once during a session. Most of the time it appears on the Homepage but can also appear once I click on one of the sections. I have tried using the xpath but it seems to change every time I run the browser. I have also tried using link text but that doesnt seem to work either. I can use an Implicit wait and manually click out of it but I feel like there has to be a way to do this with code. I also run into a problem where sometimes the ad wont load at all so it allows me to complete the initial few tests before it pops up out of nowhere. Its become a hurdle for me that I cant seem to get over. Ive attached what my code looks like now and can really use some help figuring this out.
package com.LT.Tests;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.LT.Pages.googlePage;
import com.LT.Pages.homePage;
public class homepageTest {
WebDriver driver;
googlePage gp;
homePage hp;
#BeforeTest
public void beforeTest() throws InterruptedException {
System.setProperty("webdriver.chrome.driver","/Users/nezamseraj/Desktop/WSA/Drivers/chromedriver 5");
driver = new ChromeDriver();
hp = new homePage(driver);
gp = new googlePage(driver);
driver.manage().deleteAllCookies();
Thread.sleep(2000);
driver.get("https://www.google.com/");
driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
}
#AfterTest
public void afterTest() {
driver.quit();
}
#Test
public void gpValidation() {
boolean searchbar = gp.searchBar().isEnabled();
boolean searchbutton = gp.searchbutton().isEnabled();
Assert.assertTrue(searchbar);
Assert.assertTrue(searchbutton);
}
#Test(priority = 2)
public void searchLT() {
gp.searchBar().sendKeys("Lord and Taylor");
gp.searchbutton().click();
gp.ltLink().click();
}
#Test(priority = 3)
public void dismissAD() {
WebDriverWait wait = new WebDriverWait(driver,30);
wait.until(ExpectedConditions.elementToBeClickable(By.linkText("DECLINE OFFER")));
hp.declineOffer().click();
}
#Test(priority = 3)
public void hpValidation() {
boolean ws = hp.womenSection().isEnabled();
boolean ds = hp.designerSection().isEnabled();
boolean as = hp.accessoriesSection().isEnabled();
Assert.assertTrue(ws);
Assert.assertTrue(ds);
Assert.assertTrue(as);
}
}
We are using QAF and utilizing qaf webdriver and webelement listeners for to deal with random popups.
You can add retry in element or driver listener's onfailure method. Ideal example is unexpected or random popup that needs to be dismissed. The implementation may look like below:
public void onFailure(QAFExtendedWebElement element, CommandTracker commandTracker) {
//check and close popup if exist
boolean shouldRetry = closePopUpIfExist();
if (commandTracker.hasException()
&& commandTracker.getException() instanceof StaleElementReferenceException) {
element.setId("-1");
shouldRetry = true;
}
commandTracker.setRetry(shouldRetry);
}
closePopUpIfExist implementation may look like below:
private void closePopUpIfExist(){
QAFExtendedWebElement popup_declineEle = new QAFExtendedWebElement("link=DECLINE OFFER");
if(popup_declineEle.isPresent() && popup_declineEle.isDisplayed()){
popup_declineEle.click();
return true;
}
return false;
}
UPDATE:
With qaf take benefit of base test class and base test page
public class homepageTest extends extends WebDriverTestCase {
#Test
public void gpValidation() {
GooglePage gp = new GooglePage();
gp.launchPage(null);
gp.getSearchTextbox().verifyEnabled();
gp.searchbutton().verifyEnabled();
// this test case doesn't makes sense because google is not your AUT and here you are testing google functionality!...
}
#Test(priority = 3)
public void hpValidation() {
GooglePage gp = new GooglePage();
gp.launchPage(null);
gp.getSearchTextbox().sendKeys("Lord and Taylor");
gp.searchbutton().click();
gp.ltLink().click();
HomePage hp = new HomePage();
hp.womenSection().verifyEnabled();
hp.designerSection().verifyEnabled();
hp.accessoriesSection().verifyEnabled();
}
}
Your page class may look like below:
public class GooglePage extends WebDriverBaseTestPage<WebDriverTestPage>{
//declare elements
// you can use #Findby from selenium support or from qaf
#FindBy(locator = "name=q")
private QAFWebElement searchTextbox;
...
protected void openPage(PageLocator loc){
driver.get("https://www.google.com/");
}
public QAFWebElement getSearchTextbox(){
return searchTextbox;
}
...
}
public class HomePage extends WebDriverBaseTestPage<WebDriverTestPage>{
//declare elements
// you can use #Findby from selenium support or from qaf
protected void openPage(PageLocator loc){
driver.get("/");
}
}
You may observe that there is no code to create/teardown driver. That is done through properties.
driver.name=chromeDriver
I would suggest checkout sample project. It has basic google search test case available with example configuration. you can download from github and run as maven test for playing around.
Related
everyone, I'm struggling with the following situation.
Returned driver is not recognized.
I want to make a method to used it for rest of the methods for a test suite but
package acceptanceTesting
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.testng.annotations.BeforeSuite;
public class LoginTestingStep {
#BeforeSuite
public static WebDriver driver() {
System.setProperty(WebDriverPage.webDriverAdress, WebDriverPage.webDriverPath);
WebDriver driver = new ChromeDriver();
driver.get("https://evernote.com/");
return driver;
}
public static void WaitForElementVisible(String option) {
WebDriverWait wait = new WebDriverWait(driver(), 5);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(option)));
}
#Test
public static void UnauthotisedLogin() {
driver.manage().window().maximize();
driver.findElement(By.cssSelector(LoginTestingPage.loginButton)).click();
driver.findElement(By.cssSelector(LoginTestingPage.emailAdreesField)).sendKeys("spacesiatat#yahoo.com");
WaitForElementVisible(LoginTestingPage.continueButton);
driver.findElement(By.cssSelector(LoginTestingPage.continueButton)).click();
// TimeUnit.SECONDS.sleep(5);
// driver.close();
}
public static void AuthotisedLogin() {
}
}
Simple advice first: Do not make return anything when using TestNG annotations (like #BeforeSuite and etc.) Because everytime you want to call this method as parameter it will open new Chrome Browser.
Since you are creating driver in the inside of driver() method
No any other methods can see that there is driver already defined. Insted use it like global variable, not inside method.
public class LoginTestingStep {
WebDriver driver; //declare as global
#BeforeSuite
public static void driver() {
System.setProperty(WebDriverPage.webDriverAdress,
WebDriverPage.webDriverPath);
driver = new ChromeDriver(); //then create instance
wait = new WebDriverWait(driver, Duration.ofSeconds(5));
driver.get("https://evernote.com/");
}
public static void WaitForElementVisible(String option) {
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector(option)));
}
#Test
public static void UnauthotisedLogin() {
driver.manage().window().maximize();
driver.findElement(By.cssSelector(LoginTestingPage.loginButton)).click();
driver.findElement(By.cssSelector(LoginTestingPage.emailAdreesField)).sendKeys("spacesiatat#yahoo.com");
WaitForElementVisible(LoginTestingPage.continueButton);
driver.findElement(By.cssSelector(LoginTestingPage.continueButton)).click();
}
}
**What I changed in your code? **
Made driver() method to be return type void instead of WebDriver return type
Deleted line that include: return driver;
Made WebDriverWait global to enable using it in other methods as well and avoiding repeating creating instance of it every time
Added 'DurationOfSeconds' to 'WebDriverWait' constructor parameter since raw usage of second is deprecated
I'm trying to use only PageFactory in my project, without using fields with type By. And I'm looking to implement something like this:
#FindBy(className = "loading-container")
private WebElement loadingElement;
public LoadingPage(WebDriver driver) {
PageFactory.initElements(driver, this);
this.waitDriver = new WebDriverWait(this.driver, 20);
}
public void waitLoadingToFinish() {
this.waitDriver.until(ExpectedConditions.elementNotExists(loadingElement));
}
Is there a way to implemet custom Expected Condition for this? or any other way to implement this? (without using By fields, only using page factory).
As far as I understand you have some elements on the page that you consider ready for usage only when there is no certain element on the page (like waiting wheel).
There is a special locator class in Selenium called AjaxElementLocator. What you need to do is to extend that type by changing isElementUsable method when you initialize your page so that all the controls you use on that page would first check the condition. Here is the example:
package click.webelement.pagefactory.conditions;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.AjaxElementLocator;
public class LoadablePage {
#FindBy(id = "cntrl-id")
WebElement control;
public LoadablePage(WebDriver driver){
PageFactory.initElements(field -> new AjaxElementLocator(driver, field, 10){
#Override
protected boolean isElementUsable(WebElement element) {
return driver.findElements(By.xpath("//WHEEL_XPATH")).size() == 0;
}
}, this);
}
}
Here you can find more information on such approach.
Selenium has this method
ExpectedConditions.InvisibilityOfElementLocated
An expectation for checking that an element is either invisible or not
present on the DOM.
For your code
public void waitLoadingToFinish() {
this.waitDriver.until(ExpectedConditions.invisibilityOfElementLocated(loadingElement));
}
also, you might try to add a javascript executor to wait until the page is loaded
public static void waitForScriptsToLoad(WebDriver driver) {
WebDriverWait(driver, 30).until((ExpectedCondition<Boolean>) wd ->
((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
}
and then your page constructor becomes this
public LoadingPage(WebDriver driver) {
PageFactory.initElements(driver, this);
this.waitDriver = new WebDriverWait(this.driver, 20);
waitForScriptsToLoad(driver);
}
If you create wait method in your program that is easy and customize in selenium framework
private static WebElement waitForElement(By locator, int timeout)
{
WebElement element=new WebDriverWait(driver,timeout).until(ExpectedConditions.presenceOfElementLocated(locator));
return element;
}
//If you want wait for id following code should use
waitForElement(By.id(""),20);
Here 20 is miliseconds
and you can use any web elements to wait
To simulate an ExpectedConditions like elementNotExists you can use either among invisibilityOfElementLocated() or invisibilityOf().
invisibilityOfElementLocated()
invisibilityOfElementLocated() is the implementation for an expectation for checking that an element is either invisible or not present on the DOM. It is defined as follows:
public static ExpectedCondition<java.lang.Boolean> invisibilityOfElementLocated(By locator)
An expectation for checking that an element is either invisible or not present on the DOM.
Parameters:
locator - used to find the element
Returns:
true if the element is not displayed or the element doesn't exist or stale element
Code Block:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.WebDriverWait;
public class fooPage {
WebDriver driver;
public fooPage(WebDriver driver)
{
PageFactory.initElements(driver, this);
}
//you don't need this
//#FindBy(className = "loading-container")
//private WebElement loadingElement;
public void foo()
{
Boolean bool = new WebDriverWait(driver, 20).until(ExpectedConditions.invisibilityOfElementLocated(By.className('loading-container')));
//other lines of code
}
}
As an alternative you can also use the invisibilityOf() method as follows:
invisibilityOf()
invisibilityOf() is the implementation for an expectation for checking the element to be invisible. It is defined as follows:
public static ExpectedCondition<java.lang.Boolean> invisibilityOf(WebElement element)
An expectation for checking the element to be invisible
Parameters:
element - used to check its invisibility
Returns:
Boolean true when elements is not visible anymore
Code Block:
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.WebDriverWait;
public class fooPage {
WebDriver driver;
public fooPage(WebDriver driver)
{
PageFactory.initElements(driver, this);
}
#FindBy(className= 'loading-container')
WebElement loadingElement;
public void foo()
{
Boolean bool = new WebDriverWait(driver, 20).until(ExpectedConditions.invisibilityOf(fooPage.getWebElement()));
//other lines of code
}
public WebElement getWebElement()
{
return loadingElement;
}
}
You can find a detailed discussion in How to use explicit waits with PageFactory fields and the PageObject pattern
I want to create a class where I set all the common actions of the WebDrivers such as: waitExplicit, findElement, click. But if I create a method then I have to create the WebDriver and WebDriverWait over and over on each method of the class, I already tried having a class for the Drivers, but when I call the methods, they just create instances over and over, so multiple windows open, I tried this way, but still cannot get to it:
public class AutomationActions{
static LoadProperties prop = new LoadProperties(); //This class has the System.setProperty for the driver
prop.getSysProp(); //***This is the issue, how can I solve this?****
WebDriver driver = new ChromeDriver(); //this will not work without the one above working
WebDriverWait wait = new WebDriverWait(driver, 30);//this will not work without the one above working
public void waitForPageToLoad() throws Exception {
ExpectedCondition<Boolean> pageLoadCondition = new
ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete");
}
};
// WebDriverWait wait = new WebDriverWait(driver, 30); // I want to avoid having to set this in every method
wait.until(pageLoadCondition); //this is supposed to replace the line of code above
}
I don't really work on Java much any more, I've written our framework in C# but I put together some quick classes in Java to show you how I set things up. I use page object model and I recommend you do too so I've written this example using page object model. I've written a simple test that uses Dave Haeffner's (one of the Selenium contributors) demo site, http://the-internet.herokuapp.com.
The basic concepts are:
There is a class BaseTest that holds things that correspond to tests, e.g. setting up the driver at the start of the test, quitting the driver at the end of the test, etc. All of your tests will inherit from BaseTest
There is a class BasePage that holds things that correspond to generic methods for finding elements, clicking on elements, etc. Each of your tests inherit from BasePage. (This is what I think the main part of your question is asking about).
To follow the page object model, each page (or part of a page) is its own class and holds all locators and actions done on that page. For example, a simple login page would have the locators for username, password, and the login button. It would also hold a method Login() that takes a String username and a String password, enters those in the appropriate fields and clicks the Login button.
The final class of this example is a sample test aptly named SampleTest.
You shouldn't have any FindElements() or related calls in your tests, all those should be in the appropriate page object.
This is using TestNG as the unit test library. Use it or JUnit, your preference but if you use JUnit, you will need to change the asserts and the annotations.
Under 'src', I create a folder for page objects, 'PageObjects', and a folder for tests, 'Tests'. Here's what the files look like on disk.
\src
\PageObjects
BasePage.java
DropdownListPage.java
\Tests
BaseTest.java
SampleTest.java
BasePage.java
package PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class BasePage
{
private WebDriver driver;
private final int shortWait = 10;
public BasePage(WebDriver _driver)
{
driver = _driver;
}
public void ClickElement(By locator)
{
new WebDriverWait(driver, shortWait).until(ExpectedConditions.elementToBeClickable(locator)).click();
}
public WebElement FindElement(By locator)
{
return new WebDriverWait(driver, shortWait).until(ExpectedConditions.presenceOfElementLocated(locator));
}
// add more methods
}
DropdownListPage.java
package PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.Select;
public class DropdownListPage extends BasePage
{
private final By dropdownListLocator = By.id("dropdown");
public DropdownListPage(WebDriver _driver)
{
super(_driver);
}
public String GetSelectedOption()
{
return new Select(FindElement(dropdownListLocator)).getFirstSelectedOption().getText();
}
public void SelectOptionByIndex(int index)
{
new Select(FindElement(dropdownListLocator)).selectByIndex(index);
}
}
BaseTest.java
package Tests;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
public class BaseTest
{
public WebDriver driver;
public WebDriver GetChromeDriver()
{
System.setProperty("webdriver.chrome.driver", "C:\\Path\\To\\Chrome\\Driver\\chromedriver.exe");
return new ChromeDriver();
}
#BeforeTest
public void Setup()
{
driver = GetChromeDriver();
driver.manage().window().maximize();
driver.get("http://the-internet.herokuapp.com/dropdown");
}
#AfterTest
public void Teardown()
{
driver.close();
}
}
SampleTest.java
package Tests;
import org.testng.Assert;
import org.testng.annotations.Test;
import PageObjects.DropdownListPage;
public class SampleTest extends BaseTest
{
#Test
public void SampleTestCase()
{
DropdownListPage dropdownListPage = new DropdownListPage(driver);
dropdownListPage.SelectOptionByIndex(1);
Assert.assertEquals(dropdownListPage.GetSelectedOption(), "Option 1", "Verify first option was selected");
}
}
You will need to create a project that contains Selenium for Java and TestNG. Download them and put them on your build path. Create the folder structure as described above and create each of these classes and copy/paste the contents into them. Now all you need to do is run SampleTest as a TestNG Test and it should go.
The test creates a new Chromedriver instance, navigates to the sample page, selects the first option in the dropdown, asserts that the dropdown text is correct, and then quits the driver.
That should get you started. There's a lot of info crammed into the above wall of text, let me know if you have some questions.
I am new to automation testing in Selenium and i am doing some basic automation testing such as searching for something in Google and then clicking on a link which is required from the search results.
The code below, which i have produced works up until i get to the testing method. I am unable to select a link from the Google search page but i am not being shown any errors on my console. So i setup a thread on this particular line and it mentioned it could find the link name however the link name is used in the html code as i have checked on Google inspect.
Am i missing something obvious? I am relatively new to Selenium so any help is appreciated. Also i have tried mirroring some code from this users response "How to click a link by text in Selenium web driver java" but no luck!
Thanks
package com.demo.testcases;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class MyFirstTestScript {
private static WebDriver driver;
public static void main (String[] args) {
SetUp();
testing();
}
// TODO Auto-generated method stub
#setup
public static void SetUp () {
driver = new FirefoxDriver();
driver.get("http://www.google.co.uk");
System.setProperty("webdriver.gecko.driver", "usr/local/bin/geckodriver");
driver.findElement(By.name("q")).sendKeys("BBC" + Keys.ENTER);
}
#Test
public static void testing() {
driver.findElement(By.partialLinkText("BBC - Home")).click();
}
}
Once you obtain the search results for the text BBC on Google Home Page next to click() on the link containing the text BBC - Home you can use the following code block :
List <WebElement> my_list = driver.findElements(By.xpath("//div[#id='rso']//div[#class='rc']/h3[#class='r']/a"));
for (WebElement item:my_list)
{
if(item.getAttribute("innerHTML").contains("BBC - Home"))
item.click();
}
You can use this code:
public class MyFirstTestScript {
private static WebDriver driver;
private static WebDriverWait wait;
public static void main (String[] args) {
SetUp();
testing();
}
#setup
public static void SetUp () {
System.setProperty("webdriver.gecko.driver", "usr/local/bin/geckodriver");
driver = new FirefoxDriver();
wait = new WebDriverWait(driver,50);
driver.manage().window().maximize();
driver.get("http://www.google.co.uk");
wait.until(ExpectedConditions.elementToBeClickable(By.name("q")));
driver.findElement(By.name("q")).sendKeys("BBC" + Keys.ENTER);
}
#Test
public static void testing(){
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.linkText("BBC - Homepage"))));
driver.findElement(By.linkText("BBC - Homepage")).click();
}
Disclaimer: I come from a dynamic language background (Ruby) and I'm trying to level up my skills in Java. I fear with my problem here I'm thinking too much in the context of Ruby and would appreciate some pointers.
Problem:
I want to see how I can have a helper class know about the existence of a WebDriver instance without having to pass the instance around all the time. The reason I'm trying to avoid that is because it leads to extra method arguments and less fluent test writing.
Context:
I have a test case (LoginIT.java) that looks like this:
package com.testerstories.learning.symbiote;
import static com.testerstories.learning.helpers.Selenium.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
public class LoginIT {
WebDriver driver;
#BeforeTest
public void startBrowser() {
driver = new FirefoxDriver();
}
#AfterTest
public void quitBrowser() {
driver.quit();
}
#Test
public void loginAsAdmin() {
driver.get("http://localhost:9292");
withElement("open").click();
waitForPresence("username");
withElement("username").sendKeys("admin");
withElement("password").sendKeys("admin");
withElement("login-button").submit();
waitForPresence("notice");
assertThat(withElement("notice", "className"), equalTo("You are now logged in as admin."));
}
}
The key methods to note here are the calls to withElement and waitForPresence. These are defined on the Selenium class that I created and reference via the static import at the top.
Here is Selenium.java which contains that logic:
package com.testerstories.learning.helpers;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class Selenium {
public static WebElement withElement(String identifier, String locator) {
switch (locator) {
case "className":
return driver.findElement(By.className(identifier));
default:
return withElement(identifier);
}
}
public static WebElement withElement(String identifier) {
return driver.findElement(By.id(identifier));
}
public static void waitForPresence(String identifier, String locator) {
WebDriverWait wait = new WebDriverWait(driver, 10, 500);
switch (locator) {
case "className":
wait.until(ExpectedConditions.visibilityOfElementLocated(By.className(identifier)));
default:
waitForPresence(identifier);
}
}
public static void waitForPresence(String identifier) {
WebDriverWait wait = new WebDriverWait(driver, 10, 500);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(identifier)));
}
}
The key problem here are the lines that reference driver. When these methods were defined in LoginIT.java that wasn't an issue because WebDriver is defined there in the driver instance.
I do know that one answer to my problem is that I could just pass the WebDriver instance to each method. So, for example, I could use this method signature with withElement:
public static WebElement withElement(WebDriver driver, String identifier)
I'm adding another argument to pass in the WebDriver instance. But that means my test logic has to look like this:
withElement(driver, "username").sendKeys("admin");
withElement(driver, "password").sendKeys("admin");
withElement(driver, "login-button").submit();
Not the end of the world but it's less fluent and it just seems like there should be a better way.
Question:
Is there a better way?
Or am I in fact on the right track and should just accept that if I want the helper methods to be separate from the tests the driver reference must be passed in? And thus accept the slightly more verbose test statements?
Other Thought:
The only other thing I can immediately think of is that I create yet a third class that represents just WebDriver itself. This third class is then used by both my test (LoginIt.java) and my test helper (Selenium.java). I'm not sure how best to implement that but I'm also not sure if going that route makes sense.
I say that because it seems if I do that, I'm simply creating a class to wrap the creation of a WebDriver instance. So I have to create a class to then get an instance of that class so that I can create an instance of WebDriver. It seems like I'm adding complexity ... but maybe not. Hence my quest for pointers.
An alternative may be to allow your Selenium class to be initialized. Rather than having the methods be static, you can require the WebDriver reference in the class constructor.
package com.testerstories.learning.helpers;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class Selenium {
private final WebDriver driver;
public Selenium(WebDriver webDriver) {
this.driver = webDriver;
}
public WebElement withElement(String identifier, String locator) {
switch (locator) {
case "className":
return driver.findElement(By.className(identifier));
default:
return withElement(identifier);
}
}
public WebElement withElement(String identifier) {
return driver.findElement(By.id(identifier));
}
public void waitForPresence(String identifier, String locator) {
WebDriverWait wait = new WebDriverWait(driver, 10, 500);
switch (locator) {
case "className":
wait.until(ExpectedConditions.visibilityOfElementLocated(By.className(identifier)));
default:
waitForPresence(identifier);
}
}
public void waitForPresence(String identifier) {
WebDriverWait wait = new WebDriverWait(driver, 10, 500);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(identifier)));
}
}
Your LoginIT.java would then initialize the reference with in the #Before along with the driver, and change the formerly static calls to the instance you stood up:
package com.testerstories.learning.symbiote;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.testerstories.learning.helpers.Selenium;
public class LoginIT {
WebDriver driver;
Selenium selenium;
#BeforeTest
public void startBrowser() {
driver = new FirefoxDriver();
selenium = new Selenium(driver);
}
#AfterTest
public void quitBrowser() {
driver.quit();
}
#Test
public void loginAsAdmin() {
driver.get("http://localhost:9292");
selenium.withElement("open").click();
selenium.waitForPresence("username");
selenium.withElement("username").sendKeys("admin");
selenium.withElement("password").sendKeys("admin");
selenium.withElement("login-button").submit();
selenium.waitForPresence("notice");
assertThat(selenium.withElement("notice", "className"), equalTo("You are now logged in as admin."));
}
}
I'm not entirely certain if this solution follows best design practices, but here is what I ended up doing. (Note: I changed from TestNG to JUnit but other than that, everything is functionally equivalent.)
I created a Driver.java class:
package com.testerstories.learning.helpers;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class Driver {
private WebDriver driver;
public WebDriver getDriver() {
if (null == driver) {
driver = new FirefoxDriver();
}
return driver;
}
public void quitDriver() {
if (null != driver) {
driver.quit();
driver = null;
}
}
}
So this class is responsible for creating a driver instance and getting rid of that instance. It checks if the driver is null before creating so it won't keep creating new instances. Here I'm hard-coding a new instance of Firefox. Eventually I would have to make this class create new browser drivers based on what the user wants, but baby steps first.
Then I created DriverFactory.java which looks like this:
package com.testerstories.learning.helpers;
import org.junit.After;
import org.junit.Before;
import org.openqa.selenium.WebDriver;
public class DriverFactory {
private static Driver driver;
#Before
public void createDriver() {
driver = new Driver();
}
public static WebDriver getDriver() {
return driver.getDriver();
}
#After
public void quitDriver() {
driver.quitDriver();
}
}
This factory is used to create the driver instances. Notice the #Before and #After annotations. So this is where the tests have to rely on the driver factory. So what this means is that my tests now inherit the DriverFactory, whose responsibility is to provide an actual driver instance. So my LoginIT.java test now changes to this (continuing to use Jeremiah's provided solution as well):
package com.testerstories.learning.symbiote;
import com.testerstories.learning.helpers.DriverFactory;
import com.testerstories.learning.helpers.Selenium;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
public class LoginIT extends DriverFactory {
WebDriver driver;
Selenium selenium;
#Test
public void loginAsAdmin() {
driver = DriverFactory.getDriver();
selenium = new Selenium(driver);
driver.get("http://localhost:9292");
selenium.withElement("open").click();
selenium.waitForPresence("username");
selenium.withElement("username").sendKeys("admin");
selenium.withElement("password").sendKeys("admin");
selenium.withElement("login-button").submit();
selenium.waitForPresence("notice", "className");
assertThat(selenium.withElement("notice", "className").getText(), equalTo("You are now logged in as admin."));
}
}
I'm still doing some (all?) of the things that jpmc26 was concerned about and I can't honestly say I'm doing this well. But this does work.
If anyone has any thoughts -- particularly critical ones -- I'm most open to hearing them. As mentioned in my original question, I'm attempting to get better at Java after many, many years as a Ruby programmer. It's been rough going, to say the least.