Multiple locator for finding webelement with webdriver - java

I am using Selenium Webdriver with QAF.
The issue I am facing is related to finding an element on webpage.
for few of elements, different locators work at different times.
For example - sometimes name=nameA works and sometimes name=nameB(may be depending upon different environments of AUT, I have no clue).
Find code below:
public class HomePage extends WebDriverBaseTestPage<WebDriverTestPage> {
#FindBy(locator="name=nameA")
private QAFWebElement btnSomeElement;
#Override
protected void openPage(PageLocator locator, Object... args) {
driver.get("/");
}
}
What should I do to come over this issue?

While you are already using QAF you already have solutions available for such use case. First of all you should use Locator repository,
Instead of hard-coding locator in page just provide locator key.
For example:
In page.loc File
my.ele.locator=<locatorStretegy>=<locator>
my.ele.locator=name=elemName
In Page class:
#FindBy(locator = "my.ele.loc")
private QAFWebElement btnSomeElement; Now coming to your problem, if most of the locator very with environment then you can utilize
resource management capabilities of QAF. In other case you can
use alternate locator strategy provided by QAF. For example:
my.ele.locator=['css=.cls#eleid','name=eleName','name=eleName2']
my.ele.locator=['name=eleNameEnv1','name=eleNameEnv2']

You should get a selector that matches both environments, use css or xpath selectors.
Add html snippet with the sections/elements if you need any help.
Assuming that you are selecting by name and this name changes, you can write a selector that matches both names like:
css: [name=nameA], [name=nameB]
xpath: //*[#name='nameA' or #name='nameB']

It is very common that the locator changes sometimes. Some are even dynamic especially elements in the table rows. It is better to use more specific locator like xpath to locate the element.
#FindBy(locator = "xpath=//button[text()='ButtonName']")
private QAFWebElement btnSomeElement;

btn.login.page.logon.button = {\"locator\":[\"xpath=//div[contains(#id,'logonbutton')]//span[contains(text(),'Logon')]\",\"xpath=//*[contains(text(),'Logon') or #id='__screen0-logonBtn']\"]}
This is an example of the same for plain text file using multiple locators in QAF. Works for me.

Related

Python equivalent/alternative for Java field annotations

I recently moved from Java test automation to Python test automation. In Java, I would use a field annotation named #Important to specify which elements in the Page Object Model are important (so I can query the important elements for each page and check whether they are displayed). This looks similar to this:
class MyPageObjectModel {
#Important
#FindBy(...)
WebElement myElement1;
#Important
#FindBy(...)
WebElement myElement2;
#FindBy(...)
WebElement optionalElement;
}
I wonder whether something similar is available for Python? I found out about decorators, but those seem to be only applicable to functions and not to fields.

Initialize All pages in TestBase page Correct or Wrong habit?

i'm new to Selenium and Java and trying to figure out some things. The questing is straightforward, is it a good habit to initialize all the pages of an application from TestBase? e.g.
public class TestBase {
protected static WebDriver driver;
protected static PageBase base;
protected static HomePage home;
private void initPages()
{
base = new PageBase();
base.setUpWebDriver(driver);
home = new HomePage();
home.setUpWebDriver(driver);
}
private void webDriverSetUp()
{
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(5,TimeUnit.SECONDS);
}
private void webDriverClose() throws InterruptedException {
if(driver!=null)
{
Thread.sleep(5000);
driver.close();
}
}
public void webDriverInit()
{
webDriverSetUp();
initPages();
}
public void webDriverTerminate() throws InterruptedException {
webDriverClose();
}
}
or not, so every time you have to pass the driver to the page you want to test.
Is the load so big that it could cause problem during test execution?
Thanks in advance for any answers.
So it looks as if you are spinning up two separate drivers at the same time. One for the PageBase and one for HomePage. I strongly recommend that you only launch one driver executable at one time, unless you are multi-threading or using selenium grid. Or are they just adding a reference of the BaseTest driver to some field in the PageObjects? Not sure either way from the above code.
Also why do you have driver setup methods on the base page and in the base test? (page.setUpWebDriver() vs test.webDriverSetUp()) Little confused by that. I would handle all driver setup in the base test (along with all direct references to it). Move anything related to that out of basepage, in my opinion. Also, I may be misinterpreting that.
Also, I would remove the instances of home and base page from the TestBase. Instead, if you are NOT using multithreading and have no intent to, I would use singletons for page objects. Have the page object classes provide a static property that points to an instance of the page object, and store them all there in each page object. Additionally, the base page should be accessible from home page by default. You are extending it, right? That should not need an instance anywhere.
Singleton's have the added bonus of making it super easy to spin up something as needed. A sort of lazy-loading type process. The property that supplies the static self object should initialize anything on the page when it is referenced, and only then. Also, you may want to add logic that sets a flag to tell a page that it is dirty. If the webdriver navigates or refreshes, that flag should tell all page objects (if you have your PO's store IWebElement references) that it should re-get the entire page of elements if referenced again - which prevents stale element errors.
Although these are assumptions based on what I am reading, without the other source code, so ignore anything that is an incorrect assumption here.
Singleton example based on my suggestion above; kind of pseudo code:
public class LoginPage : BasePage {
public static LoginPage Instance {
get {
if(_loginPage == null || BaseTest.IsDirty) {
_loginPage = new LoginPage();
}
return _loginPage;
}
}
private _loginPage;
public IWebElement PasswordField ...
... any selenium logic that auto-populates values of page elements.
}
I have a strong preference to avoid populating page objects with values automatically. I personally prefer to make PageObjects a single repository of a page's element's identifying selectors, rather than instance of the actual object. It eliminates the majority of instances where you need to worry about stale elements and tracking that.

