Learning XPath by examples in Selenium, tips in automated test cases
Part 1. XPath examples
Relative Path : //ul[@class='home-list']//a[text()='Querying']
- Locate element by id
Locate any div with id 'account-form'//div[@id='account-form']
2. Locate element by index
If the page have 3 input field and we want to locate the 1st one//input[1]2nd one, vice versa…//input[2]last one//input[last()]
3. Parentheses ( )
Some of the page maybe contains more than one forms, it may contains several forms and each form have different input elements. As a result, simply placing index after will return unexpected result//input[1]In fact, the above code will locate all input elements which is the first element within their parent element<div>
<form>
<input id="a" type="text"></input>
<input id="b" type="text"></input>
</form>
</div>
<div>
<form>
<input id="c" type="text"></input>
<input id="d" type="text"></input>
</form>
</div>//input[1] - will return input element id:a and input element id:cResolution (//input)[1] - will return the first input element within the page
4. Locate element by text (exact match, case sensitive), which text means the content between tag. For example”<div>abc</div>”, “abc” is the text we’re looking for
Locate any button with text equal to 'Register'//button[text()='Register']
5. Locate element by text (partial match, case sensitive)
Locate any button with contains text 'Account'//button[contains(text(),'Account')]Locate any button with CSS class which starts with 'form-style'//button[starts-with(@class, 'form-style')]Use case
i. Some class name are auto generated, with random numbers suffix which is different from time to time, with contains syntax, we can ignore the numbers
Two buttons returned, we can append the above xpath with index to narrow the result
First button with 'Account' text//button[contains(text(),'Account')][1]Second button with 'Account' text//button[contains(text(),'Account')][2]
6. Locate element by type
Text input//input[@type='text']Password input//input[@type='password']Checkbox//input[@type='checkbox']Radio//input[@type='radio']
Of coz, we can append the above xpath with index to narrow the result
1st Text input//input[@type='text'][1]2nd Password input//input[@type='password'][2]3rd Checkbox//input[@type='checkbox'][3]4th Radio button//input[@type='radio'][4]
7. Locate element by CSS class
button with class 'registerbtn'//button[@class='registerbtn']
8. Locate element by multiple condition
div contains style class with text 'abc' and 'def', but do not have any style class with text 'ghi'This is 'contains' not equal, partial match onlyUse casei. Element contains multi css class//div[contains(@class, 'abc') and contains (@class, 'def') and not(contains (@class, 'ghi'))]
9. Single slash ‘/’ vs Double slash ‘//’
Child only
Locate input elements which is immediate under div//div[@class='streams-tabs-section']/input
All Descendant
Locate input elements which is under div//div[@class='streams-tabs-section']//div
10. Wildcard
Any element contains text 'keyword'//*[contains(text(),'keyword')]
11. Parent / child / ancestor / descendant
Any div which contains p //div[p]Any p element which contained by a div which contains p//div[h4]//p
Consider the following code snippet from HTML form
<div class="input-container">
<label><b>Name</b></label>
<input type="text" required>
</div>
<div class="input-container">
<label><b>Email</b></label>
<input type="text" required>
</div>Besides using index, we can make use of the label text to locate the input element with the help of 'ancestor' syntaxi.
//label/b[text()='Name']/ancestor::div[@class='input-container']/input//label/b[text()='Name'] - locate any b element within label element with text 'Name'
//label/b[text()='Name']/ancestor::div[@class='input-container'] locate any parent or ancestor div which class equals 'input-container'
And then locate the input element inside the divii.
//label[b[text()='Name']]/following-sibling::input//label[b[text()='Name']] - Locate any label with b element and in which text is equal 'Name'
//label[b[text()='Name']]/following-sibling::input - Then locate the next input element of the label, so we got the input element
12. Absolute XPath vs Relative XPath
Absolute XPath — Starting from root to your target element
Relative XPath — Starting from any point to your target element
Always use relative XPath as absolute XPath difficult to maintain, anything changed between the root element and the target element may require to update the XPath too.
13. Avoid overuse of index
Same reason as above, hard to maintain and hard to understand
Part 2 Tips in writing test case
- Reusable
Modularize some the common test case procedure like login, logout.
Modularize some common action, like click, onMouseOver, drop-down selection, element count, sum of columns and those function to read the value from different inputs.
Reuse element location, like those elements in the same form, their locators can share the same starting string. - Maintainable
It is expected that the scope and user requirement are keep changing especially in agile environment which facilitate short delivery times. When we composing our test case, we should consider the cost of maintain it afterwards.
For example, let say we have a form which contains more than 100 input elements.
There are several ways to locate the elements
i. locate by index
ii. locate by label with ancestor or sibling syntax
If we locate element by index, that means adding or removing any field will require the update of locator of other elements too. So, in this case, locate those elements by label would be much better - Implicit Wait vs Explicit Wait
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
Implicit Wait is a system wide setting, it is a default time to wait until the Selenium give up and throw the elementNotFoundExceptionWebDriverWait wait = new
WebDriverWait(driver,30);
Explicit Wait is targeting for a specific element, for example, waiting an element to appear or waiting an element to be editable.
wait.until(ExpectedConditions.visibilityOfElementLocated(
By.xpath("//div[contains(text(),'Register')]")));