I'm trying to select a particular item from a dropdown list, however the location of the list on the page can change with each run.
At the moment my code tries to find the dropdown, click on it, and then select a specific element, based on the position of the dropdown in the page - unfortunately the position of the containing divs in the body
can be different on each load, so hard-coding the array counter isn't working on subsequent runs - i.e, in /html/body/div[16]
the 16
can change:
IWebElement questType = _driver.FindElement(By.XPath("/html/body/div[4]/div/div/div/div[4]/table/tbody/tr[2]/td[2]/div/div[4]/table/tbody/tr[2]/td[2]/div/div/div[2]/div/div[4]/table/tbody/tr/td[5]/span[1]/span"));
questType.Click();
IWebElement slctquestType = _driver.FindElement(By.XPath("/html/body/div[16]/div/div[3]/ul/li[1][contains(text(),'" Questn_Type "')]"));
slctquestType.Click();
The select element looks like this:
<ul unselectable="on" class="k-list k-reset" tabindex="-1" aria-hidden="true" id="TypeId_listbox" aria-live="off" data-role="staticlist" role="listbox">
<li tabindex="-1" role="option" unselectable="on" class="k-item" aria-selected="false" data-offset-index="0">Data</li>
<li tabindex="-1" role="option" unselectable="on" class="k-item" aria-selected="false" data-offset-index="1">Other</li>
...
</ul>
CodePudding user response:
XPath is very mighty. Using such a direct path is the simplest approach. You can also do other things like these to find your desired element:
Search by some text within the node:
By.XPath("//*[contains(text(), 'message part')]")
Find a button with a specific title:
By.XPath("//button[@title = 'Continue']")
Find a div element using a specific class name which has a child div element with some text within its title:
By.XPath("//div[@class='className']//div[contains(@title, 'Title')]")
As you can see, you can work with a lot of information to find a specific element. What best matches your specific case is up to you and you should probably check for further documenation about XPath syntax
to dive deeper.
CodePudding user response:
There is some hidden complexity here, since this is not a traditional <select>
element. Presumably, JavaScript is opening and closing the dropdown. These elements could be fading in or out (or sliding up or down) when toggling the dropdown. When those elements are in the process of fading or sliding up or down, Selenium will not be able to click on them. This isn't just a locator issue, it is also a timing issue.
You will likely need to click the dropdown, and then wait. Click the option, and then wait again. Since the logic is non-trivial, consider using the Page Object Model Design Pattern to encapsulate this behavior.
To more directly address your code, first find the dropdown by HTML tag Id. HTML Tag Ids are unique on a web page. If they are not unique, this should be fixed, because the HTML spec declares that HTML tag Id values should be unique on a web page.
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(30));
var questType = _driver.FindElement(By.Id("TypeId_listbox"));
questType.Click();
var optionLocator = By.XPath("//ul[@role = 'listbox']/li[contains(text(),'" Questn_Type "')]");
var slctquestType = wait.Until(ExpectedConditions.ElementIsClickable(optionLocator);
slctquestType.Click();
wait.Until(ExpectedConditions.InvisibilityOfElementLocated(optionLocator);
The important thing to remember is that XPath selectors do not need to be exact. Two consecutive forward-slash characters //
returns elements arbitrarily deep in the document tree. This allows you to skip several levels, and target only the elements you are interested in. For instance, finding the list box option can be simplified to:
"//ul[@role = 'listbox']/li[contains(text(),'" Questn_Type "')]"
The initial //
characters will search the document tree at any level for a <ul>
tag. This makes the xpath expression more resilient to changes in the HTML structure, which will result in more stable tests.