Apply element selectors
Selectors (or Locators) are used to find and match the elements of a web page or desktop app that a robot needs to interact with. Using the right selector ensures the bots are faster, more reliable or has lower maintenance over releases. If you’re fortunate enough to be working with unique IDs and Classes, then you’re usually all set. It can be a real challenge to verify that you have the right selectors to accomplish what you want.
This tutorial explains different selectors, how, when and ideal strategies to use these selectors.
Web selectors
ID selector
IDs are the most preferred way to locate elements on a page, as each ID is supposed to be unique which makes IDs a very fast and reliable way to locate elements.
With this strategy, the first element with the ID attribute value matching the selector will be returned. If no element has a matching ID attribute value, NoSuchElementException is raised.
For example, if an element is given like this:
<form name="loginForm">
Login Username:
<input id="username" type="text" name="login" />
Password:
<input id="password" type="password" name="pass" />
<input type="submit" name="signin" value="SignIn" />
</form>
You can easily choose the element with the help of ID selector from the above example:
- id = username
- id = password
WebElement elementUser = $(byId("username"))
elementUser.val("my_login")
$(byId("password")).sendKeys("secure_pass").pressEnter()
Even though this is a great selector, obviously it is not realistic for all objects on a page to have IDs. In some cases, developers make it having non-unique IDs on a page or auto-generate the iIDs, in both cases it should be avoided.
Name selector
This is also an efficient way to locate an element with name attribute, after IDs give it your second preference but likewise IDs, name attributes don’t have to be unique.
With this strategy, the first element with the name attribute value matching the selector will be returned. If no element has a matching name attribute, NoSuchElementException is raised.
WebElement elementUser = $(byName("login"))
elementUser.setValue("my_login")
$(byName("pass")).sendKeys("secure_pass").pressEnter()
Text selector
You can find elements by their inner text using the following selectors.
byTextreturns all elements with given text (exact match).withTextreturns all elements containing given text (substring).byLinkTextreturns all anchor<a>elements with given text (exact match).byPartialLinkTextreturns all anchor<a>elements containing given text (substring).
Sample HTML element:
<a href="link.html">Name of the Link</a>
<button>Order now!</button>
To click the hyperlink or button using the tag’s text, you can use the following text selectors:
WebElement spanElement1 = $(byText("Order now!"))
WebElement spanElement2 = $(withText("Order"))
WebElement linkElement1 = $(byLinkText("Name of the Link"))
WebElement linkElement2 = $(byPartialLinkText("Name of"))
Tag and attribute selectors
Let's automate the following form using tag and attribute selectors:
<p><abbr title="World Health Organization">WHO</abbr> was founded in 1948.</p>
<form name="loginForm">
Login Username:
<input id="username" type="text" name="login" class="login" />
Password:
<input id="password" type="password" name="pass" />
<input type="submit" name="signin" value="SignIn" />
</form>
You can use the following selectors:
byTitlebyValuebyTagNamebyClassNamebyAttributeby('attribute-name', 'attribute-value')
$(byTitle("World Health Organization")).getText()
$(byValue("SignIn")).click()
$(byTagName("p")).getText()
$(byClassName("login")).sendKeys("username")
$(byAttribute("type","text")).sendKeys("username")
$(by("value","SignIn")).click()
CSS selector
Let's automate the following form using CSS selectors:
<form name="loginForm">
Login Username:
<input id="username" type="text" name="login" />
Password:
<input id="password" type="password" name="pass" />
<input type="submit" name="signin" value="SignIn" />
</form>
You can use both $() and $(byCssSelector) selectors, which are equivalent.
$("#password").sendKeys("secure_pass").pressEnter()
$(byCssSelector("form input:first-child")).sendKeys("my_login")
XPath selector
While DOM is the recognized standard for navigation through an HTML element tree, XPath is the standard navigation tool for XML, and an HTML document is also an XML document (xHTML).
For example, to select the username from the above example, do as follows:
$(byXpath("//*[@id='username']"))
$(byXpath("//input[@id='username']"))
$(byXpath("//form[@name='loginForm']/input[1]"))
$(byXpath("//*[@name='loginForm']/input[1]"))
Desktop selectors
The following selectors are available for the desktop driver:
$orbyCssSelector. For more details, see CSS selectors.$or Object selector. For more details, see Object selectors.byXpath. For more details, see Automate SwingSet app.byImage. For more details, see Surface-based Robotics driver.
FindElement (\$) and FindElements (\$\$) commands
The difference between the findElement() and findElements() method is that the first returns an uiElement object. Otherwise, it throws an exception and the latter returns a list of uiElements. It can return an empty list if no elements match the query.
findElement() – \$()
- On Zero match: throws
NoSuchElementException - On One match: returns
uiElement - On One+ match: returns the first
uiElementmatching the specified selector
findElements() – \$\$()
- On Zero match: returns an empty list
- On One match: returns a list of one uiElement only
- On One+ match: returns a list with all matching instances
Expand to see the example with findElements
import com.workfusion.odf2.compiler.BotTask;
import com.workfusion.odf2.core.task.AdHocTask;
import com.workfusion.odf2.core.task.TaskInput;
import com.workfusion.odf2.core.task.output.TaskRunnerOutput;
import com.workfusion.odf2.core.webharvest.rpa.RpaDriver;
import com.workfusion.odf2.core.webharvest.rpa.RpaFactory;
import com.workfusion.odf2.core.webharvest.rpa.RpaRunner;
import com.workfusion.rpa.helpers.RPA;
import com.workfusion.rpa.helpers.UiElement;
import com.workfusion.rpa.helpers.UiElementCollection;
import org.openqa.selenium.JavascriptExecutor;
import org.slf4j.Logger;
import javax.inject.Inject;
@BotTask(requireRpa = true)
public class ApplyElementSelector implements AdHocTask {
private final RpaRunner rpaRunner;
private final Logger logger;
@Inject
public ApplyElementSelector(RpaFactory rpaFactory, Logger logger){
this.rpaRunner = rpaFactory
.builder(RpaDriver.UNIVERSAL)
.closeOnCompletion(true)
.build();
this.logger = logger;
}
@Override
public TaskRunnerOutput run(TaskInput taskInput) {
rpaRunner.execute(driver->{
RPA.timeouts(15000);
RPA.openChrome("https://rpa-tutorial.s3.amazonaws.com/trainings/iframe/form.html");
UiElementCollection inputList = RPA.$$("input");
logger.debug("Number of form inputs on a page: " + inputList.size());
logger.debug("ID of the 12th input: " + inputList.get(11).getAttribute("id"));
driver.switchDriver("chrome");
JavascriptExecutor javaexec = (JavascriptExecutor)driver;
for(UiElement input : inputList){
driver.executeScript("alert(\"Input Tag's Attribute Name Value - \" + arguments[0])",input.getAttribute("name"));
RPA.sleep(1000);
RPA.switchTo().alert().accept();
}
});
return taskInput.asResult()
.withColumn("example_bot_task_output", "completed_successfully");
}
}
Win32 desktop applications
To accelerate finding UI elements of Win32 applications, you may use a specific search capability allowing to perform a search among components with a handle attribute only.
Mind that the solution works best when used for applications built on the Win32 technology. When attempting to find elements without a handle attribute (for example, in applications like Microsoft Outlook 2016), you may face performance degradation instead.
There are two ways to enable advanced searching for UI elements.
Recommended: Add a specific capability to the robot plugin.
...
<robotics-flow>
<robot driver="desktop" name="driver1" close-on-completion="true">
<capability name="desktopOptions">
<script return="optionsMap"><![CDATA[
optionsMap = ['FindByHandle':true]
]]></script>
</capability>
<script><![CDATA[
...
]]></script>
</robot>
</robotics-flow>
...Optional: If you have a number of scripts and it's time-consuming to add a capability to each of them, you may launch a Worker with the option allowing to constantly execute a search by handle for all of these scripts. Mind that in this case, only the scripts automating Win32 applications should go to the specified Worker. To apply the option, launch the Worker adding the
-DdesktopOptions.FindByHandle=trueparameter.
If two of the above-mentioned ways are used, adding the capability is preferable, as a search like this can be applied not only for the entire Worker but for a specific script (a step in the business process).