This document describes a UI testing pattern using React, Material-UI, BDD with Cucumber, and Kotlin/Selenide. It utilizes the Se34 page object generator to generate page object classes from metadata. This separates page representation from test logic for improved maintenance. Tests are defined in BDD style and cover use cases like login, account creation, and password recovery. Page objects and step definitions enable clear and maintainable automation code. Test results are output to the console.
2. Table of
CONTENTS
Introduction.............................................................................1
Structure of Solution.................................................................2
The Tooling .............................................................................4
The Browser Configuration ........................................................5
The Software under Test ...........................................................6
Finding Visual Elements.............................................................7
The Meta-Data Description ........................................................9
Generating Page Objects ......................................................... 10
Separation of Concerns ........................................................... 12
Table Metrics ......................................................................... 13
Page Object Characteristics ..................................................... 17
Test Characteristics ................................................................ 19
Use Cases ............................................................................. 20
Test Results........................................................................... 21
Preparing to Search Customers ................................................ 22
Searching.............................................................................. 26
Selecting Tests....................................................................... 32
Remarks ............................................................................... 34
Summary .............................................................................. 35
3. INTRODUCTION
The purpose of this work is to exemplify a pattern of agile UI testing in a
modern UI context. In addition, the use of BDD (Cucumber [here]) should
prove useful to other practitioners looking for a serious example of its use.
One of the pillars of the approach shown, is the use of the Se34 Page Object
generator [here]. This tool, using as it does, page metadata, is a critical
component of this test automation strategy that brings it to (Warp Speed ==
Agile).
The application under test is one that is based on the React development
framework [here]. In addition, the UI makes use of the Material-UI [here]
element library.
Many examples that can be found on the web of UI testing are rather small
scale, often focused on a basic version of a login panel. The application in this
work is fully fledged and contains many business-oriented workflows, operating
against a realistic backend. The initial user workflow studied does not break
with the tradition of UI testing examples but goes very much beyond “the
normal”, connecting our testing with BDD expressions for a range of key
(SignIn/Create/Recover) workflows.
Our multi-lingual target application (SUT) presents a SignIn page as:
4. 2
And once signed in, shows a Dashboard page as:
This page comprises a SideBar menu with content links, a TitleBar with
language and UserName links (to switch language or SignOut, for example) and
a main area containing a grid of graphs of various types (using synthetic data).
STRUCTURE OF SOLUTION
The pattern of solution proposed here has several “moving parts”. Firstly, the
starting point of setting out the structure of tests are the BDD statements
expressed in Cucumber. In an ideal development world, arguably grounded in
agile processes, these sorts of statements would be written with Business
stakeholder involvement, perhaps using the Three Amigo’s style.
5. 3
This form of BDD expression is supported in both the C#/.Net [Specflow, here]
and Java/Kotlin [Cucumber, here] development platforms.
In the example pattern shown here, based on the use of the IntelliJ IDE [here],
the BDD statements are transformed to Kotlin [here] code, the Step Definitions.
They could just as easily have been transformed to Java.
It’s at this point, when we have the Step Definitions that we need to decide how
the parts of the UI will be represented in the tests and how we will use this
representation to “drive” the Software Under Test (SUT). Since 2013, there has
been a general understanding that the Page Object model of application page
representation, is perhaps the most effective approach. This is the approach
adopted here, however with the crucial difference that all the Page Object
classes needed are generated from meta-data. This deals with an observation
that can be made when one starts using the Page Object pattern in real projects
settings, it takes real work to bring them into being. This work quickly becomes
arduous, especially in agile project settings, and the ability to sprint test
automation becomes difficult if not impossible. In addition, the maintenance of
the Page Object collection, growing as it with the target application, becomes
challenging when there is significant change in the application UI codebase.
Embracing change becomes difficult.
Here, use is made of the Se34 tool [here] which takes a meta-data description
of the pages of an application that are to be considered, and generates
appropriate, finished Page Object classes. These classes can be in C#, Java or
Kotlin and contain code that reflects an automation framework of either
6. 4
Selenium [here] or Selenide [here]. The latter is built over Selenium, a Façade
so to speak, and represents a strong step forward in achieving a test
automation codebase that is both fully featured as well as fluent. The basic
structure of this generator-based solution for Page Objects, is shown below:
The page property files contain language- and page-specific key-value pairs
representing the textual values that can be validated.
THE TOOLING
In terms of the general tooling used, the table below gives the detail:
VERSION COMMENT
IntelliJ 2020.2 Community, IDE
Kotlin 1.4.10-release-
IJ2020.2-1
This is an add-in for IntelliJ
Page
Meta-data
Se34
Page Object
Classes
Page
property
Files
(optional
production)
7. 5
VERSION COMMENT
Selenide 5.15.1
Cucumber for
Kotlin
1.1.5 This is an add-in for IntelliJ
Gherkin 202.7660.3 This is an add-in for IntelliJ
Se34 V2 Cover the generation of C#, Java and
Kotlin for (Selenium, Selenide)
frameworks
THE BROWSER CONFIGURATION
For this example, only the Chrome browser is used (v86.0.4240.111) and its
configuration is as shown in the table below:
VALUE COMMENT
timeout 60000mS General timeout
pageLoadStrategy eager This is an add-in for IntelliJ
pageLoadTimeout 60000mS Timeout to be applied on page
loading
startMaximised true Maximise the browser screen
8. 6
THE SOFTWARE UNDER TEST
The software used as the target of the test pattern example, is one from
ScaffholdHub [here], in particular one which uses the React [here] and
Material-UI [here] frameworks. The main page of this multi-language
application is shown below:
And once signed in with a set of valid credentials, shows a Dashboard page as:
9. 7
This comprises a SideBar menu with category content links, a TitleBar with
language and UserName links (to switch language or SignOut, for example) and
a main area containing a grid of graphs of various types (using synthetic data).
Of course, in a real application such graphic content could well be showing
business-critical information. Our tests offer a first step to testing the presence
of these sorts of display items.
As well as a frontend, the application connects with a MySql [here] backend
database. The tables in the d/b correspond to a subset of the categories shown
in the SideBar menu.
FINDING VISUAL ELEMENTS
In its basic form, this application design takes no special steps to help the
process of test automation, for example by decorating visual elements with a
page-unique test-related attribute. So, for this work, where possible, elements
have had a new attribute, “data-test-id”, added. The process for such a step is
as outlined below by comparing the original view of the DashBoard page, and
its Chrome developer view, with the final form after modifying the appropriate
page code, in this case to more reliably address the graph elements:
Original view:
10. 8
Page code modification:
Final view:
Here the graphic elements on the DashBoard page have been decorated with
two custom attributes, (data-test-id) and (data-chart-type). These allow the
elements to be found and their graph type to be discovered.
The issue of reliably finding elements is a common one in test automation and
the addition of a special attribute to visual elements is a common step to help
resolve this issue. It should be noted, however, that several visual elements in
this application are already marked with a reliable (id) attribute.
Being able to embrace change in the UI as project development work proceeds
is also a key challenge. Having the UI, its locators and locator types expressed
in meta-data, as well as introducing test-related custom attributes to visual
elements, brings relief for this difficulty and allows change to be handled in a
fluent and agile way.
11. 9
THE META-DATA DESCRIPTION
The meta-data used to describe the “pages” of the application, and used as the
basis of Page Object generation, is held in a so-called Test Organisation (TO)
file, in XML form. A fragment of this file, for example, is shown below:
Here we see the fragment that provides the following information to Se34:
• Basic project data including the range of spoken languages to be covered
• The generation language to be used
• The test framework to be used
• For the Main page of the application what possible validations and
operations for which we want to code to be generated in an associated
Page Object class
This meta-data has an associated Schema which ensures correct data loading in
Se34.
12. 10
GENERATING PAGE OBJECTS
The Se34 tool is run from the command line and has the command structure is
as shown below:
When generating a set of Page Objects using the TO file appropriate to the
target application (no property stub files generated), we see the following
output:
As noted earlier, in addition to the generation of the basic Page Object classes,
Se34 can optionally generate starter validation property files. These are a set of
13. 11
page- and language-specific csv files that are read by the individual Page Object
classes when performing textual validation. As can be seen in the TO file of the
previous section, and from a study of the landing page of the application (see
section “THE SOFTWARE UNDER TEST” above), the current application handles four
distinct language types, English (EN), Spanish (ES), Brazilian Portuguese (PT)
and German (DE). In this case Se34 generates the file structure as shown
below:
The English version for the Main page has the content, once the appropriate
data values are entered, as shown below:
Whereas the Spanish version related to the Main page has the content:
14. 12
SEPARATION OF CONCERNS
From the previous sections it can be seen that the pattern of UI testing being
described has a strong theme of “Separation of Concerns” – meta-data, Page
Object classes (in which all the messy page detail is held and which act as
Service Providers), textual property data, BDD statements and Step Definition
files. The benefit of this separation is that there is a much clearer way forward
for the maintenance of tests, the following table shows what this means in
practical terms.
COMMENT
There have been
changes to a page of
the application
• Make appropriate adjustments to the TO file
• Re-generate the affected pages
• Generate new property file (if reqd.), saving
existing ones
• Add new property values (if reqd.)
The text of a visual
element has changed
• Adjust the specific property file (Page/Language)
content
The locator of an
element on a page has
changed
• Adjust the TO file content
• Re-generate the affected pages
An existing test needs
to change
• Update the relevant BDD statements
• Generate new/modified Step Definitions
• Change/add logic to achieve the step purpose
15. 13
COMMENT
A new page is to be
covered in tests
• Append a new page element the TO file content
• Generate the new Page Object and property file
• Add property values to the property file
• Author BDD, generate Step Definitions
• Change/add logic to achieve the step purpose
Under normal circumstances the code generated in Page Objects to perform
validations and operations is entirely satisfactory. However, there are some
visual elements for which interactions must be expressed in a more
fundamental form, for example using JavaScript. For these cases, the
corresponding “<operation/item>” in the TO meta-data must have its “key”
specified as “external”. Se34 then generates an operational method/function in
the Page Object which then immediately delegates to a user written
method/function in a specific namespace and having a specific naming
convention. This process is exemplified by the case of getting the relevant
metrics for the CustomerOverViewPage, shown below:
TABLE METRICS
16. 14
The metrics displayed in the footer area of the Customers table are key facts we
need when examining the content of the table and drawing conclusions relevant
to any test of the data.
The metrics highlighted in yellow in the figure above, have the following format:
“Per page: page-size” + “page-min - page-max of total-record-count” (not
forgetting that the prefix part is subject to the internationalization process). If
we look at the DOM for the elements (“EN” selected as language), we see:
The containing class for the group, “class=MuiTablePagination-root” has been
adapted to have a custom attribute “data-test-id=’tablePagination’”, but the
individual, interesting elements carry IDs that are dynamic, they get generated
17. 15
new each time the Customers page is displayed. The underlying design of the
page precludes blessing these elements with a custom attribute, so we need a
different strategy.
In the first instance we signal to Se34 that the “value” of the collection of items
should be retrieved using a function/method external to the
CustomerOverviewPage Page Object, see below:
This results in a method being generated in the Page Object class which looks
like:
The hand-coded delegate function to retrieve the page metrics looks like:
18. 16
As can be seen, we have used Selenide calls to retrieve the inner collection of
DOM “div” elements contained within the one having the custom attribute
named “tablePagination”. The text value of the first “div” element within this
container has embedded “n” characters and these we replace prior to returning
a string to the Page Object caller. The function “parsePageExtentText” is used
by our test code to parse the base string into its component parts and construct
an appropriate object for use in tests.
The relevant test code fragment, in the Customer step definition file, now looks
as below:
19. 17
Of course, the specific values we are asserting here are fully dependent on the
test data in the MySQL database.
PAGE OBJECT CHARACTERISTICS
In designing the Page Object generation process a clear naming convention has
been adopted. This has the benefit to the test developer of clear and repeatable
name forms associated with methods/functions, for example that would be
shown in an IDE-based Intellisense list.
TO File: Validation
Item name: createAccountTextLabel
Method/Function name: validateCreateAccountTextLabel()
The validation indicators, e.g. validateCreateAccountTextLabel() in the above
example, act fluently so can be chained together ending in a validatePage()
call.
A representative example of the way in which this fluent validation approach
works, can be seen in the following figure, one of the steps related to the basic
(“SmokeTest”) for the main landing page (“SignIn”):
20. 18
The exception to this pattern is that of dynamic validation, where a context-
specific value needs to be passed from the test to the validation function, for
example:
TO File: Operation
Item name/style: passwordValue/Enter
Method/Function name: doPasswordValueEnter(data: String)
21. 19
TEST CHARACTERISTICS
BDD statements form the starting point for what we want to test, the actual
coding part being delegated to the Step Definition files. Using the pattern
presented here, where “concerns” are separated, the coding of tests become
clear and uncluttered with such details as locator strings, locator types, URLs
and the like. Below is shown a fragment of the BDD statements for the
“SignIn/Create/Recover” Workflow Feature.
22. 20
Note how the Feature and Scenarios carry Cucumber decorations. This aspect
will form a key feature of our test selection process when we look at the
Cucumber test runner class in a later section.
USE CASES
For this test pattern example, given the specific SUT being tested (here), the
following User Cases are covered, centred on the (SignIn/Create
Account/Recover Password) user workflow:
COMMENT
Validation of Login screen static
textual elements
Tag: @SmokeTest
Validation of the Login screen
helper text elements
Tag: @RegressionTest
Login as the Test User and validate Tag: @SmokeTest
New User wants to create a new
account
Tag: @RegressionTest
User tries to SignIn with bad
credential
Tag: @SmokeTest
Existing user wants to recover
password then reverts
Tag: @SmokeTest
Existing User successfully signs in Tag: @SmokeTest
23. 21
These Use Cases are, of course, only a rough reflection of what a project might
decide as important to test in (SignIn/Create Account/Recover Password)
workflow.
TEST RESULTS
Of course, no example would be complete without showing the outcome of
testing.
Below is the IntelliJ test console output reflecting test completion when
executing the full BDD Feature set:
Of course, on project we would need to communicate test outcome not only to
immediate project members but also to a wider group of project stakeholders.
For this the console output (or some copy-paste version of it) is not quite
satisfactory. To provide quality reporting we might opt to use the Extent
Reports library [here].
24. 22
PREPARING TO SEARCH CUSTOMERS
In the earlier section “TABLE METRICS”, we touched upon the issues related to
getting the values of Table metrics, displayed, on the CustomerOverviewPage,
shown again below:
Of course, it is important to gather the metrics of the data being displayed, but
we need also to be able to access the header and body of the Table as well.
This operation is essential if we are to validate the Table content at any point in
our tests.
In addition to the basic display of data, three further characteristics of the Table
need to be highlighted and catered for in our testing.
Firstly, the content of the table can be sorted by clicking on any header, for
example, compare the fragment shown below with the figure above, following a
click on the column header “Customer Number”:
25. 23
We need to be able to perform this click on named headers in any relevant test.
Secondly, we have, on each of the Table rows three action controls:
• a “display detail” control (spyglass), the first of the three action elements
shown on the far right of each row, to view the full data of the associated
row
• an “edit row” control (pencil) to edit the row content
• a “delete row” control (trash can) to delete the Customer data associated
with the row
In our tests we should ensure that all these parts, key to user interaction, are
functioning correctly. Each result in a dialog or panel being shown.
And lastly, we will need to ensure that filtering of the data shown in the table is
working correctly. To filter, the user clicks on the rectangular strip, entitled
“Filters”, just below the top-level buttons (“NEW”, “IMPORT”, “DELETE”, “AUDIT
LOGS”, “EXPORT TO EXCEL”). Clicking on this strip causes a filter specification panel
to show, as below:
26. 24
Testing the operation of this filter on Table content is only possible when we can
address the Table content by row/header/cell.
However, before this, we need to ensure that the filter mechanism itself is
working as expected. To ensure this, consider the BDD below:
This test seems quite straightforward, except for one thing, detecting when the
Filter panel is on display or not. The detection of this state is another case
where we need to do something special.
In the TO file we specify the metadata for the CustomersFilterPage as shown
below:
27. 25
We take advantage of the capability of Se34 to generate external
functions/methods to allow a special logic to be defined to reliably detect the
existence of the Filter panel.
The page-specific (generated) and external functions are as shown below:
The logic here checks the value of a specific attribute of the root element of the
Filter accordion control, which reflects the real state/visibility of the panel and
this attribute is shown highlighted below in the Chrome DOM view:
28. 26
When we execute the BDD test we see the following outcome:
Now we have a reliable visibility mechanism, let us perform a test of searching
on our test data set using the Filter control.
SEARCHING
Now we have a mechanism to test the display/(non-display) of the Filter
accordion, we can test that the filtered search works as expected. For this we
are fully dependent on our test data.
29. 27
Many projects, at least in early product increments, get searching wrong, lots of
defects emerge and need to be dealt with, so as test automators we need to be
alert to this functionality early on in a project.
In the case of the application we are looking at we find that, per default, there
is no direct way to reflect over the table content. Whilst the table column
headers can be found using fixed xpath expressions this approach does not
work for the content. As was done in other places we looked at above, we add a
custom attribute to the cell elements that go to make up the table, decorating
them with a customerNumber key (specified in the d/b Schema), unique for the
Customer data rows. So now when we look at a cell element, an entry in the
column “Customer Name”, we see the DOM as shown below:
Now, for any search test we write we can specify the expected outcome and
given the unique customerNumber for the row we want to examine and hence
perform the validation on a cell-by-cell basis.
In the TO file the table meta-data comprises two parts: one specifying data for
the table headers and the other specifying how a row is organised, see below.
Se34 takes this definition and generates an appropriate class, which in our case
is called (CustomerOverviewTable.kt, CustomerOverviewTable). This, as with the
Page Object classes, operates as a service provider, and contains all the messy
detail that we would prefer not to have in our tests.
30. 28
A straightforward test for searching, expressed in BDD form, is as shown below:
Here the expected results are specified in a Cucumber Data Table bound to the
step “And the results match the entered Filter condition”. Several cases are
specified but here we only apply one example (“case = 1”).
The interaction with the CustomersOverviewPage, which displays the table,
comprises two of the BDD steps.
First, we assert the table metrics:
31. 29
And then we assert the detailed validation of the table headers as well as the
expected results, as defined in the BDD Data Table, for the currently executing
case:
32. 30
The results of running this simple test are shown in the following
IntelliJ/Cumber HTML report:
33. 31
Of course, this should not be the end of testing the search functionality, but our
test automation approach seems to have set us on a good way.
34. 32
SELECTING TESTS
As the tests grow, we will want to execute only some of the tests during a given
test campaign on the way to deliver a product increment, not all. Indeed, in
performing the tests in a DevOps build/delivery pipeline we should aim to
distribute them based on priority, from initial commit through to pre-UAT.
Cucumber allows the Features/Scenarios to be decorated so that they can be
selectively executed. The BDD statements we have been using have already
been decorated with this selectivity in mind, as shown, again, below:
With an appropriate test runner script, specific to Cucumber, we are able to run,
or block execution, by specifying one or more of the decorations “@REACT-
MATERIAL-UI-1001”, “@wip”, “@SmokeTest” or “@RegressionTest” (using perhaps
logical operators).
35. 33
The test runner to achieve this operational step, is as shown below:
In this example case we are requesting that all tests that are tagged with
“@REACT-MATERIAL-UI-1002” and “@SmokeTest”, are to be executed. In our case
this results in 2 tests with 6 cases being executed and this can be seen in the
resultant execution output:
Note that the Feature-level decoration is participating in our selection here.
36. 34
REMARKS
No test automation approach can be guaranteed to execute flawlessly in every
project and application setting. We should expect one or two surprises along the
way, some of which might turn out to be defects in the application and some
which need additional tricks on the part of the tester.
As shown in the foregoing the reliable interaction with a target application can
prove challenging. Some tactics have been touched on:
• Adapt the visual elements to have a test-specific, element-specific,
custom attribute, e.g. “data-test-id=”. Use context-specific values for
this custom attribute as was done when adapting cell values in the
CustomersOverviewTable
• Be wary of “id”s provided by an application which can handle a range of
spoken languages. Check that the value of this attribute is not subject to
the internationalization process
• When looking at the text of elements on screen it maybe that the actual
value of the element text is not in the case displayed. For example, the
text of a “<span” may be in upper case but in the DOM view it is shown in
lower case, having been transformed by an associated styling class
• Use code external to a Page Object where more fundamental steps of
interaction need to be taken. We do not want to mess with the generated
codebase.
The pattern of testing presented here goes a substantial way to minimise the
frustration & disappointment often associated with test automation in the real
world, on project. Because it rests on the generation of Page Objects from
relatively simple meta-data, it also allows the test automation theme to
participate more fully and satisfactorily in an Agile project. The test automator
can be more confident that completing a particular test, or set of tests,
(UNDERSTAND -> WRITE -> DEBUG -> SIGN-OFF -> EXTEND) is achievable in a sprint.
This work builds on previous Test Automation publications, see (here).
37. 35
SUMMARY
The tests developed in our example are as follows:
@REACT-MATERIAL-UI-1002 @wip
Customer.feature
@SmokeTest: Scenario Outline: Validation of Customer page access
(3 cases)
@SmokeTest: Scenario Outline: Basic Validation of Customer Page Filter
(3 cases)
@RegressionTest: Scenario Outline: Basic search of test data using Filter
@REACT-MATERIAL-UI-1001 @wip
LoginValidation.feature
@SmokeTest: Scenario Outline: Validation of Login screen static textual elements
(3 cases)
@RegressionTest: Scenario Outline: Validation of the Login screen helper text
elements
@SmokeTest: Scenario Outline: Login as the Test User and validate
(1 case)
@RegressionTest: Scenario Outline: New User wants to create a new account
@SmokeTest: Scenario Outline: User tries to SignIn with bad credential
(2 cases)
@SmokeTest: Scenario Outline: Existing user wants to recover password then reverts
(1 case)
@SmokeTest: Scenario Outline: Existing User successfully signs in
(1 case)
@REACT-MATERIAL-UI-1003 @wip
NonFunctional.feature
@NonFunctional: Scenario Outline: Validation of Error 500 page display
(1 case)
The Page Objects we described in meta-data and generated were as follows:
CreateAccountPage.kt
CustomerEntryPage.kt
CustomersFilterPage.kt
CustomersOverviewPage.kt
CustomersOverviewTable.kt
DashboardPage.kt
Error500Page.kt
ForgotPasswordPage.kt
HeaderLanguagePage.kt
HeaderOperationPage.kt
HeaderPage.kt
MainPage.kt
SidebarMenuPage.kt
This set represents something in the order of 25% of the collection of Page
Objects we would need to address all the functionality of the application.