public static void login() {
if (driver.findElements(By.xpath("//*[#id=\"name\"]")).size() > 0) {
System.out.println("true, element found, logging in");
WebElement loginform = driver.findElement(By.name("name"));
WebElement passwordform = driver.findElement(By.name("password"));
loginform.sendKeys("fhfghgfgh");
passwordform.sendKeys("dgfgdfgdfdgf");
driver.findElement(By.className("default__button")).click();
driver.findElement(By.xpath("//*[#id=\"page\"]/div[1]/div/div[1 ]")).click();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.findElement(By.cssSelector("*[class^='default__button'")).click();
driver.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
}
}
I have the above method in my java class three times. I wanted to save time and effort and just call on "login();" to save time.
However, when that code is taken out of the class and is in a vacuum, it turns into errors because it doesn't understand what the variables are anymore.
Is there anyway to just quote code into a method to be executed later?
It's a static method. That sounds like a mistake, but if you intended to be, that does make it easier:
public class OneOfThoseClasses {
public static void login() {
OneOfTheOthers.login();
}
}
You can do login directly by hitting login API from your code without any UI interaction. That will save your time for sure.
Happy Testing!
Related
Console Output
Please help me to fix the below issue in Selenium WebDriver with Java.
I am validating eComm web based application. I have couple of test cases.
1. Sign in to the application
2. Add to item to the cart. This test case is a continuation of first test case. It means that after sign in, user is gonna add the item to the cart.
I have 2 class files, In that I have given #Test condition in both the class files.
In CommonFunctions class file, I am lauching browser in #BeforeSuite section. And closing the browser in #AfterSuite section.
I am doing it in Page object model and executing from TestNG XML.
On running the suite, First test case is getting passed. When it comes to the second test case (In the #Test condition present in the 2nd class file), the in it entering into the #Test section but getting failed immediately with out any reason.
I have tried with Implicit, Explicit wait and even thread.sleep as well. But no luck. Please can some one take a look and suggect me.
Appreciate your help!
I assume that it is getting failed for some exception and the stack trace is not getting printed somehow. Try explicitly getting it printed by:
#Test
public void SecondTestCase(){
try{
yourSecondTestCase;
}
catch(Throwable e){
e.printStackTrace();
}
}
Just don't reuse the driver.
Create it in #BeforeMethod then quit it in #AfterMethod and you should not meet any issues.
Update answering a comment question below:
In general it's not a good idea to have the tests depending on each other.
Regardless of how special you think your case is, you should never do it if you want to keep your dignity.
This does not neccessarily mean that you can't reuse 'logic' in tests, it just means that you should not expect any other test to have executed before the current one.
Let's put some thought about it:
Why would you want to do it at first place?
What is the effect that you're seeking?
My blind-guess answers are:
You want to have the login test executed first.
This will help you avoid repeating the same login code in all of the remaining tests, and you save time from starting/stopping the driver.
If my guesses are correct, then I can tell you that it's perfectly normal to want those things, and that everyone who ever needed to write more than 2 tests has been there
You may ask:
Am I not trying to achieve the same thing?
Where is my mistake then?
The following answers can be considered brief and quite insufficient compared to the truth that lies beneath, but I think they will serve you as a good starting point for where you're heading at.
Yes, you're trying to achieve the same thing.
Your approach can't be called mistake, but negliance - you're trying to write automated tests with Selenium while you lack basic coding skills. It's actualy not so bad, because you didn't knew what you're getting into, but after reading this you have the choice of either letting it slip or begin the journey to personal improvement. Take some time to learn Java - a really good starter would be the 'Thinking in Java' book. Do make the exercises after each chapter and the experience that you'll gain will be priceless. The book will help you get familiar with the Java language features and good idea of how code organization works.
Enough with the general notes, below is a simple guide for you to follow in implementing v0.1 of your newly-born 'automation project'.
(Yes, it is 'project' and 'framework' is having completely different meaning, to be clear.)
First you need to decide how to approach the page objects.
(Based on my own experience)
the best way to do it is to keep all the business-logic inside the page objects and by business-logic I mean:
all the methods that perform some action,
and all the methods that retrieve some data.
Examples include doSomething / getSometing / setSomething / isSomething / canSomething / waitSomething and so on.
You should not be doing any assertions inside the page objects
You should never throw AssertionError from your page objects
If you need to throw something from page-object method/constructor just
throw IllegalArgumentException
or RuntimeException.
All the assertions should happen exclusively in the test-method-body
You should never write 'assert...' statement outside #Test method
You can have Util classes to do some data transformations
but do not ever write assertion logic inside them
rather expose methods with boolean/other return type and assert on their result.
The following example is one of the best I've ever seen:
// Don't do this:
ColorVerifications.assertAreSame(pen.getColor(), ink.getColor());
// If you do, you'll eventually end-up having to write this:
ColorVerifications.assertAreNotSame(pen.getColor(), ink.getColor());
// Do this instead:
Assert.isTrue(ColorUtil.areSame(pen.getColor(), ink.getColor());
// Then you can actually 'reuse' the logic that's already in the Assert class:
Assert.isFalse(ColorUtil.areSame(pen.getColor(), ink.getColor());
// NOTE: Regarding the term "code reuse"
// - it is NOT referring to "reusing your own code"
// - it IS referring to "reusing all the existing code"
The idea is to clearly communicate both the assert intention and how the check is done.
Always keep the logic of 'checking' and 'asserting' separate, becuse those words are having different semantics.
It is really important to put a good amount of thinking when naming classes / methods / variables - really!
Don't be afraid to rename it when you come up with a better fit - go for it on the spot!
Now back to our page obects and login stuff - I won't be diving much into it but, you'll get where I'm going at.
Carefully organize the #Test methods in separate classes
Keep the ones that need common setup in the same class
Keep the number of test-class methods that are not annotated with #Test to the minimum
Do not inherit between test classes to reuse logic other than
Outputs/Results folders preparaton/colletion/archiving
Logs/Reports initialization/disposal
Driver creation/cleanup
In general: prefer composition to inheritance
Put other logic in the page objects / util classes and reuse them
Have a base DriverWrapper class for the generic UI checks/interactions
Have a base PageObject class that is hosting a DriverWrapper member
Don't use the #FindBy / PageFactory model (you'r life will be happier)
Use static final By locators instead
Log everything!
logging is not included in the examples
but do assume every method's first line is logging the method name and the passed arguments
always remember - your log is your best friend
Reading what happened in the log takes considerably less time than manually debugging the code (which is pratically re-running it at half-speed)
You are writing atuomation code, not production code so you can nevver be wrong when logging additional info.
Except for the cases of passwords and confidential data
those you should never log.
Now that you have been thaught some basic ideas, lets dive into code:
Page basics
Sample DriverWrapper:
class DriverWrapper {
protected final WebDriver driver;
public DriverWrapper(WebDriver driver){
this.driver = Objects.requireNotNull(driver, "WebDriver was <null>!");
}
// it's okay to have 'checked exceptions' declared by all wait* methods
// but it is tottaly not okay to have 'checked exceptions' for the others
public WebElement waitForVisibleElement(By locator, int timeoutMillis)
throws TimeoutException { // <- the 'checked exception'
return new WebDriverWait(driver)
.withTimeout(Duration.ofMillis(timeoutMillis))
.pollingEvery(Duration.ofMillis(100))
.until(
ExpectedConditions.visibilityOfElementLocatedBy(locator)
);
}
public boolean isVisible(By locator, int timeoutMillis){
try{
return waitForVisibleElement(locator, timeoutMillis) != null;
catch(TimeoutException ignored){
return false;
}
}
// .get(String url){...}
// .click(By locator){... elementToBeClickable(locator) ....}
// .typeInto(bool shouldLog, By locator, CharSequence... keys){...}
// .typeInto(By locator, CharSequence... keys){typeInto(true, locator, keys);}
// you got the idea ;)
}
Sample PageObject:
class PageObject{
protected final DriverWrapper driver;
public PageObject(WebDriver driver){
this.driver = new DriverWrappr(driver);
}
}
Sample LoginPage:
class LoginPage extends PageObjet{
// NOTE: keep the locators private
private static final By USERNAME_INPUT = By.id("usernameInput");
private static final By PASSWORD_INPUT = By.id("passwordInput");
private static final By LOGIN_BUTTON = By.id("loginButton");
private static final By ERROR_MESSAGE = By.id("errorMessage");
public LoginPage(WebDriver driver){
super(driver);
}
public LoginPage goTo(){
driver.get(url);
return this;
}
public void loginAs(String user, String pass){
// NOTE:
// Do not perform navigation (or other actions) under the hood!
// Resist the urge to call goTo() here!
// Page object methods should be transparent about what they do.
// This results in better level of control/transparency in the tests.
driver.typeInto(USERNAME_INPUT, user);
driver.typeInto(PASSWORD_INPUT, pass);
driver.click(LOGIN_BUTTON);
}
public boolean isErrorMessageVisible(int timeoutMillis){
// NOTE: We delegate the call to the driver
// Allowing the page-object to later define it's own isVisible method
// Without having collision with driver methods.
return driver.isVisible(ERROR_MESSAGE, timeoutMillis);
}
}
Infrastructure basics
Sample DriverManager class:
class DriverManager{
private static WebDriver driver;
public static WebDriver getDriver(){
return driver;
}
public static void setDriver(WebDriver driver){
// NOTE: Don't do null checks here.
DriverManager.driver = driver;
}
public static WebDriver createDriver(String name){
//...
return new ChromeDriver();
}
Sample TestBase class:
class TestBase{
// NOTE: just define the methods, do not annotate them.
public static void setUpDriver(){
// In v0.1 we'll be sharing the driver between tests in same class
// Assuming the tests will not be running in parallel.
// For v1.0 you can improve the model after reading about test-listeners
WebDriver driver = DriverManager.getDriver();
if(driver != null){
return;
}
driver = DriverManager.createDriver("chrome");
DriverManager.setDriver(driver);
}
public static void tearDownDriver(){
WebDriver driver = DriverManager.getDriver();
if(driver != null){
driver.quit();
DriverManager.setDriver(null);
}
}
}
Finally - a test class:
class LoginTests extends TestBase{
private LoginPage loginPage;
#BeforeClass
public static void setUpClass(){
setUpDriver();
}
#AfterClass
public static void tearDownClass(){
tearDownDriver();
}
#BeforeMethod
public void setUp(){
// actions, that are common for all test cases in the class
loginPage = new LoginPage(DriverManager.getDriver());
loginPage.goTo();
}
#AfterMethod
public void tearDown(){
// dispose the page objets to ensure no local data leftovers
loginPage = null;
}
#Test
public void testGivenExistingCredentialsWhenLoginThenNoError(){
loginPage.loginAs("TestUser", "plain-text password goes here");
boolean errorHere = loginPage.isErrorMessageVisible(30 * 1000);
Assert.assertFalse(errorHere, "Unexpected error during login!");
}
#Test
public void testGivenBadCredentialsWhenLoginThenErrorShown(){
loginPage.loginAs("bad", "guy");
boolean errorHere = loginPage.isErrorMessageVisible(30 * 1000);
Assert.assertTrue(errorHere, "Error message not shown!");
}
}
That's all there is to it.
Hope you enjoyed the ride.
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
I have been reading about stale elements and am still a bit confused. For instance, the following won't work, correct?
public void clickFoo(WebElement ele) {
try {
ele.click();
} catch (StaleElementReferenceException ex) {
ele.click();
}
}
because if ele is stale, it will remain stale. The best thing is to redo the driver.findElement(By), but as you can see in this example, there is no xpath. You can attempt to ele.getAttribute("id") and use that, but if the element has no id, this also will not work. All methods calling this would have to put the try/catch around it, which may not be feasible.
Is there some other way the element could be refound? Also, assuming there is an id, would the id remain the same after the element goes stale? What in the WebElement object ele is different once it goes stale?
(Java Eclipse)
I would recommend you NOT create a method like the above. There's no need to add another function layer on top of .click(). Just call .click() on the element itself.
driver.findElement(By.id("test-id")).click();
or
WebElement e = driver.findElement(By.id("test-id"));
e.click();
One way that I use regularly to avoid stale elements is find the element only when you need it and generally I do this by in a page object method. Here's a quick example.
The page object for a home page.
public class HomePage
{
private WebDriver driver;
public WebElement staleElement;
private By waitForLocator = By.id("sampleId");
// please put the variable declarations in alphabetical order
private By sampleElementLocator = By.id("sampleId");
public HomePage(WebDriver driver)
{
this.driver = driver;
// wait for page to finish loading
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(waitForLocator));
// see if we're on the right page
if (!driver.getCurrentUrl().contains("samplePage.jsp"))
{
throw new IllegalStateException("This is not the XXXX Sample page. Current URL: " + driver.getCurrentUrl());
}
}
public void clickSampleElement()
{
// sample method code goes here
driver.findElement(sampleElementLocator).click();
}
}
To use it
WebDriver driver = new FirefoxDriver();
driver.manage().window().maximize();
driver.get("http://www.example.com");
HomePage homePage = new HomePage(driver);
homePage.clickSampleElement();
// do stuff that changes the page and makes the element stale
homePage.clickSampleElement();
Now I no longer have to rely on an old reference. I just call the method again and it does all the work for me.
There are many references on the page object model. Here's one from the Selenium wiki. http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern
If you want to read more info on what a stale element is, the docs have a good explanation. http://docs.seleniumhq.org/exceptions/stale_element_reference.jsp
The problem I have is that my Page Object class finds all of the objects on the screen and then sends it to a more generic method to do the actual data entry. During this data entry process, some objects become stale and I get the "StaleElementException".
My plan is to catch that exception and attempt to re-find the element again.
Is there a way of extract the selection strategy from the runtime WebElement object other than doing a "object.toString()" and then parsing it?
I use page objects by defining the locators at the top of the class, then the constructor that verifies that we're on the right page (etc), and then methods for each action available on the page. Here's a simple example for the Google search page.
GoogleSearchPage.java
public class GoogleSearchPage
{
private WebDriver driver;
private By waitForLocator = By.id("lst-ib"); // optional
private By searchBoxLocator = By.id("lst-ib");
private By searchButtonLocator = By.cssSelector("button[name='btnG']");
private By feelingLuckyButtonLocator = By.id("gbqfbb");
public GoogleSearchPage(WebDriver webDriver)
{
driver = webDriver;
// wait for page to finish loading
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(waitForLocator));
// see if we're on the right page
if (!driver.getCurrentUrl().contains("https://www.google.com"))
{
throw new IllegalStateException("This is not the Google search page. Current URL: " + driver.getCurrentUrl());
}
}
public void doSearch(String searchString)
{
driver.findElement(searchBoxLocator).sendKeys(searchString);
driver.findElement(searchButtonLocator).click();
}
}
GoogleSearchTest.java
public class GoogleSearchTest
{
public static void main(String[] args)
{
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");
GoogleSearchPage googleSearchPage = new GoogleSearchPage(driver);
googleSearchPage.doSearch("selenium");
System.out.println(driver.getCurrentUrl().contains("#q=selenium"));
}
}
This is obviously a super simple example but it shows one good way to create page objects that should significantly reduce the frequency of StaleElementExceptions and, in some cases, speed up your script execution because you only scrape what you need and move on.
Reduce the urge to provide a getX() and clickX() method for each and every element on the page. Instead favor task based methods. Ask yourself what tasks a user is going to want to accomplish on the page and provide methods to do those tasks. It will keep your page object API much cleaner and more clear in what it provides to the consumer (you and other script writers).
I created a POM for 'Create project' page
public static class addProjInfo_container
{
public static WebElement ProjName_txt(WebDriver driver)
{
element=driver.findElement(By.xpath("//label[text()='Project Name']/following-sibling::input"));
return element;
}
// and so on for every text field for adding project...
And I created a TestUtility class with method for waitForElement as show below
public final class TestUtility {
private static WebDriver driver;
public static void waitforElementXpath(final WebDriver driver,final int waitTime,final String xp)
{
WebDriverWait wait =new WebDriverWait(driver, waitTime);
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath(xp)));
}
}
Now, in Test script I want to avoid using Thread.sleep() to wait for webelement to be ready to start performing actions.
so, I use
TestUtility.waitforElementXpath(driver,10,CreateProject_Page.addProjInfo_container.projName_txt(driver));
But,it displays error as
The method waitforElementXpath(WebDriver, int, String) in the type TestUtility is not applicable for the arguments (WebDriver, int, WebElement)
Kindly, let me know how to handle the issue.
Basically your want to reverse the By to get its string and you are using xpath
so change to this which return the String instead of WebElement
public static class addProjInfo_container {
public static String projName_txt(WebDriver driver) {
By by = By.xpath("//label[text()='Project Name']/following-sibling::input");
driver.findElement(by);
return getSelectorAsString(by);
}
public static String getSelectorAsString(By by) {
String str = by.toString();
return str.substring(str.indexOf(" ") , str.length());
}
// and so on for every text field for adding project...
}
hope this could help
This is really a convoluted way of trying to accomplish this task. Your ProjName_txt() method already has found the element because that's what it returns so you don't need to wait for it to appear by using waitforElementXpath(). I would recommend that you read some articles on OOP and classes before you write too much more code.
The best way is:
String name = driver.findElementByClassName("classnamesample").getText() ;
Just add .getText() in the last of xpath and receive this as String.