Initialize WebElements for part of a page - selenium

I'm following the Page Object model approach. I’m working on implementing a SearchResultsPage where a bunch of search results are displayed. In thinking about this page, I would like to implement it in such a way that it would support a getSearchResultByIndex(int index) method. Ideally, I would like the return type of this method to be a SearchResult, which would be a mini-page object (aka panel) that encapsulates the functionality found on a search result item since there are a number of attributes of a search result that the user can interact with. I don’t see how to accomplish this though. I was hoping to find a method like PageFactory.initElements() that would take in the WebDriver, a WebElement or selector (that identified an individual search result), and an instance of my SearchResult, but haven’t seen anything.
For clarity. Here's the basic structure of a SearchResults page.
<div class="searchResultsContainer">
<div class="searchResult">various internal fields to interact with/inspect</div>
<div class="searchResult">various internal fields to interact with/inspect</div>
...
<div class="searchResult">various internal fields to interact with/inspect</div>
</div>
It seems like this has to be a common problem out there that people have solved. I've used this "panel" notion for other common page elements like header, footer, etc, but never in the case where multiple instances of the same panel type are on the same page.
Any thoughts would be appreciated. Thanks.

If it were me I would approach it differently. I would split this into 2 page object classes. One for SearchResults, and one for SearchResultPage. The SearchResults would be the generic results list and actions you can take on those results. Within that class you would add a method to click on an individual search result, to pop up the details of that result, that would be what returns your SearchResultPage object.
Here is a rough sketch of what that method could look like inside your SearchResults page object. Not sure what language you are using but this is in C# (Java would be similar, Python much different but you'll get the general idea):
public SearchResultPage GetSearchResult()
{
// do something to click and show search details
return new SearchResultPage(_driver);
}
And then a skeleton of the SearchResultPage class object itself:
public class SearchResultPage
{
IWebDriver _driver;
// add whatever elements you want to work with specific to that single record view
//constructor
public SearchResultPage(IWebDriver driver)
{
_driver = driver;
}
// add whatever methods you want to interact with the elements in that view
}
The good thing about keeping the page objects separate in this case is SearchResults could actually be used in other areas of the application as well, if there are results on other pages that use the same elements etc. I find myself taking out common page elements (drop down menus, grids, etc) into their own objects all the time. Otherwise you end up repeating a lot of code if you stick to strict Page Object model where common functionality exists on multiple pages.

I think I've got this solved. I ended up abandoning PageFactory.initElements(), which I think I've learned is really key and likely an old-school way of implementing the page/object model. Adopting the use of By rather than FindBy seems to work much better as long as the appropriate conditional WebDriverWait.until(ExpectedConditions.elementToBeClickable(elementLocator)) is used.
After coming to this understanding, introducing the concept of a panel locator in my base Panel class allowed me to combine that locator with a nth-of-type(idx) locator to get things wired up and working as expected. Here's a simplified example of that in use in my SearchResultsPage:
public SearchResult getSearchResult(int idx) {
SearchResult res = new SearchResult(getWebDriver(),
By.cssSelector(".searchResultsContainer .seachResult:nth-of-type(" + idx + ")"));
return res;
}
My SearchResult class then just has a number of By locators defined that essentially call new ByChained(panelLocator, locator);
So glad to have solved this!

Related

Selenium Page Factory - finding element with nested selectors

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.

Best practices using the Page Object Mode

