FEEL THE
POWER OF THE
JDI
LIGHT
JDI
LIGHT
22 MARCH 2019
•
JDI Evangelist
•
Almost 14 years in Testing
•
More than 12 years in Test Automation
ROMAN IOVLEV
Chief QA Automation
roman.Iovlev
roman.Iovlev.jdi@gmail.com
2
http://roman-iovlev.info/
JDI FRAMEWORK
JDI
L
i
g
h
t
•
Simple to use test Automation Framework
•
Based on UI Elements (Buttons, Forms etc.)
•
Fast and stable tests execution
•
User friendly logs and reports with no effort
[since
2015]
JDN
•
Simple and modern
Page Objects Generator
JDI Dark
•
Web Services testing Framework
powered by Rest Assured
3
JDI Mobile
•
UI Test Framework for Mobile
JDI Desktop
•
UI Test Framework for Desktop
PAGE OBJECTS
4
Header
Main Section
Menu
Odd Radio buttons
Weather Multiselect
Name Textfield
Passport checkbox
Description textarea
Composite
Complex
Simple
UI PAGE OBJECTS
@JSite
(
“https://epam.github.io/JDI/ "
)
public class
EpamSite
{
@Url
(
"/index.html”
)
public
static
HomePage
homepage
;
public
static
ContactPage
contactPage
;
}
@Url
(
"/contact/%s”
)
@Title
(
“Contact Form"
)
public class
ContactPage
extends
WebPage
{
@FindBy
(id=
“#Name”
) public
TextField
name
;
@Css
(
“#LastName”
) public
TextField
lastName
;
@UI
(
“[‘Submit’]”
) public
Button
submit
;
}
@BeforeSuite
(alwaysRun = true)
public static void setUp() {
initElements
(
EpamGithubSite
.class);
}
open()
back()/ forward()
refresh()/ reload()
clearCache()/ addCookie()
String getHtml()
zoom(double factor)
5
UI ELEMENTS
6
public class LoginForm extends Form<User> {
TextField
login, password;
Button
enter;
}
@FindBy
(id =
“jdi-logo”
)
public static
Image
jdiLogo;
@Css
(
“#colors”
) public
Dropdown
colors
@XPath
(
“//*[@id=‘nav’][text=‘%s’]”
)
public
Menu
navigation;
@UI
(
“#nav[‘%s’]”
) public
Menu
navigation;
HTML ELEMENTS
•
HTML elements pack
•
Bootstrap elements
•
Angular collections
•
React collections
•
Vue collections
•
Telerik (KendoUI)
https://epam.github.io/JDI/html5.html
7
LIST OF SECTIONS
8
List<Section> = Elements in JDI
T
get
(String name/int index)
MapArray<String, T>
getMap
()
List<E>
asData
(Class<E> entityClass)
−
Get Section by name/index
−
Get all elements as Map
−
Get All elements as List of Sections
public class
SearchResult
extends Section {
@Title
public
WebElement
label
;
public
WebElement
link
;
public
WebElement
description
;
…
}
8
LIST OF SECTIONS EXAMPLES
@UI
(
“.g”
) List<
SearchResult
>
results
;
Assert.assertContains(
results
.get(5).
description
.getText(),
“JDI some text”
);
results
.get(
“EPAM JDI”
).
link
.click();
results
.assertThat().
value
(expetedValue);
results
.assertThat().
any
(e -> e.
name
.toLowerCase().contains(
“epam jdi"
));
results
.assertThat().
each
(e -> e.
name
.toLowerCase().contains(
"jdi"
))
|| e.
description
.contains(
“jdi"
));
results
.assertThat().
onlyOne
(e -> e.
name
.contains(
"EPAM JDI"
));
results
.assertThat().
noOne
(e -> e.
name
.contains(
"SELENIDE"
));
9
ELEMENT ASSERTS
redButton
.assertThat().text(
is
(
“Submit”
));
notification
.is().displayed();
teaser
.assertThat().text(
containsString
(
“JDI is Awesome"
))
.cssClass(
is
(
“jdi-teaser"
)).attr(
"type"
,
is
(
“submit"
));
results
.is().notEmpty();
results
.assertThat().values(
hasItem
(
“JDI"
));
Powered by Hamcrest
No more waits!
Compare to Selenide and Selenium asserts
10
NO PAGE OBJECTS STYLE
@Test
public void nonPageObjectTest() {
WebPage
.
openUrl(
"https://epam.github.io/JDI/index.html"
)
;
$
(
"img#user-icon"
).
click()
;
$
(
"form #name"
).
input(
"epam"
)
;
$
(
"form #password"
).
input(
"1234"
)
;
$
(
"form [type=submit]"
).
click()
;
Assert.assertEquals(
WebPage
.
getUrl()
,
"https://epam.github.io/JDI/index.html"
);
}
11
REDUCE AMOUNT OF
CODE IN 5-30 TIMES
1
FORM EXAMPLE
13
REDUCE AMOUNT OF CODE
public class ContactForm {
@FindBy(xpath = "//div[contains(@class,'contacts')]//*[text()='Name']")
public WebElement name;
@FindBy(xpath = "//div[contains(@class,'contacts')]//*[text()='Family Name']")
public WebElement familyName;
@FindBy(xpath = "//div[contains(@class,'contacts')]//*[text()='Passport Id']")
public WebElement passId;
@FindBy(xpath = "//div[contains(@class,'contacts')]//*[text()='Passport Number']")
public WebElement passNum;
...
@FindBy(xpath = "//div[contains(@class,'contacts')]//*[text()=‘Accept’]")
public WebElement accept;
@FindBy(xpath = "//div[contains(@class,'contacts')]//button[text()='Submit']")
public WebElement submitButton;
public void submit(String name, String familyName, String passportId,
String passportNumber, ...., Boolean accept) {
this.name.sendKeys(name);
this.familyName.sendKeys(familyName);
this.passportId.sendKeys(passportId);
this.passportNumber.sendKeys(passportNumber);
...
if (accept && !this.accept.isSelected() ||
!accept && this.accept.isSelected())
this.accept.click();
submitButton.click();
}
}
14
REDUCE AMOUNT OF CODE
public class ContactForm {
public void submit(String name, String familyName, String passportId,
String passportNumber, ...., Boolean accept) {
$("//div[contains(@class,'contacts')]//*[text()='Name']").sendKeys(name);
$("//div[contains(@class,'contacts')]//*[text()='Family Name']").sendKeys(familyName);
$("//div[contains(@class,'contacts')]//*[text()='Passport Id']").sendKeys(passportId);
$(“//div[contains(@class,'contacts')]//*[text()='Passport Number']").sendKeys(passportNumber);
...
SelenideElement accept = $("//div[contains(@class,'contacts')]//*[text()=‘Accept’]")
if (accept && !this.accept.isSelected() ||
!accept && this.accept.isSelected())
$("//div[contains(@class,'contacts')]//button[text()='Submit']") .click();}
}
15
public void check(String name, String familyName, String passportId,
String passportNumber, ...., Boolean accept) {
assertEquals($("//div[contains(@class,'contacts')]//*[text()='Name']").getText(), name);
assertEquals($("//div[contains(@class,'contacts')]//*[text()='Family Name']").getText(), familyName);
assertEquals($("//div[contains(@class,'contacts')]//*[text()='Passport Id']").getText(), passportId);
assertEquals($(“//div[contains(@class,'contacts')]//*[text()='Passport Number']").getText(), passportNumber);
REDUCE AMOUNT OF CODE
Ability to fill and submit form of 10 elements
And verify that this form filled correctly takes
40-45 lines of code
16
?
REDUCE AMOUNT OF CODE
…
public void submit(
String
name,
String
familyName,
String
passportId, ....,
Boolean
accept) {
this.
name
.sendKeys(name);
this.
familyName
.sendKeys(familyName);
...
if (accept && !this.
accept
.isSelected() ||
!accept && this.
accept
.isSelected())
this.
accept
.click();
submitButton
.click();
}
}
public class ContactForm
{
@FindBy
(xpath =
"//div[contains(@class,'contacts')]//*[text()='Name']"
)
public WebElement
name
;
@FindBy
(xpath =
"//div[contains(@class,'contacts')]//*[text()='Family Name']"
)
public WebElement
familyName
;
…
}
17
REDUCE AMOUNT OF CODE
public class ContactForm
extends Form<Contacts>
{
@FindBy
(xpath =
"//div[contains(@class,'contacts')]//*[text()='Name']"
)
public WebElement
name
;
@FindBy
(xpath =
"//div[contains(@class,'contacts')]//*[text()='Family Name']"
)
public WebElement
familyName
;
…
}
@FindBy
(css =
“div.contacts"
)
public ContactForm contactForm;
public class ContactForm extends Form<Contacts> {
@FindBy
(xpath =
"//*[text()='Name']"
) public WebElement
name
;
@FindBy
(xpath =
"//*[text()='Family Name']"
) public WebElement
familyName
;
…
}
18
REDUCE AMOUNT OF CODE
submitContactForm(
“Roman”
,
“Iovlev”
,
“123456”
, ... ,
true
);
contactForm.submit(
ROMAN
);
19
public class ContactForm extends Form<Contacts> {
@XPath
(
"//*[text()='Name']"
) WebElement
name
;
@XPath
(
"//*[text()='Family Name']"
) WebElement
familyName
;
…
}
public class ContactForm extends Form<Contacts> {
@UI
(
"['Name']"
)
WebElement
name
;
@UI
(
"['Family Name']"
) WebElement
familyName
;
…
}
public class ContactForm extends Form<Contacts> {
UIElement
name
= $(
"['Name']"
);
UIElement
familyName
= $(
"['Family Name ']"
); …
…
}
REDUCE AMOUNT OF CODE
public class ContactForm {
@FindBy(css = "//div[contains(@class,'contacts')]//*[text()='Name']")
public WebElement name;
@FindBy(css = "//div[contains(@class,'contacts')]//*[text()='Family Name']")
public WebElement familyName;
@FindBy(css = "//div[contains(@class,'contacts')]//*[text()='Passport Id']")
public WebElement passId;
@FindBy(css = "//div[contains(@class,'contacts')]//*[text()='Passport Number']")
public WebElement passNum;
...
@FindBy(css = "//div[contains(@class,'contacts')]//*[text()=‘Accept’]")
public WebElement accept;
@FindBy(css = "//div[contains(@class,'contacts')]//button[text()='Submit']")
public WebElement submitButton;
public void submit(String name, String familyName, String passportId,
String passportNumber, ...., Boolean accept) {
this.name.sendKeys(name);
this.familyName.sendKeys(familyName);
this.passportId.sendKeys(passportId);
this.passportNumber.sendKeys(passportNumber);
...
if (accept && !this.accept.isSelected() ||
!accept && this.accept.isSelected())
this.accept.click();
submitButton.click();
}
}
public class ContactForm extends Form<Contacts> {
@UI("['Name']") WebElement name;
@UI("['Family Name']") WebElement familyName;
@UI("['Passport Id']") WebElement passtId;
@UI("['Passport Number']")WebElement passNum;
...
@UI("['Name']") WebElement accept;
@UI(“['Submit']") WebElement submitButton;
}
4X
public class ContactForm extends Form<Contacts> {
WebElement name, familyName, passtId, passNum,
... accept, submitButton;
}
20
40-45
10
2
20X
SMART LOCATORS
21
public class Contacts extends Form<User> {
TextField
name, lastName,
gender, city,
address, state;
Button
submitButton;
}
public class Contacts extends Form<User> {
WebElement
name, lastName,
gender, city, address, state, submitButton;
}
public class Contacts extends Form<User> {
@Css
(
“#name”
)
WebElement
name;
@Css
(
“#last-name”
)
WebElement
lastName;
@Css
(
“#gender”
)
WebElement
gender;
@Css
(
“#city”
)
WebElement
city;
@Css
(
“[ui=address]”
)
WebElement
address;
@Css
(
“[ui=state]”
)
WebElement
state;
@XPath
(
“#submit-button”
)
WebElement
submitButton;
}
IN ELEMENT ACTIONS
new Actions(driver)
.moveToElement(
myInput
)
.click().keyDown(Keys.SHIFT)
.sendKeys("hello")
.build().perform();
myInput
.do(a ->
a.click().keyDown(Keys.SHIFT)
.sendKeys("hello")
);
new Actions(driver)
.clickAndHold().release()
.build().perform();
myInput
.doActions(a ->
a.clickAndHold().release()
);
22
myButton
.jsExecute(“click()”)
((JavascriptExecutor) driver).executeScript
(
"arguments[0].click();"
,
myButton
);
JAVA SCRIPT
ACTIONS
ALSO REDUCE CODE
•
Remove all Thread.sleep / waitUntil / waitPageLoad etc.
•
Reduce code for Tables, Dropdowns and other Complex elements
•
Remove most of logger strings in code
•
Simplify locators
•
Driver Manager: Auto-download driver
23
JDN: UI OBJECTS
GENERATOR
2
WRITE TESTS IN MINUTES
JDN
25
JDN DEMO
26
1. Open EpamGithub site https://epam.github.io/JDI
2. Click user icon
3. Login as User (name: epam, password: 1234)
4. Select Contacts page in sidebar menu
5. Check that Contacts page opened
6. Submit contacts form with:
gender = "Male"; religion = "Other"; wheather = "Sun, Snow"; passport = "true"; name = "Roman"; lastName =
"Iovlev"; position = "QA Automation"; number = "4321"; seria = "123456"; description = "JDI awesome";
7. Check that contact form filled with expected data:
gender = "Male"; religion = "Other"; wheather = "Sun, Snow"; passport = "true"; name = "Roman"; lastName =
"Iovlev"; position = "QA Automation"; number = "4321"; seria = "123456"; description = "JDI awesome";
GUESS HOW LONG IT WILL TAKE FOR YOU
> 8h
4-8 h
2-4 h
1-2 h
< 1 h
10-20 min
27
JDN DEMO
28
JDN DEMO
27
BEFORE GENERATION
30
GENERATED PAGE OBJECTS
31
WRITE TESTS IN MINUTES
32
@Test
public void loginTest() {
homePage
.open();
header
.userIcon.click();
header.loginForm
.loginAs(DEFAULT_USER);
homePage
.checkOpened();
leftMenu
.select(ContactForm);
contactFormPage
.checkOpened();
main.contactForm
.submit(DEFAULT_CONTACTS);
main.contactForm
.check(DEFAULT_CONTACTS);
}
1.
Generate PageObjects via JDN and place in appropriate folder
2.
Add test data (
DEFAULT_USER, DEFAULT_CONTACTS
)
3.
Write simple test code
15 sec
2-3 minutes
1-2 minutes
< 5 mins
RULES
33
Rules
<input type=“button”
value=“Next”
>
<input type=“button”
value=“Previous”
>
<button class=“btn”>
Submit
</button>
"type":"Button",
"name": “value",
"css": “input[type=button]“
"type":"Button",
"name": “text",
"css": “button.btn"
@Findby(
css=“input[type=button]
[value=Next]
”)
public Button
next
;
@Findby(
css=“input[type=button]
[value=Previous]
”)
public Button
previous
;
@Findby(
xpath=“//button[@class=‘btn’
and
text()=‘Submit’
]”)
public Button
submit
;
BUSINESS LOGS AND
REPORTS
3
GET CLEAR AND OBVIOUS RESULTS
LOGS AND REPORTING
35
[ STEP 53:25.314] : Open 'Home Page'(url=https://epam.github.io/JDI/index.html)
[ STEP 53:29.616] : Click on 'User Icon'
[ STEP 53:29.760] : Login as User(name:epam; password:1234)
[ STEP 53:30.307] : Check that 'Home Page' is opened (url CONTAINS '/JDI/index.html'; title EQUALS 'Home Page')
[ STEP 53:30.330] : Select '[Contact form]' in 'Left Menu'
[ STEP 53:31.176] : Check that 'Contact Form Page' is opened (url CONTAINS '/JDI/contacts.html'; title EQUALS
'Contact Form')
[ STEP 53:31.202] : Submit 'Contact Form' with Contacts(
gender:Male; weather:Sun, Snow; passport:true; name:Roman;
lastName:Iovlev; position:QA Automation; passportNumber:4321; passportSeria:123456; description:JDI awesome
)
[ STEP 53:33.043] : Check that 'Contact Form' values are: Contacts(
gender:Male; weather:Sun, Snow; passport:true;
name:Roman; lastName:Iovlev; position:QA Automation; passportNumber:4321; passportSeria:123456; description:JDI awesome
)
LOGS
LOGS AND REPORTING
36
STABLE AND FAST
4
INCREASE TRUST TO RESULTS
REDUCE MAINTENANCE EFFORT
STABLE
38
1. No more false negative
2. No Thread sleeps or
other waits
•
Increase trust to your tests and to testing as practice
•
Saves up to 50-80% of waste engineers effort on bug analyses
•
Increase tests execution speed
Write Less code
FAST TEST EXECUTION
39
1.
Textarea input 3000 symbols
2.
Dropdown 300 values
3.
Table with 400 rows (4 columns)
?
https://epam.github.io/JDI/performance.html
- for login use epam/1234
FAST TEXTFIELD AND DROPDOWN
JDIPerformanceTests.CS
~96ms
1.
Textarea input 3000 symbols
~17 seconds
~250ms
2.
Dropdown with 300 values
~12,5 seconds
40
170X
50X
…
FAST TABLE EXAMPLE
usersTable
.assertThat().hasRowWithValues(
containsValue(
“Brock"
, inColumn(
"Name"
)),
containsValue(
".org"
, inColumn(
"Email"
)));
JDIPerformanceTests.CS
41
https://epam.github.io/JDI/performance.html
400
~900ms
~5 seconds
5X
JDI IS SIMPLE
5
SIMPLE TO START FROM SCRATCH
1.
Simple to setup
•
Just add dependency in your project or use predefined Project Templates
2.
Simple to learn and write tests
•
Native UI Elements based interface
•
Well documented
(will be available at April-May 2019)
•
Lot of real examples
3.
Simple to maintain
•
Native tests, No false negatives, No waits, detailed logs and reporting
4.
Simple to customize
•
Flexible to customization on all levels
5.
Simple to integrate
•
Templates for integration with popular tools
43
SIMPLE TO IMPROVE SELENIUM PROJECTS
1. Change dependency
<
dependency
>
<
groupId
>com.epam.jdi</
groupId
>
<
artifactId
>jdi-light</
artifactId
>
<
version
>RELEASE</
version
>
</
dependency
>
dependencies
{
compile
'com.epam.jdi:jdi-light:+'
}
2. Init your page objects with JDI Light
PageFactory.initElements
(
driver
, HomePage.
class
);
import static
com.epam.jdi.light.ui.html.PageFactory.
initElements
;
44
See
https://github.com/jdi-examlples/jdi-selenium
> branch
switch-selenium-to-jdi
Already have
thousands of
Selenium tests?
JDI
L
I
G
H
T
HIGHLIGHTS
45
JDI LIGHT HIGHLIGHTS
1.
Simple to start and use. Simple to switch from Selenium
2.
Cool typified elements that native for your application and has
a lot of cool unique features out of the box
3.
Stable and predictable test results
4.
Good logs and reports out of the box
5.
Helps to reduce amount of code (LOC) in times
6.
Increase test’s development speed and test run’s performance
46
START AND INTEGRATION
Templates:
https://github.com/jdi-templates/
47
Examples:
https://github.com/jdi-examples/
Our projects:
https://github.com/jdi-testing/
Hamcrest matchers
Powered by Selenium
Use Hamcrest, log4j and Allure
Easy integration with Selenium Grid,
Selenoid, BrowserStack etc.
Documentation:
https://jdi-docs.github.io/jdi-light/
(will be available at April-May 2019)
JDI PRODUCTS
https://github.com/epam/jdi
https://github.com/jdi-testing/jdi-2.0
https://github.com/jdi-testing/jdi-light
-
JDI original
-
JDI 2.0
-
JDI Light
Since 2015
Autumn 2017
Spring 2018
https://github.com/jdi-testing/jdi-http
-
JDI Dark
https://github.com/jdi-testing/jdi-flash-pages
-
JDN
Spring 2018
https://github.com/jdi-testing/jdi-lightsaber
-
JDI LightSaber
Autumn 2017
Since 2016
48
ULTIMATE GOAL
49
You may say I'm a dreamer
But I'm not the only one
I hope someday you'll join us
And the world will have a fun
Imagine there's no, time wasting
It isn't hard to do
No matter what are you testing
And logs are clear too
Imagine all applications tested by AI…
You-u-u-u
•
Use AI for PageObjects generation and self-healing; test cases and test
data generation; smart tests execution and analyzes
•
Increase testing tool efficiency and reduce time on routines dramatically
•
I hope you’ll join us
Q & A
50
roman.Iovlev
roman.Iovlev.jdi@gmail.com
http://roman-iovlev.info/
https://join.skype.com/u2Cel0MWHkAO
https://jdi-docs.github.io/jdi-light
APPENDIX:
MORE COOL FEATURES
X
XPATH OR CSS LOCATORS?
//*[contains(@class,’user’)]//*[@value=‘Roman’]
//div[@name=’product’]//*[contains(class(),‘toast’)]
//*[@id=’product’]/*[@type=‘checkbox’]
//*[@class=’ice-cream’]//*[@type=‘text’]
.user [value=Roman]
div[name=product] .toast
#product [type=checkbox]
.ice-cream [type=text]
@FindBy
(xpath =
“…”
)
@FindBy
(css =
“…”
)
BUT…
52
UI LOCATORS
//*[contains(@class,’user’)]//*[text()=‘Roman’]
//div[@name=’product’]//*[contains(text(),‘Toast’)]
//*[@category=’product’][2]
//*[text()=’Ice Cream’]/..//*[type=‘checkbox’]
.user
[‘
Roman
’]
div[name=product]
[*‘
Toast
’]
[category=product]
[2]
[’Ice Cream’]
<
[type=checkbox]
@FindBy
(xpath =
“…”
)
@UI
(
“…”
)
public void selectMenu(
String
value
) {
driver.findElement(
By.cssSelector(
“[value=“
+
value
+
”]”
)).click();
}
selectMenu(
“About”
)
@Css
(
“[value=
%s
]”
) WebElement menu;
menu.select(
“About”
)
53
MANAGE WINDOWS
getWindows()
newWindowIsOpened()
setWindowName(String name)
windowsCount()
switchToNewWindow()
openNewTab()
originalWindow()
switchToWindow(int number)
switchToWindow(String name)
closeWindow()
closeWindow(String name)
WindowsManager
WindowsAndFramesTests.cs
−
return all WindowHandles
−
return true if new window opened
−
Set specific name for current window
−
return count of opened windows
−
switches to latest opened window
−
open new window
−
switch to first window
−
switch to window by number
−
switch to window by name
−
close current window
−
close window by name
54
acceptAlert()
declineAlert()
getAlertText()
sendKeysInAlert(String text)
Alert alert()
WindowsAndFramesTests.cs
−
accept alert
−
decline alert
−
get alert text
−
Input text in alert text field
−
get alert instance
WindowsManager
55
MANAGE ALERTS
UI ELEMENTS
UIElement = WebElement
•
jsClick
();
•
setText
(String value);
•
getText
();
•
input
(String text);
•
setAttribute
(String name, String value);
•
select
(String name);
•
setValue
(String value);
•
show
();
•
hover
();
•
scrollUp/Down/Left/Right
(int value)
•
higlight
() /
higlight
(String color)
•
find
(By) /
find
(String) /
finds
(String)
•
printHtml
()
•
isDisabled
() /
isHidden
()
•
dragAndDropTo
(WebElement) (x, y)
•
doubleClick
()
•
rightClick
()
•
select
() -> new Select(myElement)
56
WEB LIST FILTERS
public void
HeaderLine
{
@Css
(
“#navigation”
) public List<WebElement> menu;
@Css
(
“#navigation[item=
%s
]”
) public WebList menu;
@Css
(
“#navigation”
) public WebList menu;
}
WebElement
get
(String)
void
select
(String/Int)
String
getValue
()
−
Get element by text value
−
Click on element with text/int value
−
Print list values
WebList = List<WebElement>
57
WEB LIST FILTERS
WebList = List<WebElement>
+ stream()
WebElement
first/last
()
T
first/last
(JFunc1<T, Boolean> condition)
List<T>
where/filter
(JFunc1<T, Boolean> condition)
<R> List<R>
select/map
(JFunc1<T, R> transform)
void
ifDo
(JFunc1<T, Boolean> condition, JAction1<T> action)
List<R>
ifSelect
(JFunc1<T, Boolean> c,
JFunc1<T, R> t)
void
foreach
(JAction1<T> action)
boolean
any
(JFunc1<T, Boolean> condition)
boolean
all
(JFunc1<T, Boolean> condition)
List<T>
listCopy
(int from[, int to])
List<R>
selectMany
(JFunc1<T, List<R>> func)
−
Get first/last element
−
Get first/last element by condition
−
Filter elements by condition
−
Transform all elements using template
−
Do action in case of
condition true
−
Transform all elements that suit to condition
−
Do action for all elements
−
True if
at least one
of elements suit condition
−
True if
all
elements suit condition
−
Copy part of list
−
Transform List of list to list
58
Forget about Selenium. May the JDI Light force be with you