Let’s look at that HTML element:
<div class="multiselect-example__container">
<div>
<div
tabindex="-1"
role="combobox"
aria-owns="listbox-null"
class="multiselect"
>
<div class="multiselect__select"></div>
<div class="multiselect__tags">
<div class="multiselect__tags-wrap" style="display: none;"></div>
<!---->
<div class="multiselect__spinner" style="display: none;"></div>
<input
name=""
type="text"
autocomplete="off"
spellcheck="false"
placeholder="Pick badges"
tabindex="0"
aria-controls="listbox-null"
class="multiselect__input"
style="width: 0px; position: absolute; padding: 0px;"
aria-activedescendant="null-2"
/>
<!---->
<span class="multiselect__placeholder"> Pick badges </span>
</div>
<div
tabindex="-1"
class="multiselect__content-wrapper"
style="max-height: 300px; display: none;"
>
<ul
role="listbox"
id="listbox-null"
class="multiselect__content"
style="display: inline-block;"
>
<!---->
<li id="null-0" role="option" class="multiselect__element">
<span
data-select=""
data-selected=""
data-deselect=""
class="multiselect__option"
><span class="badge__name">License</span
><img
src="https://camo.githubusercontent.com/d0e25b09a82bc4bfde9f1e048a092752eebbb4f3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666c6174"
alt="License"
class="badge__img"
/></span>
<!---->
</li>
<li id="null-1" role="option" class="multiselect__element">
<span
data-select=""
data-selected=""
data-deselect=""
class="multiselect__option"
><span class="badge__name">GitHub Stars</span
><img
src="https://img.shields.io/github/stars/shentao/vue-multiselect.svg?label=Stars"
alt="GitHub Stars"
class="badge__img"
/></span>
<!---->
</li>
<li id="null-2" role="option" class="multiselect__element">
<span
data-select=""
data-selected=""
data-deselect=""
class="multiselect__option multiselect__option--highlight"
><span class="badge__name">Npm Monthly Downloads</span
><img
src="https://camo.githubusercontent.com/64f9a2333bb303d34b1587e1436b24dee6a8e134/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f7675652d6d756c746973656c6563742e737667"
alt="Npm Monthly Downloads"
class="badge__img"
/></span>
<!---->
</li>
<li id="null-3" role="option" class="multiselect__element">
<span
data-select=""
data-selected=""
data-deselect=""
class="multiselect__option"
><span class="badge__name">Full Test Coverage</span
><img
src="https://camo.githubusercontent.com/47ff0923e959e736113988e900268dfc7a601d3b/68747470733a2f2f636972636c6563692e636f6d2f67682f6d6f6e74657261696c2f7675652d6d756c746973656c6563742f747265652f6d61737465722e7376673f7374796c653d736869656c6426636972636c652d746f6b656e3d35633933316666323866643132353837363130663833353437326265636464353134643039636566"
alt="Full Test Coverage"
class="badge__img"
/></span>
<!---->
</li>
<li id="null-4" role="option" class="multiselect__element">
<span
data-select=""
data-selected=""
data-deselect=""
class="multiselect__option"
><span class="badge__name">NO Dependencies</span
><img
src="https://img.shields.io/badge/dependencies-none-brightgreen.svg?style=flat"
alt="NO Dependencies"
class="badge__img"
/></span>
<!---->
</li>
<li style="display: none;">
<span class="multiselect__option"
><span
>Badge not found. Suggest a badge
<a
href="https://github.com/shentao/vue-multiselect/issues"
target="_blank"
class="typo__link"
>here</a
>.</span
></span
>
</li>
<li style="display: none;">
<span class="multiselect__option">List is empty.</span>
</li>
</ul>
</div>
</div>
</div>
<div class="grid__row start__list">
<div class="grid__column grid__unit--md-6 list">
<ul class="list__ul">
<li class="typo__li">Single / multiple select</li>
<li class="typo__li">Dropdowns</li>
<li class="typo__li">
<a href="#sub-select-with-search" class="typo__link">Searchable</a>
</li>
<li class="typo__li">
<a href="#sub-tagging" class="typo__link">Tagging</a>
</li>
<li class="typo__li">Server-side Rendering support</li>
</ul>
</div>
<div class="grid__column grid__unit--md-6 list">
<ul class="list__ul">
<li class="typo__li">
<a href="#sub-vuex-support" class="typo__link"
>Vuex support by default</a
>
</li>
<li class="typo__li">
<a href="#sub-asynchronous-select" class="typo__link">Ajax support</a>
</li>
<li class="typo__li">
<a href="#sub-custom-configuration" class="typo__link"
>Fully configurable</a
>
</li>
<li class="typo__li">+99% test coverage</li>
<li class="typo__li">No dependencies</li>
</ul>
</div>
</div>
</div>
Given that the multi-select for your project is going to look like this, the question is: how should we handle it?
Breakdown of the action steps, in plain English
Given the example, on that page, we know the following should happen:
- for each option to click:
- click the dropdown button
- wait for dropdown list to spawn
- click on the right dropdown option
- wait for dropdown option to disappear
- wait for the tag to appear
Knowing that, we can now start writing code for it…
Writing code for it?! I want the Katalon Recorder!
Bear with me. You’re going to hate this initially, but if you trust the process, and get good at writing code, you’re going to end up loving it, and having a solid system in place…
NOTE: this code is going to be in Katalon Studio, not Selenium… We’re going to be using the WebUI
and TestObject
s, not the driver
methods and WebElement
s.
Page Objects
I will not give you full breakdown of that on here, instead I’ll refer you to this question.
Let’s implement this concept:
public class YourPage {
public final String MultiSelectPartDesc = "Your Page/Multi-Select Dropdown";
public void doMultiSelect(List<String> options) {
for (String option : options) {
final TestObject firstDropdownOption = findTestObject("${this.MultiSelectPartDesc}/First dropdown option");
WebUI.click(findTestObject("${this.MultiSelectPartDesc}/Dropdown button"));
WebUI.waitForElementVisible(firstDropdownOption ,
2);
WebUI.click(this.getDropdownOption(option));
WebUI.waitForElementNotVisible(firstDropdownOption ,
2);
WebUI.waitForElementPresent(this.getOptionTag(option),
3);
}
}
public TestObject getDropdownOption(String option) {
throw new cucumber.api.PendingException();
}
public TestObject getOptionTag(String option) {
throw new cucumber.api.PendingException();
}
}
This is what our Page Object class should look like…
What are those Test Objects?
Yea, so about those… We’re either going to create those ourselves (this means NOT using the Recorder any more, also we write our own xpath for them), or find the ones that the Recorder generated and rename them…
Dropdown button
Recorder should have the dropdown button already. It’s whatever we click on to spawn the dropdown list.
Rename it to, for the sake of this question, Your Page/Multi-Select Dropdown/Dropdown button
. This means making a folder called Your Page
, inside which create a folder called Multi-Select Dropdown
, inside which go this and all the other Test Objects for this multi-select!
First dropdown option
Recorder should have it already, however I would still change its xpath to something like:
(//div[contains(concat(' ', @class, ' '), ' multiselect ')]//span[contains(concat(' ', @class, ' '), ' multiselect__option ')])[1]
NOTE: this will NOT work if you have multiple multiselect widgets on the page. For the example you gave, I had to change the first part of that xpath to :
((//div[contains(concat(' ', @class, ' '), ' multiselect ')])[1]
Alternatively, you can use CSS selector for this one to simplify things:
div.multiselect:first-child span.multiselect__option:first-child
Save this Test Object (or the existing Recorder Test Object) as Your Page/Multi-Select Dropdown/First dropdown option
.
The getDropdownOption()
method
If we look at the xpath for the first dropdown option (this stuff is why xpath is so commonplace in web automation testing!), we see that we can easily adapt it for selecting dropdown option based on ANY key words:
((//div[contains(concat(' ', @class, ' '), ' multiselect ')])[1]//span[contains(concat(' ', @class, ' '), ' multiselect__option ')])[.//text() = 'License']
is the xpath for the ‘License’ dropdown option for your example
We just add a [.//text() = 'License']
to get that.
Knowing this, we can now implement the method:
public TestObject getDropdownOption(String option) {
return new TestObject("${this.MultiSelectPartDesc}/'${option}' Dropdown option")
.addProperty("xpath",
ConditionType.EQUALS,
"(//div[contains(concat(' ', @class, ' '), ' multiselect ')]//span[contains(concat(' ', @class, ' '), ' multiselect__option ')])[.//text() = '${option}']");
}
The getOptionTag()
method
Let’s select an option and take a look at the DOM for the option tag (we select the ‘License’ option):
<div class="multiselect__tags">
<div class="multiselect__tags-wrap" style="">
<span class="multiselect__tag"
><span>License</span> <i tabindex="1" class="multiselect__tag-icon"></i
></span>
</div>
<!---->
<div class="multiselect__spinner" style="display: none;"></div>
<input
name=""
type="text"
autocomplete="off"
spellcheck="false"
placeholder="Pick badges"
tabindex="0"
aria-controls="listbox-null"
class="multiselect__input"
style="width: 0px; position: absolute; padding: 0px;"
aria-activedescendant="null-0"
/>
<!---->
<!---->
</div>
The “License” tag is a span, with class multiselect__tag
. It’s pretty straightforward what the xpath, and the method, should look like:
public TestObject getOptionTag(String option) {
return new TestObject("${this.MultiSelectPartDesc}/'${option}' Tag")
.addProperty("xpath",
ConditionType.EQUALS,
"(//div[contains(concat(' ', @class, ' '), ' multiselect ')]//span[contains(concat(' ', @class, ' '), ' multiselect__tag ')])[.//text() = '${option}']");
}
OK this is great, now how do I use all this?
Simple:
- change your data store to just pass the names (no need for the indices any more)
- create an instance of the page object (let’s call it
page
)
page.doMultiSelect(yourList)
Lmk if you have any questions