Webdriver instances - how to run multiple tests using one test base - java

I'm learning automation testing in Java using Selenium and Testng. I've came across the issue that I cannot really solve, maybe someone here knows the solution.
I'm trying to run multiple tests from one class using multisession.xml. That works only if I add separate instance to each test, something like this:
System.setProperty("webdriver.chrome.driver", "src/main/resources/chromedriver.exe"); WebDriver driver; driver = new ChromeDriver();
However, I want to use separate class (test base) that extends my class with tests in order to not duplicate code. If I do that, then my multisession.xml can only perform one test at the time and returns following exception:
org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
My guess there is a problem with instances of webdriver. In my base initialization code looks like that:
public class TestBase {
public static WebDriver driver;
public static WebDriverWait wait;
public static Properties config;
public static Properties testdata;
Then setting up separate files with test data and initialization:
public static void initialization() {
String url = config.getProperty("URL");
String browser = config.getProperty("browser");
String pageLoadTimeout = config.getProperty("pageLoadTimeout");
String windowsMaximize = config.getProperty("windowsMaximize");
String deleteAllCookies = config.getProperty("deleteAllCookies");
String waitTimeout = config.getProperty("waitTimeout");
switch (browser) {
case "chrome":
System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") +
"/src/main/resources/chromedriver.exe");
ChromeOptions options = new ChromeOptions();
options.addArguments("--incognito");
driver = new ChromeDriver(options);
break;
case "firefox":
System.setProperty("webdriver.gecko.driver", System.getProperty("user.dir") +
"/src/main/resources/geckodriver.exe");
driver = new FirefoxDriver();
break;
case "msedge":
System.setProperty("webdriver.edge.driver", System.getProperty("user.dir") +
"/src/main/resources/msedgedriver.exe");
driver = new EdgeDriver();
break;
case "opera":
System.setProperty("webdriver.opera.driver", System.getProperty("user.dir") +
"/src/main/resources/operadriver.exe");
driver = new OperaDriver();
break;
I will appreciate any help!

StaleElementReferenceException is thrown when the element you're looking for is no longer attached to the DOM. Most probably you're trying to .findElement first, then DOM gets refreshed and you're trying to access it. This often happens with parallel tests execution if your page-class instances with elements are not thread-safe.
Try to use:
object _lock = new object();
lock (_lock)
{
//your driver initialization/login
}
You should also define how many threads will be executing your tests by modifying thread-count="5" parallel="methods"

Related

How to use driver initialize code from a separate class and use it in different class of same package and different package

Stuck with making a separate class for the driver (capabilities, allowing notification, an opening web browser) within one separate class and using it is a different class of the same package and a different package
Need to fetch it on different classes
Code:
public class Permission {
public static ChromeDriver accesspermission() {
DesiredCapabilities caps = new DesiredCapabilities();
ChromeOptions options = new ChromeOptions();
HashMap < String, Integer > conentSettings = new HashMap < String, Integer > ();
HashMap < String, Object > profile = new HashMap < String, Object > ();
HashMap < String, Object > prefs = new HashMap < String, Object > ();
conentSettings.put("notifications", 1);
conentSettings.put("geolocation", 1);
conentSettings.put("media_stream", 1);
profile.put("managed_default_content_settings", conentSettings);
prefs.put("profile", profile);
options.setExperimentalOption("prefs", prefs);
caps.setCapability(ChromeOptions.CAPABILITY, options);
ChromeDriver driver = new ChromeDriver(options);
driver.manage().window().maximize();
driver.manage().deleteAllCookies();
driver.get("URL");
}
return driver;
}
You could do something like this:
public class MyDriverStuff {
WebDriver driver = null;
public static WebDriver getDriver(){
if(driver == null){
// configure and create your driver here
driver = new ChromeDriver();
}
return driver;
}
public static void terminateDriver(){
driver.quit();
driver = null;
}
}
Disclaimer 1 - This is the most simple case. You need to add different posible exception cases handling.
Disclaimer 2 - I didn't use IDE to write this code so there could be sytax errors.
Disclaimer 3 - This case won't be working for parallel execution case. You'll have to read about ThreadLocal class to add multithreading support.

Create a test base page for calling desired capabilities

I was hoping for some help setting up my test frame work as its causing me issue. Im currently using zalenium in conjunction with my selenium tests. Currently im setting the desired capabilities in the #BeforeTest section of my tests:
#BeforeTest
#Parameters("browser")
public void setup(String br)throws MalformedURLException {
de = new DesiredCapabilities();
if (br.equals("chrome")) {
de.setCapability(CapabilityType.BROWSER_NAME, BrowserType.CHROME);
de.setCapability(CapabilityType.PLATFORM_NAME, org.openqa.selenium.Platform.LINUX);
}
else if(br.equals("firefox")){
de.setCapability(CapabilityType.BROWSER_NAME, BrowserType.FIREFOX);
de.setCapability(CapabilityType.PLATFORM_NAME, Platform.LINUX);
}
URL url = new URL("http://localhost:4444/wd/hub");
driver = new RemoteWebDriver(url,de);
driver.get(URL);
This allows me to run my testing in the docker environment and not on my local machine and is working correctly.
However i would like to create a base for these capabilities so i don't have to keep stating the desired capabilities for each test.
I want to do this also because I would like to set up separate classes for each page. Currently when i try this im getting a null pointer exception because the driver isnt declared. I tried to inject the Remote webdriver like so:
#Test
public void loginTest( RemoteWebdriver driver){
WebElement user_account_menu = driver.findElement(By.cssSelector("[data-user-account-settings-button]"));
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(ExpectedConditions.elementToBeClickable(user_account_menu));
user_account_menu.click();
System.out.println("Login clicked successfully");
}
Im receiving the error: Cannot inject #Test annotated Method [loginTest] with [class org.openqa.selenium.remote.RemoteWebDriver
So im basically trying to figure out how i can set up these capabilities for the driver in a class and then extend them onto my tests.
From this link, it appears, you're not adding the right annotation, namely #Parameters.
However, in some other testing frameworks, it's not typical to be able to pass in variables that aren't determined at runtime, i.e. variables for objects like RemoteWebdriver won't work, but variables for strings or ints will work. From this link, it appears that what you're trying to accomplish is doable. But I recommend the following approach.
Have an enumeration:
enum BrowserType
{
Chrome, Firefox, IE;
}
Have your test base page:
public class BrowserSetup
{
public static RemoteWebdriver Initialize(string browserType)
{
switch (browserType)
{
case BrowserType.Chrome:
// set chrome capabilities and initialize your browser here
break;
case BrowserType.Firefox:
// set firefox capabilities and initialize your browser here
break;
default:
// set default case, or fail if browser type doesn't match
break;
}
}
Then, from you're test class, assuming you have a private RemoteWebdriver driver initialized somewhere in the test class, you can do the following:
#BeforeTest
#Parameters("Chrome")
public void setup(String browserType) throws MalformedURLException
{
driver = BrowserSetup.Initialize(browserType);
}

When trying to run Selenium Cucumber test in two browsers, test runs in only one browser

I am stuck whilst trying to run a selenium cucumber java script in two browsers (Chrome, Firefox). The test works fine when I use a single browser. I use selenium PageFactory class to initialise the web elements.
The problem is when I run the test for two browsers, the first browser opens, navigates to the URL and then nothing happens. Web elements are not initialised. The test moves on to the second browser, navigates to URL, web elements are initialised, subsequent test methods (testMethod1) run as expected. Why is the test not running on the first browser?
This is the PageFactory BasePage class holding the web elements:
public class BasePage {
private final WebDriver driver;
public BasePage(WebDriver driver) {this.driver = driver;} //constructor
#FindBy(id = "cc-amount")
public WebElement amountField;
This is the test class and how I have tried to run the test in two browsers:
public class Convert {
private static WebDriver driver;
private final BaseUtil baseUtil = new BaseUtil();
private static BasePage basePage;
private static int browser;
public void navigateToUrl(String url) throws InterruptedException {
for (browser = 1; browser <= 2; browser++) {
if (browser == 1) {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
} else if (browser == 2) {
WebDriverManager.firefoxdriver().setup();
driver = new FirefoxDriver();
}
driver.get(baseUtil.getMyUrl()); //Url coming from a utility class
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
basePage = PageFactory.initElements(driver, BasePage.class); //initialisation of the BasePage class conttaining the pagefactory web elements
After the browser initialisation, this method runs ok in the second browser (Firefox) but did not run at all in the first browser (Chrome):
public void testMetod1(String amount) throws InterruptedException {
basePage.amountField.click();
My suspicion is that PageFacory could not be initiated for both browsers in a single run but I do not know how to diagnose this further. It could also be a for loop error.
I believe it's a synchronisation issue. Just you need to create a class for each browser, then you can create another base class which contains all the shared code.
Creating a class for each browser will give you some advantages:
Cleaner code.
Your tests will run in parallel.
Specific test cases for a specific browsers (Specially for IE).
Solve this sync issue.
So basically instead of using Converter, you can create:
ChromeConverter
FirefoxConverter
And inside your TestExecuter class, just create instance for each one and run your tests.
And to be more detailed, you are using one class Convert and inside it you have two static variables:
private static WebDriver driver;
private static BasePage basePage;
Removing the static keyword from BasePage class won't solve the root cause as you are using BasePage which is a static class and can't be instantiated as instance class and it's inside an external library so you need to separate the classes as kind of wrapper.
Here you are trying to use the same exact initialisation with Chrome then Firefox which won't work in parallel or even in for loop until you can wait to the first test then dispose it and initialise all the stuff related to the second test again.
So the correct approach is to create a separate Convert for each browser like that:
ChromeConverter
FirefoxConverter
And In your TestExecuter you just need to initialise each one and use it, something like that:
#Given("...")
public void navigateToUrl(String url) throws InterruptedException {
chromeConverterApp.navigateToUrl(url);
firefoxConverterApp.navigateToUrl(url);
}
#When("^..")
public void enterCurrencies(String amount, String from, String to) throws InterruptedException {
chromeConverterApp.enterCurrencies(amount, from, to);
firefoxConverterApp.enterCurrencies(amount,from,to);
}
The for loop opens Chrome on the first run (browser=1), then opens Firefox on the second run (browser=2) and then continues with the test on the last opened browser (Firefox).
If you wish to run the complete test on both browsers, you need to put the rest of the test inside of the for loop.
Basically, you have something like:
public void navigateToUrl(String url) throws InterruptedException {
for (browser = 1; browser <= 2; browser++) {
//body of the for loop
}
//some more code
} //end of navigateToUrl
You need to change it to:
public void navigateToUrl(String url) throws InterruptedException {
for (browser = 1; browser <= 2; browser++) {
//body of the for loop
}
//some more code
testMethod1()
} //end of navigateToUrl
Your approach is not correct, if you need to run your tests in parallel you should go for either Selenium Grid or consider implementing other multi-threading option, i.e.:
Plain Java: ExecutorService
JUnit or TestNG: Maven Surefire Plugin Fork Options and Parallel Test Execution
In any case you will need to refactor your test to follow Parallel Tests - Best Practices at least:
Remove static modifier from the WebDriver declaration
Consider putting your WebDriver instance into the Thread Local Storage otherwise you will have clashes

Stabilizing test methods in one test class

I have a test class using testNG, with 4 test methods, testing one website for 4 different browsers, like the following code:
public class MyTest {
#Test
public void acceptanceFFTest()
{
}
#Test
public void acceptanceChromeTest()
{
}
#Test
public void acceptanceIETest()
{
}
#Test
public void acceptanceSafariTest()
{
}
#AfterMethod
public void tearDown()
{
webDriver.quit();
}
}
After one test method finished, the webdriver for that browser is killed in the #AfterMethod, the next browser starts with new webdriver respectively, and so on.
My problem is: if I run these test methods one by one, then they are always passed. But if I run from the test class level as a test suite with 4 test methods, then sometimes one or two of them are failed with NoElementException, But the exception is not always at one page or one element, sometimes here, sometimes there. If I run the failed tests alone again, then they are passed.
I think for the test class level, maybe the http signal sometimes is fast and sometimes is slow, the test class level is not stable as single test method, I am not sure.
Is there anyway to stabilize them? Thank you.
I would rewrite the tests so that you have a single test that runs on all browsers. You may run into some slight differences in the browser that you may have to handle but I think this overall approach is much better and more manageable. Here's a simple example of how to do this. For this example I just hardcoded a string browserType. I'm assuming you would pass this in or read it from a file or whatever. The basic idea is that you define your driver variable as a generic WebDriver. Once you determine what browser you want for that run, you instantiate the specific driver and execute the test(s).
String browserType = "firefox"; // hardcoded for the example
WebDriver driver;
switch (browserType)
{
case "firefox":
driver = new FirefoxDriver();
break;
case "ie":
driver = new InternetExplorerDriver();
break;
case "chrome":
driver = new ChromeDriver();
break;
default:
throw new Exception("browserType: " + browserType + " not defined.");
}
// do test case
driver.get("http://www.google.com");
// ... and so on

how to test multiple browser(versions) with selenium and junit

I just discovered selenium - a great tool!
I plan to run/use selenium-ide generated junit4 code. But I need it to run with many browsers/web drivers.
Is there a junit/java-pattern for this use case? My first idea was to use #RunWith(Parameterized.class) and provide a List of WebDrivers (the parameter for the class - probably provided as an external file listing browsers and versions?!). Is this a good idea? Is it possible to provide a central #Parameters -method to be used by all my Selenium-tests?
What alternatives are there?
Probably it is possible to change the "Format" that Selenium exports to minimize manual changes?
Well, I do need to switch drivers from time to time, so I did this:
I initialize selenium related stuff in my own Class - called by name of the application and the driver is approached by the getters. When calling my class constructor, I use enum type of driver to initialize with:
private WebDriver driver;
public TestUI(Environment.DriverToUse drv){
switch (drv){
case CHROME:{
ChromeDriverService service = ChromeDriverService.createDefaultService();
File file = new File(TestUI.class.getResource("/chromedriver.exe").toURI());
System.setProperty(ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY, file.getAbsolutePath());
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized");
driver = new ChromeDriver(service,options);
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
break;
}
case FIREFOX:{
FirefoxProfile ffProfile = new FirefoxProfile();
ffProfile.setPreference("browser.safebrowsing.malware.enabled", false);
driver = new FirefoxDriver(ffProfile);
driver.manage().window().setPosition(new Point(0, 0));
java.awt.Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension dim = new Dimension((int) screenSize.getWidth(), (int) screenSize.getHeight());
driver.manage().window().setSize(dim);
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
break;
}
public WebDriver getDriver(){
return driver;
}
of course my Environment class looks like this
public class Environment {
public enum DriverToUse {FIREFOX, CHROME};
// .. and some other stuff, because I need to test on different environments, so I store here Environment URL for example
And my test class looks something like this
#Before
public static final Environment.DriverToUse USED_DRIVER = Environment.DriverToUse.FIREFOX;
#Test
public void testVersionNumber() throws Exception{
TestUI testUI= new TestUI(USED_DRIVER);
WebElement version = testUI.getDriver().findElement(By.id("the Id of element"));
version.click();
//...
}
Use Selenium RC/Selenium Server. These come with the API's you will need to run remote tests in multiple browsers simply. Happy Hunting!
Check out the Selenide library. It's an open source wrapper for selenium that makes UI testing a breeze. Here's an example test.
#Test
public void userCanLoginByUsername() {
open("/login");
$(By.name("user.name")).setValue("johny");
$("#submit").click();
$(".loading_progress").should(disappear); // Waits until element disappears
$("#username").shouldHave(text("Hello, Johny!")); // Waits until element gets text
}

Categories