2

I like to use POM pattern with page factory and in fact I have been using it since long in my various automation frameworks.

Some time I am facing issue where I have to write text based xpaths in some projects and then when text changes, my xpath fails.

For example : currently my xpath is //div[text()='Welcome'], Now when in application developer change welcome to something else , my xpath will fail. I can not use id , class etc sometime as most of things are dynamic so I found text based xpath is only reliable option.

As we can not pass dynamic text with @Findby, Is there any better way to handle such scenarios?

What I thought is as a solution :

  1. Create one separate class which will return me web elements only and I will pass text on call or from property file in xpath. For example :

private WebElement loginTextBox(String placeholder) { By.xpath(String.format("//a/h4[text()='%s']", placeholder)); }

But does this make sense? It will need more time to write methods for each web element. Can this be replacement of @Findby for dynamic values?

4 Answers 4

3
+50

Do you really want to test that the text Welcome is there? You say the identifiers are dynamic. In these situations, I always suggest to talk to the developers and make it better testable. Preferably developers and testers write and maintain the end-to-end tests together. Let them feel your pain.

Now when in application developer change welcome to something else , my xpath will fail.

I would suggest referencing the text from the original source file. I would expect most developers to anticipate the internationalization of an application. Meaning the string texts are probably already separated from the code/view in property files. If not, maybe now is a good time to start doing this.

texts = ResourceBundle.getBundle("MessagesBundle", currentLocale);
String msg1 = texts.getString("welcome");

Now if the resource bundle text changes the tests still work. I think you can even remove the magic string "welcome" and make sure you get a compile error when someone removes the text from the code. This would be my ideal situation, the test code won't compile when a developer changes the application, meaning they have to fix it.

1
  • 2
    +1 , for the idea of 'sharing the pain' of automation with developers. Commented May 10, 2019 at 1:11
2

The page object model is a design pattern that describes the best approach for modeling a test automation framework when it comes to web automation. The @FindBy() annotation is just a shortcut provided to developers by the PageFactory class. Use of the @FindBy() annotation is not mandatory for you to use the page object principles. You can still model your classes the way the web page is designed, simply by storing the By locators that cannot be used from inside the @FindBy annotations (i.e. xpath text()).

Instead of creating WebElements define your page objects in the desired class just by storing their corresponding locators in By objects:

 private static final By loginTextBox = By.xpath("//div[text()='Welcome']");

Then just use them via driver.findElement(loginTextBox) inside your code. Once the text changes you only need to change the By locators in a corresponding page object class, so the page object principle stays, only the annotation is gone.

There is also a way to keep using the @FindBy() annotation and locate element by text via By.partialLinkText() and By.linkText(). This of course is only valid for links. The first works with any text surrounded by <a></a> tags (similar way to String.contains()), the latter will only work for the exact text match between the <a></a> tags.

0

Creating this class would break the single responsibility principle, because the responsibility of representing a page would be scattered in two objects.

One thing to ensure that the attributes would have some value before usage would be to fill it out in the constructor, injecting the dependencies as needed:

class LoginPage {

...

   LoginPage(String placeHolder) {

      this.loginTextBox = By.xpath(String.format("//a/h4[text()='%s']", placeHolder));

   }

   // Default placeholder case

   LoginPage() {

     super("Login Button");

   }

}

This way, the client, which instantiate the LoginPage, can determine (partially) how to locate the loginTextBox. It is known as Dependency Injection.

2
  • 1
    But here I could have 40 - 50 xpaths, Should I still use it? i.e create 40 constructors? Commented May 3, 2019 at 12:48
  • Good chances are that you are breaking SRP in this case. A LoginPage is composed, e.g., Header, Footer, LoginArea, etc. Each noun is a different class. The integration to form a LoginPage object is done through composition and inheretance. More details on this kind of refactoring here stackoverflow.com/a/9686951 Commented May 3, 2019 at 13:09
0

Instead of writing functions for each element separately,I would simply make an reusable function to pass text as parameter:

Python Selenium Implementation:

def by_css_containing_text(driver, selector, text):
    elements = driver.find_elements_by_css_selector(selector)
    return [element for element in elements 
            if text in (element.text or 
                        element.get_attribute("textContent") or 
                        element.get_attribute("innerText"))]

# usage
driver = webdriver.Firefox()
driver.get(url)

elements = by_css_containing_text(driver, '#myid .myclass', 'Some Text')
self.assertTrue(elements)
self.assertTrue(elements[0].is_displayed())
2
  • I am looking for java. Commented May 8, 2019 at 6:35
  • I gave the logic. Commented May 8, 2019 at 11:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.