Using #FindBy in Selenium without PageFactory

I've been able to successfully use #FindBy with PageFactory but the person training me insists that I can still use #FindBy without using PageFactory. He showed me his code and from a quick glance, it does seem like he isn't using PageFactory. However, without using PageFactory, I always get the NullPointerException. I did ask him to look at my code and ...well he doesn't seem to know why my code does not work either. I'm hoping I could get fresh pairs of eyes to enlighten me.
Here is a snippet of my code
public class BaseClass {
public WebDriver driver;
private String title;
private Wait<WebDriver> wait;
public BaseClass(WebDriver driver, String title) {
this.driver = driver;
this.title = title;
wait = new FluentWait<WebDriver>(driver).withTimeout(45, TimeUnit.SECONDS).pollingEvery(2, TimeUnit.SECONDS);
}
}
//
public class Test extends BaseClass {
public Test(WebDriver driver) {
super(driver, "Login page");
}
#FindBy(name="j_password")
public WebElement password;
#FindBy(name="j_username")
public WebElement username;
public void startest() throws Exception {
username.clear();
username.sendKeys(id);
password.clear();
password.sendKeys(secret); }
}
No you cannot use without using PageFactory. Read the below lines for proper explanation :
Page Factory will initialize every WebElement variable with a reference to a corresponding element on the actual web page based on “locators” defined. This is done by using #FindBy annotations.
Annotations?
In Page Factory, Annotations are used to give descriptive names for WebElements to improve code readability. And annotation #FindBy is used to identify Web Elements in the page.
By default, PageFactory will search for elements on the page with a matching id attribute, If that fails, then it will search by the name attribute. But as we need more control over identifying elements in the HTML page and mapping them to our Page Object fields.
Following link has more details :
http://www.seleniumeasy.com/selenium-tutorials/page-factory-pattern-in-selenium-webdriver
Yes, you can do it without Selenium's PageFactory, but it means you need to implement your own equivalent of it.
The PageFactory doesn't do anything magical to create/populate the elements. It's power is in how it does some of the more advanced things like caching and only populating at the time the elements are referenced (which in the C# implementation is done by using a RealProxy, not sure of the Java equivalent).
Another option is to store By properties, and evaluate them as required.

Building a selenium framework using POM and need some advise from experts

Building a selenium framework using POM and trying to add common action class for repeatedly used functions like set text, get text, set radiobutton, listbox etc., structure like below
Test --> POM page object
POM page object --> common action class
#Test
public void verifyGooglebuttontext() {
System.out.println("set search value");
googlepage.setSearchValue("Google Test");
Assert.assertEquals(addC.verifytext(),"Google");
}
POM
#FindBy(name="q")
WebElement txtGoogleSearch;
public static setSearchValue(String txt) {
setText(txtGoogleSearch, txt)
}
Common Action class
public void setText(WebElement element,String Value ){
element.sendKeys(Value);
}
My query is whether this will impact in performance as I have to call 2 functions rather than setting and getting control values directly in Page object class.
Please advise.
Given that most of the time you'll wait for the page loading and search for elements in the loaded page's DOM, I wouldn't worry too much about two extra function calls.
Really, page loading will take ages (> milliseconds) in comparison with a method call (microseconds or less).
What you gain from that is massively better: if the UI changes, you'll have to revisit a small number of places to adjust to the new element locations/type/ways to carry an action (while your #Test logic can stay virtually unchanged). Which means you'll be up-and-running in hours rather than a couple of day to go all over your code and locate/change all places affected by a change in UI.
In this case, you aren't reducing redundancy, you are adding to it. You are taking a one liner driver.findElement(...).sendKeys(value) and changing it to a different one liner setText(driver.findElement(...), value) but creating an additional function setText(). There's no need to add another layer of complexity here. It doesn't save any time here or during maintenance.
#FindBy(name="q")
WebElement txtGoogleSearch;
public static setSearchValue(String txt) {
txtGoogleSearch.sendKeys(txt);
}
I would not use PageFactory. If you test sites of any complexity, you will find yourself refetching elements on the page after the initial load. To avoid this, define your locators are part of the page object and use them to fetch elements when you need them.
Sample page object
public class AutomationPracticeTablePage
{
private WebDriver driver;
private By pageTitleLocator = By.xpath("//h1[text()='Automation Practice Table' and #class='h1-size']");
private By tableHeadingLocator = By.cssSelector("table > thead th");
public AutomationPracticeTablePage(WebDriver webDriver) throws IllegalStateException
{
this.driver = webDriver;
// wait for page to finish loading
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(pageTitleLocator));
// see if we're on the right page
if (!driver.getCurrentUrl().contains("/automation-practice-table/"))
{
throw new IllegalStateException("This is not the Automation Practice Table page. Current URL: " + driver.getCurrentUrl());
}
}
public List<String> getHeadings()
{
List<String> headings = new ArrayList<String>();
for (WebElement heading : driver.findElements(tableHeadingLocator))
{
headings.add(heading.getText());
}
return headings;
}
}
Sample code to use the page object
driver.get("http://toolsqa.com/automation-practice-table/");
AutomationPracticeTablePage automationPracticeTablePage = new AutomationPracticeTablePage(driver);
System.out.println(automationPracticeTablePage.getHeadings());
I would advise you start by not abbreviating Page Object Model as POM. POM is associated to Maven as a project dependency management file. Also to get your your questions in a more clear state. The more clear and concise you are, the better and quicker support you will get. You can visit http://toolsqa.com/ to get better understanding of Page Object Model structure, get creative, and make better your terminology.
This is a #Test script (method), not a class. It is also not a page object model. Any method annotated with this is considered a Test script to be executed.
#Test
public void verifyGooglebuttontext() {
System.out.println("set search value");
googlepage.setSearchValue("Google Test");
Assert.assertEquals(addC.verifytext(),"Google");
}
This is a WebElement and a functional method within a page object. Not a Page Object Model
#FindBy(name="q")
WebElement txtGoogleSearch;
public static setSearchValue(String txt) {
setText(txtGoogleSearch, txt)
}
This is not a class. It is a method in a class.
public void setText(WebElement element,String Value ){
element.sendKeys(Value);
}
Okay, now, your setText(WebElement, String) method is too thin to provide any value as is. You can however, add additional support into the method if you find it necessary. Most of my experience doesn't require additional support for input fields, but I have ran across some that do need additional support when javascript is involved; DOM manipulation after page load.
The point and advise here, is that don't over complicate what isn't necessary. Work with what currently works, and tweak for support as you go. Now, if you can see further into the outlook of the project that you will be needing such support, go for it, and implement the support ahead of time.

