What I'm trying to do with this is to allow one section of the app to allow the user to run a few tests with webdriver. Then, without closing that window, making changes to the web app and then kicking off a separate method to perform other actions.
What I've created is class BrowserAgent that holds a Webdriver object like so:
public class BrowserAgent
{
private static BrowserAgent instance = new BrowserAgent();
private boolean BrowserAgentBusy = false;
private static boolean BrowserAgentActive = false;
private static WebDriver driver;
...
Now when I get the instance of the driver I am simply calling BrowserAgent.getDriver() which is implemented like so:
public static WebDriver getDriver()
{
if(BrowserAgentActive && driver != null)
{
return driver;
}
else
{
BrowserAgentActive = true;
return new FirefoxDriver();
}
}
However, for some reason, every time I call getDriver(), a new window opens, and all of the context from the first window is now lost. What am I doing wrong?
You're never setting driver to anything, so it's always null and your code always takes the else{} branch.
This is the way I might do something like this:
using System;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.IE;
namespace DriverTesting
{
[TestFixture]
public class UnitTest1
{
[Test]
public void TestMethod1()
{
IWebDriver myDriver = BrowserAgent.getDriver();
myDriver.Navigate().GoToUrl("http://www.google.com/");
}
[Test]
public void TestMethod2()
{
IWebDriver myDriver = BrowserAgent.getDriver();
myDriver.Navigate().GoToUrl("http://www.yahoo.com/");
}
}
}
public class BrowserAgent
{
private static IWebDriver driver;
public static IWebDriver getDriver()
{
if (driver == null) {
driver = new InternetExplorerDriver();
}
return driver;
}
}
Related
I use ThreadLocal for thread safety and run the tests in parallel using Maven failsafe and JUnit. I am running two tests from two feature files to test parallel running.
But I always have the first browser blank. Then the subsequent ones are fine and the tests pass. If I run sequentially, there isn’t any issue.
HookStep class:
public class HookStep {
#Before()
public void beginTest() {
WebDriverFactory.setDriver(Props.getValue("browser.name"));
}
#After()
public void stopTest(Scenario scenario) {
switch (environment) {
case "local":
case "aws": {
if (scenario.isFailed()) {
Screenshots.Shot shot = new Screenshots(Screenshots.CONTEXT_TEST_FAIL)
.takeShot(scenario.getName() + formCounter.getAndIncrement() + "");
scenario.embed(shot.getContent(), "image/png", "Error - ");
}
WebDriverFactory.closeBrowser();
}
}
}
WebDriverFactory class:
public class WebDriverFactory {
private static ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static synchronized void setDriver(String browser) {
switch (browser) {
case "chrome":
driver = ThreadLocal.withInitial(() -> {
WebDriverManager.chromedriver().setup();
return new ChromeDriver(BrowserOptions.getChromeOptions());
});
prepareBrowser();
break;
case "fireFox":
driver = ThreadLocal.withInitial(() -> {
WebDriverManager.firefoxdriver().setup();
return new FirefoxDriver(BrowserOptions.getFirefoxOptions());
});
break;
default:
throw new IllegalStateException("Unexpected value: " + browser);
}
}
private static void prepareBrowser() {
getDriver().manage().window().maximize();
getDriver().manage().deleteAllCookies();
getDriver().manage().timeouts().pageLoadTimeout(15, TimeUnit.SECONDS);
getDriver().manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
}
public static synchronized WebDriver getDriver() {
return driver.get();
}
public static void closeBrowser() {
getDriver().quit();
}
}
The StepDef class:
public class SampleStepDef {
private final WorldHelper helper;
public SampleStepDef(WorldHelper helper) {
this.helper = helper;
}
#Given("I click on the URL")
public void iClickOnTheURL() {
helper.getSamplePage().navigateToSite();
}
}
public class WorldHelper {
WebDriverFactory webDriverFactory = new WebDriverFactory();
protected WebDriver webDriver = webDriverFactory.getDriver();
private BasePage basePage;
private SamplePage samplePage;
public SamplePage getSamplePage() {
if(samplePage != null)
return samplePage;
samplePage = PageFactory.initElements(webDriver, SamplePage.class);
return samplePage;
}
}
public class SamplePage extends BasePage {
public SamplePage(WebDriver webDriver) {
super(webDriver);
}
public void navigateToSite() {
webDriver.get("https://www.bbc.co.uk");
webDriver.findElement(By.xpath("//a[contains(text(),\'News\')]")).click();
}
}
public class BasePage extends WorldHelper {
public BasePage(WebDriver driver) {
this.webDriver = driver;
}
}
How can I fix this problem?
I noticed multiple problems associated with your code.
You are making use of ThreadLocal.withInitial(). Ideally speaking this should have been defined when you are instantiating the driver thread local static variable.
So instead of
private static final ThreadLocal<WebDriver> driver = new ThreadLocal<>();
it should have been
private static final ThreadLocal<WebDriver> driver = ThreadLocal.withInitial(() -> {
return null; //Your supplier goes here.
});
There's a clear mess up in your inheritance hierarchy (there's a very good chance that you were trying to create a simple example and perhaps have omitted out the details behind the layers of inheritance), but it wasn't clear as to why does all your page object classes extend WorldHelper
You are having multiple statements at the class level such as this. The problem with these field level initialisations is that they get invoked when the object is constructed. So if the object is being constructed in a different thread, then you run into the problem of the WebDriver initialisation being triggered for that thread. End result: You have a lot of ghost browser instances that keep getting opened up, but no selenium actions are directed to them.
private final WebDriver driver = WebDriverFactory.getDriver();
When working with ThreadLocal variants of WebDriver management, you need to make sure that your calls are always from within your step definitions and never from the constructor or from class level field initialisations such as above.
Here are the list of fixes that you need to do.
Remove all occurrences of private final WebDriver driver = WebDriverFactory.getDriver(); in your code. They are not needed.
Refactor your WebDriverFactory class to look like below (For brevity I have removed off all the commented out code)
public class WebDriverFactory {
private static final ThreadLocal<WebDriver> driver = new ThreadLocal<>();
public static void setDriver(String browser) {
RemoteWebDriver rwd;
switch (browser) {
case "chrome":
WebDriverManager.chromedriver().setup();
rwd = new ChromeDriver(BrowserOptions.getChromeOptions());
break;
case "fireFox":
WebDriverManager.firefoxdriver().setup();
rwd = new FirefoxDriver(BrowserOptions.getFirefoxOptions());
break;
default:
throw new IllegalStateException("Unexpected value: " + browser);
}
driver.set(Objects.requireNonNull(rwd));
prepareBrowser();
}
private static void prepareBrowser(){
getDriver().manage().window().maximize();
getDriver().manage().deleteAllCookies();
getDriver().manage().timeouts().pageLoadTimeout(15, TimeUnit.SECONDS);
getDriver().manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
}
public static WebDriver getDriver(){
return Objects.requireNonNull(driver.get());
}
public static void closeBrowser() {
getDriver().manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
getDriver().close();
getDriver().quit();
}
}
Since all your page classes seem to be extending from WorldHelper, add a getter method such as below in it (or) ensure that no where in any of your page classes you have a WebDriver field. Whenever you need to get hold of the WebDriver instance, you should do it directly via WebDriverFactory.getDriver() (or) via the getter method such as below in your WorldHelper or whatever base class you are creating.
protected WebDriver getDriver() {
return WebDriverFactory.getDriver();
}
Once you have fixed the afore-mentioned problems, you should be good and shouldn't see any blank browser windows open up.
Note: Please clean up your project on GitHub. I noticed some cloud service provider credentials in it (it could be real credentials or could be fake. I wouldn't know.)
I haven't used webDriverFactory, but I'd try calling driver.set() in the factory class, as in this tutorial:
http://makeseleniumeasy.com/2020/05/27/threadlocal-static-webdriver-for-parallel-execution/
I am learning and trying to separate locators from actual code in selenium. I have already separated them but I need guidance on more optimization, how can I optimize the code more? Is the Page Object design model used to store only locators? Or can we store their methods too. Can someone please explain with reference to below code?
Link: https://www.goibibo.com/
Actual code with Logic(TC_01Test.java) and Base.java class initializes driver
public class TC_01Test extends Base {
WebDriver driver;
#BeforeTest
public void initialize() throws IOException {
driver = initializeDriver();
}
// Sign In functionality
#Test
public void SignIn() throws InterruptedException {
TC_01 tc02 = new TC_01(driver);
tc02.siginLink().click();
System.out.println(driver.getWindowHandle());
driver.switchTo().frame("authiframe");
System.out.println(driver.getWindowHandle());
tc02.mobileNumber().sendKeys(prop.getProperty("phoneNumber"));
System.out.println("number entered");
tc02.submitButton().click();
System.out.println("button clicked");
driver.switchTo().defaultContent();
System.out.println(driver.getWindowHandle());
tc02.closePopup().click();
}
// SignUp functionality
#Test
public void SignOut() {
TC_01 tc01 = new TC_01(driver);
tc01.sigupLink().click();
driver.switchTo().frame("authiframe");
tc01.mobileNumber().sendKeys(prop.getProperty("phoneNumber"));
tc01.submitButton().click();
driver.switchTo().defaultContent();
tc01.closePopup().click();
}
#AfterTest
public void closeBrowser() {
driver = tearDown();
}
}
Below is the code for Page Object(TC_01.java) created for above test case.
public class TC_01 {
WebDriver driver;
public TC_01(WebDriver driver) {
this.driver = driver;
}
// driver.findElement(By.xpath("//a[#id='get_sign_in']"))
// mobileNumber= driver.findElement(By.xpath("//input[#id='authMobile']")
// driver.findElement(By.id("mobileSubmitBtn"))
// driver.findElement(By.xpath("//div[#class='popContent']/a"))
By signinLink = By.xpath("//a[#id='get_sign_in']");
By signupLink = By.xpath("//a[#id='get_sign_up']");
By mobileNumber = By.xpath("//input[#id='authMobile']");
By submitButton = By.id("mobileSubmitBtn");
By closePopup = By.xpath("//div[#class='popContent']/a");
public WebElement siginLink() {
return driver.findElement(signinLink);
}
public WebElement sigupLink() {
return driver.findElement(signupLink);
}
public WebElement mobileNumber() {
return driver.findElement(mobileNumber);
}
public WebElement submitButton() {
return driver.findElement(submitButton);
}
public WebElement closePopup() {
return driver.findElement(closePopup);
}
}
Answering on your question - yes, you can store methods in PO classes as well. Furthermore, it's a good practice.
Regarding your code optimization - it's better to express business behavior instead of granular technical actions. Also, instead of returning WebElement methods and then perform actions (click, sendKeys etc) in the Test class you can simply perform such actions in PO class.
Check the code below.
public void enterFirstName() {
driver.findElement(firstName).sendKeys("abc");
}
public void enterLastName() {
driver.findElement(lastName).sendKeys("qwerty");
}
public void pressSubmitButton() {
driver.findElement(submitButton).click();
}
// instead of invocation all of these methods above in test class you can simply do this:
public void loginWithValidCredentials(String firstNameValue, String lastNameValue) {
driver.findElement(firstName).sendKeys(firstNameValue);
driver.findElement(lastName).sendKeys(lastNameValue);
driver.findElement(submitButton).click();
}
// Thus your test will look like:
#Test
public void loginTest() {
POclass po = new POclass();
po.loginWithValidCredentials("yourName", "yourNameABC");
// some assert() methods...
}
This is much simplier.
BTW, it's useful to know and use PageFactory concept - https://www.guru99.com/page-object-model-pom-page-factory-in-selenium-ultimate-guide.html
P.S. - read about "Chain of responsibilities" pattern, but in case you are strong in Java, because this is a quite advanced topic.
I use Selenium.
I have Page Object like this:
public class Portal extends Utils{
private WebDriver driver;
private final By getReason= By.xpath("//a[contains(text(),'Get Sol')]");
public Portal(WebDriver driver) {
super(driver);
this.driver = driver;
}
public VisitReason clickCurrentReason() {
clickIn(getReason);
return new VisitReason(driver);
}
}
I would like this method: clickCurrentReason() return new Object extend Utils class and passed it parameter: driver.
How to do it?
I know I have to use generics. I found part of solution:
public<T extends Utils> T clickCurrentReason(){
clickIn(getReason);
return (T)(driver);
}
But how passed in return: "return new Object(driver)"
#Test method:
public void Test() {
TestingEnv testingEnv = new TestingEnv(driver);
Portal portal = testingEnv.openPage();
VisitReason visitReason = portal.clickCurrentReason();
//sometimes instead of the last line it will be: VisitInSpot visitInSpot = portal.clickCurrentReason();
//sometimes instead of the last line it will be: VisitBack visitBack = portal.clickCurrentReason();
}
In my framework i have baseTest class and BasePageObject class
Every Page extends BasePO
Thing is : a lot of my methods on Pages need to use driver instance. I have method getDriver() and i use it 'n' times on every page
This result in Appium calling the getSession for 120 times per 5 min session
Because i use explicit wait(see code):
[debug] [MJSONWP (ae82d29b)] Calling AppiumDriver.getSession() with args: ["ae82d29b-c0af-46f9-bb13-d6ecc8ff5a00"]
2019-10-10 03:22:45:681 - [debug] [XCUITest] Executing command 'getSession'
How to solve this problem?
I`m not sure what to try without changing whole framework
public class BasePO {
WaitUtils waitUtils;
AssertionUtils asrt;
public BasePO() {
asrt = new AssertionUtils();
waitUtils = new WaitUtils();
loadProperties();
initElements();
}
private void initElements() {
PageFactory.initElements(new AppiumFieldDecorator(getDriver()), this);
}
private void loadProperties() {
}
protected IOSDriver<IOSElement> getDriver() {
return IOSDriverManager.getThreadLocalDriver();
}
}
Driver Manager
public class IOSDriverManager {
public static ThreadLocal<IOSDriver<IOSElement>> webDriver = new ThreadLocal<IOSDriver<IOSElement>>();
public static DesiredCapabilities getIOSCaps() {
Here Are my Caps
}
public static IOSDriver<IOSElement> getThreadLocalDriver() {
IOSDriver<IOSElement> driver = webDriver.get();
if (driver == null) {
createThreadLocalWebDriver();
driver = webDriver.get();
}
return driver;
}
public static void createThreadLocalWebDriver() {
IOSDriver<IOSElement> driver = null;
try {
driver = new IOSDriver<IOSElement>(new URL("http://" + getProperty("accessUser") + ":" + getProperty("accessKey") + "#hub-cloud.browserstack.com/wd/hub"), getIOSCaps());
} catch (Exception e) {
try {
driver = new IOSDriver<IOSElement>(new URL("http://" + getProperty("accessUser") + ":" + getProperty("accessKey") + "#hub-cloud.browserstack.com/wd/hub"), getIOSCaps());
} catch (MalformedURLException e1) {
System.out.println("IOS Driver is not created..!, Please check capabilitis or make sure Appium Server is running.");
}
return;
}
webDriver.set(driver);
}
Any Page
public class dashboardPage extends BasePO {
#iOSXCUITFindBy( id = "dashboardScreen")
private MobileElement dashboardScreen;
public boolean isDashboardScreen(){
waitUtils.waitForElementToBeVisible(dashboardScreen, getDriver());
boolean flag =dashboardScreen.isDisplayed();
return flag;
}
May Be it possible to have less getSession calls without shrinking amount of explicit waits ?
I solved the problem in my own framework by adding a protected WebDriver instance and a protected constructor in BasePageObject which assigns the WebDriver instance. Then, your other PageObjects inherit from BasePageObject, and you can implement another constructor for the PageObject that takes WebDriver as an argument.
When you initialize PageObjects, you can pass your test case's WebDriver instance into the PageObject, so you won't have to call getDriver() all the time. You can use this WebDriver instance to perform actions in your PageObjects, and then your test cases can call PageObject methods without any getDriver().
Here's what my framework looks like in C#, starting with BasePageObject:
public class BasePageObject
{
// protected WebDriver instance
protected IOSDriver<IOSElement> Driver { get; }
protected BasePageObject(IOSDriver<IOSElement> driver)
{
Driver = driver;
}
Then, a regular PageObject denoted SomePageObject:
// inherit from BasePageObject
public class SomePageObject : BasePageObject
{
// implement protected constructor from BasePageObject
public SomePageObject(IOSDriver<IOSElement> driver) : base(driver)
{
}
// we can use the PageObject's driver instance as such
public void DoSomethingWithDriver()
{
// this is protected WebDriver instance from BasePageObject above
Driver.FindElement(SomeLocator);
}
}
Now, tying it all together, here's how it looks in a test case:
public class MyTestClass
{
// initialize your driver somehow
IOSDriver<IOSElement> driver = getThreadLocalDriver();
// declare your PageObject, passing in your Driver instance
SomePageObject myPageObject = new SomePageObject(driver);
// now you can call PageObject methods as such
myPageObject.DoSomethingWithDriver();
}
This is all very generic, and will need to be modified to suit your needs, but this is the design pattern that I have followed in all of my test frameworks with good results.
Try to use page factory initialization methods like this. For your every UI page use separate class and then use page factory in the constructor..
public class MainPage {
IOSDriver<IOSElement> driver;
//constructor to initialize MainPage Class with IOSDriver with pageFactory design pattern
ProgrammesPage prog_page;
public MainPage(IOSDriver<IOSElement> driver) {
this.driver = driver;
// PageFactory.initElements(new AppiumFieldDecorator(driver), this);
PageFactory.initElements(new AppiumFieldDecorator(driver), this);
}
}
2 Instances of Firefox are being launched when running the script below.
Guru99projectdemo.java
package com.edureka;
public class Guru99projectdemo
{
public static void main (String[] args)
{
opengurusite obj2 = new opengurusite();
obj2.opensite();
login obj1 = new login();
obj1.login1();
}}
The 2 methods being called are shown below:
public class opengurusite
{
WebDriver driver = new FirefoxDriver();
public void opensite()
{
driver.manage().deleteAllCookies();
driver.get("http://demo.guru99.com/V4/");
}}
And the login method:
login.java
public class login {
public void login1 ()
{
WebDriver driver = new FirefoxDriver();
driver.findElement(By.name("uid")).sendKeys("mngr56562");
driver.findElement(By.name("password")).sendKeys("qAtugAb");
driver.findElement(By.name("btnLogin")).click();
}
}
Please advise on how to resolve this issue.
2 instances are launched because you invoked WebDriver instance without calling driver.quit();
WebDriver driver = new FirefoxDriver();
1st use is in opengurusite class and second in public void login1 method.
In order to operate on one browser you have to pass parameter driver between your classes and methods
For example:
public class Guru99projectdemo {
public static void main (String[] args)
{
WebDriver driver = new FirefoxDriver();
opengurusite obj2 = new opengurusite();
obj2.opensite(driver);
login obj1 = new login();
obj1.login1(driver);
driver.quit();
}
}
public class opengurusite {
public void opensite(WebDriver driver)
{
driver.manage().deleteAllCookies();
driver.get("http://demo.guru99.com/V4/");
}
}
public class login {
public void login1(WebDriver driver)
{
driver.findElement(By.name("uid")).sendKeys("mngr56562");
driver.findElement(By.name("password")).sendKeys("qAtugAb");
driver.findElement(By.name("btnLogin")).click();
}
}
But as suggested - you really should edit your question as it is not easy readable. Read some about JUnit to driver your tests properly http://junit.org/junit4/