Selenium Page Factory - finding element with nested selectors - selenium

I'm trying to use Page Factory and #FindBy to initialize my WebElements. However, I'm running into trouble when I have an element that cannot be found with just the ID.
The following selector is a little more complex to find, so I'm using Selenium's ByChained:
val endreBoligModal: MutableList<WebElement> = driver.findElements(ByChained(By.className("hb-card"), By.className("hb-card-header"), By.className("hb-card-tittel"), By.tagName("span")))
The reason is that, for some reason, finding the element with a unique ID doesn't work. Selenium just cannot find it.
So, with regards to the Page Factory and the #FindBy way of creating elements - how do I do it?
The way I've used it so far is like this:
#FindBy(id = "login")
private WebElemement login
Or
#FindBy(css = "[id=login]")
By for the chained selector element, I cannot figure out how to do it with Page Factory.
I think there's something called #FindBys (with an s at the end). At least, there seems to be. But for the life of it I cannot find ANY documentation about it on the net, so I don't even know if it's relevant.
All help is appreciated.

You can just look at the sources of org.openqa.selenium.support.FindBys. There is a short but informative description saying:
Used to mark a field on a Page Object to indicate that lookup should use a series of #FindBy tags in a chain as described in ByChained It can be used on a types as well, but will not be processed by default.
Eg:
#FindBys({#FindBy(id = "foo"),
#FindBy(className = "bar")})
So the example seems pretty straightforward. Give it a try.

Related

Selenium check element presence if using FindBy annotation

Trying to use the #FindBy annotation my feeling is there is no perfect way to check the presence of an element.
There are similar discussions here or here, but I see only workarounds and nothing like
#FindBy (id = "abc")
private WebElement TestElement;
if (TestElement.isPresent) {...};
I found solutions with #FindBys, but this way I have to implement an additional #FindBys for every element I would check the presence within DOM? Not really nice.
Every alternative solution with ExpectedConditions.presenceOf... or anything using FindElement needs a locator as parameter instead of a WebElement, correct?
Sure I can build workarounds using e.g. ExpectedConditions.elementToBeClickable(WebElement) within a try/catch and raise an org.junit.jupiter.api.Assertions.fail, but this is not a real DOM check.
To me it seems I'm more flexible with classic By definitions of Page Objects (e.g. private By searchInput = By.xpath("\\input[#class='input_search']");) instead of using #FindBy. Using #FindBy I always deal with the WebElement itself and there is no chance to check the presence before, right?
What are best pratice solutions to check the DOM presence of elements in context of page objects using #FindBy? Or should I stay with By to be safe.
This may not be the best solution but you can use findBy and return a list:
List<WebElement> TestElement = driver.findElements(By.id(...));
and check the size of the list elelement each time (in a while loop) to see if it is not empty.

In the Page Object Model, why do we use Webelements instead of Strings as class variables?

Simply put: when implementing the POM framework we have Pagefactory initialize all the elements in a pageobject. Why do we do this instead of storing the xpath/css selectors as strings and calling those as needed instead?
ex:
#FindBy(xpath = "//Button[text()='Add and Edit']")
#CacheLookup
private WebElement addAndEdit;
vs
private String addAndEdit;
This is not defined as part of the page object model, this is defined by PageFactory which is a helper class in initializing elements in a page object. The intent there is to set up a proxy so that the element's location strategy is defined by the #FindBy and you can reference the WebElement and it will go look it up for you when you use the reference.
If you are going to store locators, don't store a string... store instead a By locator. It has the extra information of the TYPE of locator, e.g. By.id, By.cssSelector, etc. I think this is much cleaner approach and will prevent you from having to somehow determine what type of locator that string variable is. This is the approach I use in all the Selenium projects that I have written and/or maintain.
According to the Selenium contributors, PageFactory should not be used. See this yt video of Simon Stewart, Lead Committer, stating don't use it (27:26) and so on and why. The link starts the section that leads into his comments on PageFactory.

Create a Reusable method to Verify list elements by Selenium WebDriver

I am new to this site and don't know how things show up here. I was reading the post from below where String array is being used to ListwebElements.
Verify list elements by Selenium WebDriver
String[] expected = {"GRAM", "OUNCE", "POUND", "MILLIMETER", "TSP", "TBSP", "FLUID_OUNCE"};
I am trying to do something similar using String Array trying to get different buttons on a UI page. I want to make this method reusable by changing the "expected" list per test. Does anyone know how you would make this method Reusable?
In my case, "expected" list is different each time depending on a page.
I would suggest to use the custom annotation for reusing purpose. Please see the example here.
If you are using JUnit you can also use parameterized test