First Selenium using Maven and Java,

I have written my even first code using Selenium Web driver, Maven and Java. Can someone give expert opinion if I am in the right direct and if this particular code can be improved.
public class myFirstTest {
#Test
public void startWebDriver(){
WebDriver driver = new FirefoxDriver();
driver.navigate().to("http://www.ft.com/home/uk");
Assert.assertTrue("title should start with World Business", driver.getTitle().equals("World business, finance, and political news from the Financial Times - FT.com"));
driver.findElement(By.id("ftLogin-signIn")).click();
driver.findElement(By.xpath("/html/body/div[4]/div[2]/div[1]/div[2]/div[2]/div[3]/form/fieldset[1]/input")).sendKeys("xxx#mail.com");
driver.findElement(By.id("ftLogin-password")).sendKeys("xxxxxxxx");
driver.findElement(By.xpath("/html/body/div[4]/div[2]/div[1]/div[2]/div[2]/div[3]/form/fieldset[3]/p[1]/button")).submit();
driver.quit();
}
}
Depending is you are learning or creating a test framework for your company there are some pointers.
You functions and values for finding elements are not optimal. cssSelector or id is to prefer.
;
By.cssSelector("input[id$='ftLogin-username']");
or
By.id("ftLogin-username");
Never use xpath if not is absolutly necessary. Escpecially those dependending on the DOM object. Instead change the webpage implemenation to set an id on the webpage elements.
For the button which doesnt have a unique class or id i would use the text instead
driver.findElement(By.cssSelector("button:(*'Sign in'*)"));
If this login part is to be used in several test cases, create a helper class with a login function instead. Otherwise you have to wright the same code over and over again. If a id or xpath where to change you also have to update all your test cases which uses that element.
Add an assertion att the end of the test case to verify that the login has been successful.
Definitely the code you have written can be improved.
Considering the fact, you have just started with WebDriver, below are few suggestions-
Avoid using such long xpaths for locating elements. It has lots of
dependency on DOM. Use short xpaths wherever possible or go for
using other locators.
You can read data from files(s) required for your tests.(example : username, password)
You can use annotations such as #BeforeClass, #BeforeMethod etc., if you are using any unit testing framework to run your tests such as JUnit, TestNG.

Categories