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).
Related
I have an if statement below which is getting me an issue. If certain selections are made in a different dropdown, the page displays a second dropdown and a check box. The below code works as expected when a selection is made that causes those two elements to display but it doesn't if a selection is made that doesn't make them display. I get the no such element: Unable to locate element error. At first I thought it was returning true either way but the issue is it's crashing because. I even added a check at trying to assign the value to a booolean but still get the same error.
boolean dropdown = driver.findElement(By.id("DROPDOWN")).isDisplayed(); gets the same error.
if(driver.findElement(By.id("DROPDOWN")).isDisplayed()){
driver.findElement(By.id("DROPDOWN")).click();
driver.findElement(By.xpath("Choice in Drop DOWN)).click();
driver.findElement(By.id("CheckBox")).click();
}
findElement method will throw this hard exception - No such element if the element is not found. Just include Exception Handling for No Such Element and your logic should work just fine.
try{
if(driver.findElement(By.id("DROPDOWN")).isDisplayed()){
driver.findElement(By.id("DROPDOWN")).click();
driver.findElement(By.xpath("Choice in Drop DOWN)).click();
driver.findElement(By.id("CheckBox")).click();
}
catch (NoSuchElementException e)
{
// I believe you dont have to do anything here. May be a console log will do.
}
The following answers explain how to handle checking an element exists and handle the exception by wrapping in a custom method.
How to check if Element exists in c# Selenium drivers
I would also recommend re-writing your code as the following to avoid duplication and avoid xpath selectors. Using findElement twice for in the same context is not necessary just create a variable.
var dropdown = driver.findElement(By.id("DROPDOWN"));
if (dropdown.Displayed())
{
var selectElement = new SelectElement(dropdown);
selectElement.SelectByValue("valuehere");
}
If you are using the text rather than the value in the select box you can use SelectByText("texthere"); instead of SelectByValue.
isDisplayed() will work if the element is present in the DOM, followed by the style attribute :- display should not be false or none.
If prior action is a selection which led both the element to be displayed, it means the element is in the DOM but that wont be visible. So checking the visibility condition would return u false.
Try waiting for the element to get visible and perform the check operation on it which would reduce the sync latency.
WebDriverWait wait = new WebDriverWait(WebDriverRunner.getWebDriver(),5);
wait.until(ExpectedConditions.visibilityOfElementLocated("By Locator"));
if (dropdown.isDisplayed())
`````````// If the dropdown is tagged with <Select> tag
``````````` Select dropDown = new Select(dropdown);
```````````dropDown .selectByValue("value);
```````` // Else fetch the dropdown list item and store it in a list and iterate through and perform the desired action
```````````List<WebElement> dropDownList = new ArrayList<Webelements>;
```````````dropDownList.forEach(element -> {
```````````if(element.getText().equals("value")){
``````` ````element.click();
``````````` }
``````````` });
```````````driver.findElement(By.id("CheckBox")).click();
}
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.
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);
}
I am in some kind of situation here.
I have a function editNewUser().
public void editNewUser() throws Exception
{
driver.findElement(adminModuleDD).click();
wait.until(ExpectedConditions.visibilityOfElementLocated(searchRes));
List<WebElement> elements = wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(uNames));
for(WebElement el : elements)
{
if(el.getText().equals(UserName))
{
el.click();
wait.until(ExpectedConditions.visibilityOfElementLocated(editUserHeading));
driver.findElement(editUser).click();
WebElement status_Dropdown = driver.findElement(By.xpath("//select[#id='systemUser_status']"));
Select status = new Select(status_Dropdown);
status.selectByVisibleText("Enabled");
}
}
}
The UserName is a public string variable that gets value in another function, during creation of user.
In this script, I am navigating to a page that contains list of users, I am storing all user names in List 'elements' and then iterating over each element in the list whose text matches with user name.
Now, in my test_run script, I have some other methods calling after editNewUser().
The thing happens is, this method executes the following commands in if block.
if(el.getText().equals(UserName))
{
el.click();
wait.until(ExpectedConditions.visibilityOfElementLocated(editUserHeading));
driver.findElement(editUser).click();
WebElement status_Dropdown = driver.findElement(By.xpath("//select[#id='systemUser_status']"));
Select status = new Select(status_Dropdown);
status.selectByVisibleText("Enabled");
}
But as soon as next method is called, it stops the execution and throws org.openqa.selenium.StaleElementReferenceException: Element not found in the cache - perhaps the page has changed since it was looked up
Stack Trace refers to the line if(el.getText().equals(UserName)).
Can you tell me why am I receiving this exception, event though the commands inside if block are executed.
The error message is telling you the problem. The page has changed once you edit the first "el" in your for loop, the page has been updated, so Selenium has lost track of the remaining elements in the "elements" list.
You'll need to find another way to track the elements that you are looping through. a technique that I've used in the past is create a custom object to represent the items, in your case perhaps "User". that object is then smart enough that it can re-find itself on the page.
I'm writing an automated test case for a web page. Here's my scenario.
I have to click and type on various web elements in an html form. But, sometimes while typing on a text field, an ajax loading image appears , fogging all elements i want to interact with. So, I'm using web-driver wait before clicking on the actual elements like below,
WebdriverWait innerwait=new WebDriverWait(driver,30);
innerwait.until(ExpectedConditions.elementToBeClickable(By.xpath(fieldID)));
driver.findelement(By.xpath(fieldID)).click();
But the wait function returns the element even if it is fogged by another image and is not clickable. But the click() throws an exception as
Element is not clickable at point (586.5, 278).
Other element would receive the click: <div>Loading image</div>
Do I have to check every time if the loading image appeared before interacting with any elements?.(I can't predict when the loading image will appear and fog all elements.)
Is there any efficient way to handle this?
Currently I'm using the following function to wait till the loading image disappears,
public void wait_for_ajax_loading() throws Exception
{
try{
Thread.sleep(2000);
if(selenium.isElementPresent("id=loadingPanel"))
while(selenium.isElementPresent("id=loadingPanel")&&selenium.isVisible("id=loadingPanel"))//wait till the loading screen disappears
{
Thread.sleep(2000);
System.out.println("Loading....");
}}
catch(Exception e){
Logger.logPrint("Exception in wait_for_ajax_loading() "+e);
Logger.failedReport(report, e);
driver.quit();
System.exit(0);
}
}
But I don't know exactly when to call the above function, calling it at a wrong time will fail. Is there any efficient way to check if an element is actually clickable? or the loading image is present?
Thanks..
Given the circumstances that you describe, you are forced to verify one of two conditions:
Is the element that you want to click clickable?
Is the reason that blocks the clicks still present?
Normally, if the WebDriver is able to find the element and it is visible, then it is clickable too. Knowing the posible reasons that might block it, I would rather choose to verify those reasons. Besides, it would be more expressive in the test code: you clearly see what you are waiting for, what you are checking before clicking the element, instead of checking the "clickability" with no visible reason for it. I think it gives one (who reads the test) a better understanding of what is (or could be) actually going on.
Try using this method to check that the loading image is not present:
// suppose this is your WebDriver instance
WebDriver yourDriver = new RemoteWebDriver(your_hub_url, your_desired_capabilities);
......
// elementId would be 'loadingPanel'
boolean isElementNotDisplayed(final String elementId, final int timeoutInSeconds) {
try {
ExpectedCondition condition = new ExpectedCondition<Boolean>() {
#Override
public Boolean apply(final WebDriver webDriver) {
WebElement element = webDriver.findElement(By.id(elementId));
return !element.isDisplayed();
}
};
Wait w = new WebDriverWait(yourDriver, timeoutInSeconds);
w.until(condition);
} catch (Exception ex) {
// if it gets here it is because the element is still displayed after timeoutInSeconds
// insert code most suitable for you
}
return true;
}
Perhaps you will have to adjust it a bit to your code (e.g. finding the element once when the page loads and only checking if it is displayed).
If you are not sure when exactly the loading image comes up (though I suppose you do), you should call this method before every click on elements that would become "unclickable" due to the loading image. If the loading image is present, the method will return true as soon as it disappears; if it doesn't disappear within 'timeoutInSeconds' amount of time, the method will do what you choose (e.g. throw an exception with specific message).
You could wrap it together like:
void clickSkippingLoadingPanel(final WebElement elementToClick) {
if (isElementNotDisplayed('loadingPanel', 10)) {
elementToClick.click();
}
}
Hope it helps.