Selenium's Webdriver.execute_script() returns an empty list while Chrome dev tools return a populated list

I'm trying to use Selenium's Webdriver.execute_script() to get a list of elements from Reddit's homepage. (Before you recommend PRAW: Reddit is not actually what I want to get elements from, I'm just using it as an example.)
Even though my executed script works fine when I run it in Chrome's dev tools console, Selenium's method only returns an empty list, which should be populated with the title elements of Reddit posts.
import urllib
from selenium import webdriver
from BeautifulSoup import BeautifulSoup
#Path to the chromedriver is definitely working fine.
path_to_chromedriver = 'C:\Path\goes\here\chromedriver.exe'
browser = webdriver.Chrome(executable_path = path_to_chromedriver)
url = 'http://www.reddit.com/'
browser.implicitly_wait(10)
browser.get(url)
code = 'document.getElementsByClassName("title may-blank loggedin")'
content = browser.execute_script("return "+code)
if len(content) == 0:
print content
else:
print len(content)
browser.quit()
I've also tried using the webdriver.set_script_timeout() and webdriver.set_page_load_timeout() methods.
Example of the target element:
<a class="title may-blank loggedin" href="/r/IAmA/comments/2necex/i_am_joel_hodgson_creator_of_mystery_science/" tabindex="1">I am Joel Hodgson, creator of Mystery Science Theater 3000, why don't you come at me?</a>
The JavaScript you use with execute_script should work.
However, as the class loggedin suggests, no element will match the loggedin class unless you are actually logged into the site. When you start a new Selenium instance, it is not logged in, so you won't find any element with the loggedin class. Edit your code to remove loggedin from your getElementsByClassName call and you'll get a list of matched elements.
It does not matter whether you use execute_script or find_element.... No elements will match the class loggedin until you are logged in. This being said, although sometimes there are excellent reasons to use execute_script to find elements, I don't see a good reason to use execute_script in this case so I'd suggest something like
browser.find_elements_by_css_selector('.title.may-blank')
without the loggedin class. This will return all elements that have the title and the may-blank classes.
A few problems.
Don't use JS to find the elements, that's the entire opposite to the point of Selenium.
Secondly, you have some misunderstanding behind what CSS selectors & Class name selectors are.
Thirdly, coupled with the above, your JS script is not a CSS selector. getElementsByClassName is very different, it will just return you what matches all of those classes.
Fourthly, in your comment you've bunched up the class name together. I assume this is because having spaces in there would have thrown a Compound Class error. This is correct and should have pointed you to the first three problems above. Now you've mushed them all into one string, it now no longer even relates to the original code you had - it would only return elements who had titlemay-blankloggedin as their class (obviously, none!).
Solution? I'd suggest using Selenium for what it's meant for and the locator strategies it has built in.
browser.find_element_by_css_selector('a.title.may-blank.loggedin')
(The selector can be made a little less brittle, but I've done it on purpose so you can see the effect).
The . in CSS selector will work as an and operator (an anchor element that has a class of title and a class of may-blank and a class of logged-in.)

#FindBy doesn't care if the element isn't there when I call initElements

For instance, let's say I have class FanPage, with this annotation
#FindBy(how = How.ID, using = "ctl00__lvph_Add")
private WebElement _AddFanButton;
and then in my test code I say
fanPage = homePage.GoToFanPage()
which does
return PageFactory.initElements(driver, CC_VendorStatisticsMetadata.class);
Now if my annotation is incorrect (let's say it should be ctl00_lvph_AddFan), I would expect my call to initElements to fail. However, it doesn't and it simply returns a FanPage object to me. It only fails if I try to use _AddFanButton.
How do I get PageFactory to look for my annotations from the start?
You don't. The PageFactory does lazy initialization, and that's how it's designed.
Consider a Page Object where certain of your elements don't exist on the page until some action is taken. Since Page Objects are intended to encapsulate business logic and not just the elements on the page, this is a perfectly logical scenario. In that case, your initElements() call would fail on page object initialization every single time, and not give you the chance to call the business logic method that would cause the element to appear.
It's possible that the PageFactory will not work for you if this is a requirement for your test framework. In that case, you'd do well to construct your own implementation.