Find locator in selenium by name with a parameter - java

I'm struggling to figure out how to find an element by its name, but not by multiplying elements locators but to create one for its kind and passing a parameter. E.g there's a page with 10 buttons 'Add to Cart' for different items ('Laptop A', 'Laptop b','Laptop c', etc), so instead of creating 3 different elements I want to have one, something like >>
webElement elementByName(String itemName) = driver.findElement(By.xpath("//div[#button='add to cart' and #title = '" + itemName+ "']"))
Does anything like this exist in selenium? I'm new to selenium and could not find anything similar. thanks

Yes, it is possible. Before I share the 'answer', I would like to point out the following:
Some things to note
The xpath that you need to provide is nothing special to Selenium. It is a W3C standard.
https://www.w3.org/TR/2017/REC-xpath-datamodel-31-20170321/
If you're using Chrome, before even attempting to use it in Selenium, you can first test out your xpath by going to Developer Tools (F12) and pressing 'CTRL+F', and typing in the xpath in the search box that appears.
If your xpath matches an element, it will be highlighted in yellow. If that xpath works, it should also work if you pass that same xpath into Selenium, unless there are other reasons such as framework being used, etc. For instance, Shadow DOM xpaths are not especially friendly to use.
Searching by Visible Text
Going by your example of Laptop A; Laptop B; Laptop C, if you want to find the element based on visible text, then you can use the text() function (that xpath provides).
driver.findElement(By.xpath("//div[text()='Laptop A']");
driver.findElement(By.xpath("//div[text()='Laptop B']");
With the above in mind, you can thus put the strings A and B into an array and loop through them if you want:
for(int i=0; i<arr.size; i++){
driver.findElement(By.xpath("//div[text()='Laptop " + arr[i] + "']");
}
Additionally, you can also use the contains function that sort of acts like a regex:
driver.findElement(By.xpath("//div[contains(.,'Laptop')]");
This will match anything that has a visible text of Laptop, i.e. Laptop A, Laptop b, This is a Laptop, etc.
EDIT: based on comments below
public WebElement selectButtonByName(String name){
return driver.findElement(By.xpath("//div[#button='add to cart' and #title = '" + name+ "']"));
}
And then by using it:
WebElement tvElement = selectButtonByName("TV").click();

If i understood correctly, you need to wrap your the findElement method and create a custom method for it, best practice is always wrap and have custom methods for all driver related actions, which will help you on the long run and will be scalable. here is an oversimplified example:
first step is to wrap your driver:
public class MyDriver {
//initialise your webdriver, the way you see fit and lets assume you name it
myDriver;
// create your custom find element method
public WebElement findElement(final By by){
return myDriver.findElement(by);
}
// create a method which uses the findElemet method and passes the webelement to next
// method
public WebElement click(final By bys){
return clickElement(findElement(bys);
}
// this is the click method, which you can use directly for webelements
public WebElement clickElement(final WebElement element){
Actions action = new Action(myDriver);
action.moveToElement(element).click(element).build().perform();
return element;
}
}
// Now that we have our basic wrapper, lets use it for your needs:
public class YourTestCase {
MyDriver driver = new MyDriver(); //this is not best way to do it, just an example
// now you can use whatever parameter you like and add it with custom xpath logic
private WebElement myElementFinder(final String parameter){
return driver.findElement(By.xpath("//div[#button='add to cart'][#title = '" +
parameter + "']"));
}
// here you click the button, by passing the passing only
public void clickButton(final String buttonText){
driver.click(myElementFinder(buttonText))
}
}
By taking this logic you can build a solid framework, hope it helps, comment if need more explanation, cheers.

Related

No such element exception in Selenium code using Java

While i tried automating a website using selenium and Java i got no such element exception even though i got correct xpath while inspecting the field. After giving a city by sendkeys i tried to select 1st option from dropdown by select method and actions keyword but both didnt work and throwed exception. I will attach the code and image of inspected website.Kindly help with the issue
#Test
public void abhibusHotel() throws Exception {
driver.get("https://www.abhibus.com/");
HomePage ohome=new HomePage(driver);
ohome.hotelPage();
HotelBooking obook=new HotelBooking(driver);
obook.bookingDetails();
Thread.sleep(2000);
public void bookingDetails()
{
try {
wait.until(ExpectedConditions.visibilityOfElementLocated(city)).sendKeys("Bengaluru");
//Select se=new Select(wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("(//span[text()='Beng'])[1]/parent::div"))));
//Select se=new Select(wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//span[text()='Beng'][1]"))));
//se.selectByIndex(1);
WebElement tooltip= wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//span[text()='Beng']/parent::div")));
oaction.moveToElement(tooltip).click().build().perform();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
Your screenshot is saying that there are 9 matching elements. Selenium takes the first one. That first one is not necessarily visible. You need to narrow your xpath query so that it returns the only one that represents what is currently visible.
For this situation, I see two ways of doing it by using the xpath selector
//here we'll search for the city
driver.findElement(By.id("ex1_value")).sendKeys("Bengaluru");
//first option - this will give more than one element for the desired text
driver.findElement(By.xpath("//div[#id='ex1_dropdown']/*/div[#ng-if='imageField']/img[contains(#ng-src, " +
"'city.png')]/following::div[contains(#ng-bind-html," +
" 'result.title')]/span[normalize-space(.='Bengaluru')]")).click();
//second option and the tricky one - in your web application the results are unique for the city - and the developers are using an image in the displayed list, you can check by that icon if you need a city, hotel, etc
driver.findElement(By.xpath("//div[#id='ex1_dropdown']/*/div[#ng-if='imageField']/img[contains(#ng-src, " +
"'city.png')]")).click();
both options are working - but be aware that after the city is selected the website jumps automatically to the next field (the date inputs).

What is best strategy to verify text of multiple elements without adding a ton of locators in a selenium framework?

I want to automate a test using selenium java in which I need to check whether a specific text is NOT present on the entire page.
The page has many elements where this text may be present. In general, if it were a few elements, I could resolve this via standard driver.findElement(By.xpath("//somepath")).getText(). But, I want to write an efficient test that doesn't have tons of locators just for this test.
You can use XPATH selector '//*[contains(text(), "YOUR_TEXT")] to find a text that you need.
Example of the code on Python:
def find_text_on_the_page(text):
selector = '//*[contains(text(), "{}")]'.format(text)
elements = browser.find_elements_by_xpath(selector)
assert len(elements), 'The text is not on the page'
Hope, this will help you.
You could try to locate it via xPath selector, but I would not recommend test like that.
Surely You know where to look for some test? At least a part of web page.
Here is code sample how to achieve this:
List<WebElement> list = driver.findElements(By.xpath("//*[contains(text(),'" + text + "')]"));
Assert.assertTrue("Text not found!", list.size() > 0);
or
String bodyText = driver.findElement(By.tagName("body")).getText();
Assert.assertTrue(bodyText.contains(text));
or in method:
public boolean isTextOnPagePresent(String text) {
WebElement body = driver.findElement(By.tagName("body"));
String bodyText = body.getText();
return bodyText.contains(text);
}
Hope this helps, but as I mentioned try defining a smaller scope for testing.

Selenium made a sendKeys for two fields instead of one for some reason

I made a pretty simple selenium test, where I want to open web page, clear field value, start entering text for this field, select first value from the hint drop down.
Web site is aviasales.com (I just found some site with a lot of controls, this is not an advertisement)
I did
DriverFactory.getDriver().findElement(By.id("flights-origin-prepop-whitelabel_en")).clear();
and it was working perfectly, I also checked via console that this is the only one object on a page like:
document.getElementById('flights-origin-prepop-whitelabel_en')
So, in next line I'm sending value:
DriverFactory.getDriver().findElement(By.id("flights-origin-prepop-whitelabel_en")).sendKeys("LAX");
but it send LAX value for both "flights-origin-prepop-whitelabel_en" and "flights-destination-prepop-whitelabel_en" for some reason, then i tried
DriverFactory.getDriver().findElement(By.id("//input[#id='flights-destination-prepop-whitelabel_en'][#placeholder='Destination']")).sendKeys(destinationAirport);
but I got the same result:
What could be a reason and how to fix this?
Thank you!
Yep... there's some weird behavior going on there. The site is copying whatever is entered into the first field into the second for reason I don't understand. I gave up trying to understand it and found a way around it.
Whenever I write code that I know I'm going to reuse, I put them into functions. Here's the script code
driver.navigate().to(url);
setOrigin("LAX");
setDestination("DFW");
...and since you are likely to use these repeatedly, the support functions.
public static void setOrigin(String origin)
{
WebElement e = driver.findElement(By.id("flights-origin-prepop-whitelabel_en"));
e.click();
e.clear();
e.sendKeys(origin);
e.sendKeys(Keys.TAB);
}
public static void setDestination(String dest)
{
WebElement e = driver.findElement(By.id("flights-destination-prepop-whitelabel_en"));
e.click();
e.clear();
e.sendKeys(dest);
e.sendKeys(Keys.TAB);
}
You can see the functions but basically I click in the field, clear the text (because usually there's something already in there), send the text, and then press to move out of the field and choose the default (first choice).
The reason of your issue is the ORIGIN and DESTINATION inputbox binded keyboard event which used to supply an autocomplete list according to your typed characters.
The binded keyborad event breaks the normal sendKeys() functionality. I met similar case in my projects and questions on StackOverFlow.
I tried input 'GSO' into DESTINATION by sendKeys('GSO'), but I get 'GGSSOO' on page after the sendKeys() complete.
To resolve your problem, we can't use sendKeys(), we have to use executeScript() to set the value by javascript in backgroud. But executeScript() won't fire keyborad event so you won't get the autocomplete list. So we need find out a way to fire keyborady event after set value by javascript.
Below code snippet worked on chrome when i tested on aviasales.com:
private void inputAirport(WebElement targetEle, String city) {
String script = "arguments[0].value = arguments[1]";
// set value by javascript in background
((JavascriptExecutor) driver).executeScript(script, targetEle, city + "6");
// wait 1s
Thread.sleep(1000);
// press backspace key to delete the last character to fire keyborad event
targetEle.sendKeys(Keys.BACK_SPACE);
// wait 2s to wait autocomplete list pop-up
Thread.sleep(2000);
// choose the first item of autocomplete list
driver.findElement(By.cssSelector("ul.mewtwo-autocomplete-list > li:nth-child(1)")).click();
}
public void inputOrigin(String city) {
WebElement target = driver.findElement(By.id("flights-origin-prepop-whitelabel_en"));
return inputAirport(target, city);
}
public void inputDestination(String city) {
WebElement target = driver.findElement(By.id("flights-origin-prepopflights-destination-prepop-whitelabel_en"));
return inputAirport(target, city);
}

Sencha Disabled Button and Selenium Webdriver

I have a button on a website built with sencha/extjs. Currently the button id is savebutton-1550-btnEl, but this changes everytime the page is loaded. I know that the button is disabled, but for testing purposes, I'd like to set this button as enabled, and then click it.
How would I go about finding this element each time, and then disabling it and clicking it with Java Selenium?
I'm guessing I'll have to execute some javascript, but I'm having a hard time finding the target for the javascript.
To locate the element you will have to use part of the DOM surrounding the element as a unique locator. It is impossible to give a more specific answer without seeing the DOM you are working on, but you may try something like:
WebElement saveButton = driver.findElement(By.xpath("//button[text()='Save']");
For changing the element to enabled, take a look at this answer: Selenium Webdriver - click on hidden elements
Also a longer term solution might be to work with development to see if they can build in a unique locator especially since this probably wont be the only object that you have problems with. At my company we use the "class" field to uniquely identify objects in extjs.
I override the the class "AbstractComponent":
Ext.define('Foo.overrides.AbstractComponent', {
override: 'Ext.AbstractComponent',
onBoxReady: function () {
var me = this;
var el = me.getEl();
if (el && el.dom && me.itemId) {
el.dom.setAttribute('data-test', me.itemId);
}
me.callOverridden(arguments);
}
});
If you set in configuation of the button the "itemId", you
can be accessed the button via seleniumas follows:
IWebElement element = webDriver.FindElement(By.XPath($"//*[#data-test='{itemId}']"));

Alternatives for switch statement

I am developing an Wicket application. But my question is not really Wicket related. In that app I have a horizontal menu. This menu is created by few links. On clicking the link you will be navigated to some page. Now based on the page you are currently viewing the css class attribute of the link of the menu will be changed to "selected". This is the description of the problem.
Now I am solving this problem by using a integer value. The value is saved in the session and it is updated when any one link has been clicked. Based on that saved value, which link will be "selected", will be determined at runtime.
I am implementing this in following way:
//On link click I set a number in session
public void onClick() {
session.setValue(1);// or 2 or 3
}
When the menu is created I switch between the value and modify the css class, as follows:
switch(session.getValue){
case 1: add css to home;
case 2: add css to profile;
// and so on.
}
I was wondering that is this the only right way to do it? Or there some other better techniques or design patterns exist which can help me to achieve this in better way?
Store the menu items in an array (or an ArrayList):
items[0] = home
items[1] = profile
And use the index of the array as menu identifier. When you receive the selected menu itentifier, retrieve the corresponding item with
items[selectedItem]
You could also use a Map if the identifiers are not numbers, or don't go from 0 to N.
For a start, use an enum or static constants instead of magic numbers (1, 2, 3).
The Visitor Pattern is commonly used to avoid this sort of switching. You might not want to implement the full pattern in your case, but it's worth knowning. JB Nizet's answer may be more practical in your situation.
See https://en.wikipedia.org/wiki/Visitor_pattern
These SO questions might give you some ideas, too
Java visitor pattern instead of instanceof switch
Java Enums - Switch statements vs Visitor Pattern on Enums - Performance benefits?
I have implemented it using EnumMap and an Enum type as its key. I have defined an Enum:
public enum NavigationStatus {
HOME,
PROFILE;
}
In session I set the value of the current navigation as:
private NavigationStatus activeUserNavigationStatus;
public NavigationStatus getActiveUserNavigationStatus() {
return activeUserNavigationStatus;
}
public void setActiveUserNavigationStatus(NavigationStatus activeUserNavigationStatus) {
this.activeUserNavigationStatus = activeUserNavigationStatus;
}
Primarily I set it to: setActiveUserNavigationStatus(NavigationStatus.HOME);
Now where the menu is building I created an EnumMap:
EnumMap<NavigationStatus, Component[]> menuMap = new EnumMap<NavigationStatus, Component[]>(NavigationStatus.class);
And added elements to it, as:
menuMap.put(NavigationStatus.HOME, new Component[] { homeContainer, home });
And also on click methods of the links I set the status value:
public void onClick() {
session.setActiveUserNavigationStatus(NavigationStatus.PROFILE);
}
Last of all I checked the current value from the session and set the css class accordingly:
Component[] menuComponents = menuMap.get(getSession().getActiveUserNavigationStatus());
menuComponents[0].add(new AttributeAppender("class", new Model<Serializable>(" active")));
menuComponents[1].add(new AttributeAppender("class", new Model<Serializable>(" active")));
This is without switch statement and combines the idea of JB Nizet's ArrayList index and Oli Charlesworth's Enum.
Thank you.

Categories