I'm trying to figure out the best way of making my Page Objects simple, and whether to put absolutely all logic elsewhere. And whether all Selenium code/functionality should be in the Page Object, or in the Step Definition methods I use to access it.
I have the following structure:
Cucumber.feature files
Given blablabla
Java/Kotlin step definitions
#Given("^blablabla$") {
}
And the page object files
fun getOwnerFields(): MutableList<WebElement> {
return driver.findElements(By.if("owner-fields")
}
As a simple example.
Now, what I cannot come to an agreement with myself on, or find much of other's opinions about, is:
Should I do page actions - for instance, a button click, in the Step Definition class OR in the Page Object?
This:
#Given("^I click on the Next button$") {
startPage.nextButton().click()
}
PO:
fun nextButton(): WebElement {
return driver.findElement(By.id("next-button")
}
Or:v
#Given("^I click on the Next button$") {
startPage.clickNextButton()
}
PO:
fun clickNextButton(): WebElement {
return driver.findElement(By.id("next-button").click()
}
I've tended to think that it's best to keep all the page and selenium code in the Page Object. But in cases like this, it makes the PO bigger, because I'll need different methods for clicking, checking for visibility, counting etc. While if all this is done in the step definitions, the PO can practically contain nothing but getters and setters for the page elements.
Anyone got any light to shed? I know it's not THAT important, but I tend to be quite occupied with the best and/or cleanest way of organizing my code.
This is indeed opinion based question. It depends on the bunch of factors including what your app can and how flexible the user interaction is assumed to be.
As to me since your core abstraction is assumed to be on Gherkin statement level I would say I does not make sense to put a lot of abstraction on lower levels.
Button is not that good example actually because normally you only can click it so there would hardly be other types of interactions. But for other elements my choice would definitely fall on your first approach.
Such the approach would let you to concentrate on the logic that is specific for the step so that being in the step definition you can clearly see what's happening there without extra navigation through the classes.

Create a View Component template/container that accepts HTML or other components as parameters

I have searched many places and have not seen anything similar to what I am thinking.
Let's say I want to create a reusable container component, like a card, form, or a modal, and save that as a View Component. How would I add a new view components inside of the "body" of that main View Component in a way that would make it maximally reusable?
The syntax here is just to demonstrate the idea of course, but for example, something like this:
<vc:parent var1="x" var2="y">
<vc:child var3="a" var4="b"></vc:child>
<vc:child var3="c" var4="d"></vc:child>
</vc:parent>
Is anything like this possible?
This doesn't necessarily need to use View Components—maybe partial views?—so long as the primary goal of reusing the containers of other reusable elements is achieved.
I have looked into helpers, but they are no longer available in ASP.NET Core.
So I figured out how to do it.
I used part of this tutorial about helpers: Templates With Razor
And modified it so it works with ViewComponents.
So to get it working, in a minimal example, create a ViewComponent class as such:
[ViewComponent(Name = "Test")]
public class VCTest : ViewComponent
{
public IViewComponentResult Invoke(Func<dynamic, object> Content)
{
return View(Content);
}
}
Create the actual template that you want, in a cshtml file like this:
#model Func<dynamic, object>
<div id="SomeTemplateTest">
#Model(null)
</div>
In this very simple case I just used the Func as model since there is only one parameter, but for more parameters you'd just have to call #Model.funname(null) instead of just #Model(null). No big deal.
when calling this component from your view, create your child elements beforehand like so:
#{Func<dynamic, object> children=
#<div>
<vc:child var1="a" var2="b"></vc:child>
<vc:child var1="c" var2="d"></vc:child>
<vc:child var1="e" var2="f"></vc:child>
</div>;}
The div is there only to encapsulate all the elements. I haven't found a way around that but it has no major implications.
Then call the parent ViewComponent tag passing on the parameters accordingly:
<vc:test content="children"></vc:form-test>
And that's it, worked perfectly. It is unfortunate that I could not find a more seamless way. But this does the job.
If anyone knows of a better alternative I'd love to know more.

Pass value to xpath at runtime

Please read the whole Q before disliking or commenting something. I have searched on internet before posting it here. I'm having the below project structure.
pages(package)
> Homepage.java
test(package)
> Flipkart.java
Inside Homepage.java i have declared all the WebElements using POM Page Factory methods and created respective method to click on Electronics link.
#FindBy(xpath = '//input[#title='Electronics']')
private WebElement lnkElectronics;
Inside Fipkart.java I'm calling the Electronics click method.
My doubt over here is the declared WebElement is specifically for Electronics.
Is there a way i can create a WebElement with type like mentioned below and pass value for %s dynamically from main method?
#FindBy(xpath = '//input[#title='%s']')
private WebElement lnkElectronics;
Answer referenced from Page Object Model in Selenium
You cannot create a FindBy with Variable, FindBy accepts only constants.
In case if you want to achieve that variability then you should write or find the element using normal findElement method
As per the Test Design Consideration following Page Object Design Pattern :
A Page Object is an object-oriented class that serves as an interface to a page of the Application Under Test. Your #Tests uses the methods of this Page Object class whenever they need to interact with the User Interface of that page. The benefit is that if the UI changes for the page your #Tests themselves don’t needs to be changed. Only the code within the Page Object needs to be changed.
Advantages :
Clean separation between test code and page specific code such as locators, methods and layout.
A single repository for the operations offered by the page rather than having these services scattered throughout the tests.
Based on these Page Factory features you won't be able to create any generic WebElement for which you can pass value dynamically from main() or #Test annotated method e.g.
#FindBy(xpath = '//input[#title='%s']')
private WebElement lnkElectronics;
You can find almost a similar discussion in Where should I define modal specific code in Selenium Page Object Model pattern
WorkAround : on page class you can define a method, and can pass the text on the fly from the calling class to click on specific tab
if you want to click any common text on the page. You can create a method as given below and can pass the text on the fly to click on that specific tab on that page
public void clickTab(String tabText){
String tabxpath = "//div[contains(text(), '" + tabText + "')]";
driver.findElement(By.xpath(tabxpath)).click();
}

#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.