BDDStack How to build Test Pages - bcgov/common-service-showcase GitHub Wiki
Page Object pattern
BDDStack follows the Page Object pattern, the Page Object Pattern gives us a common sense way to model content in a reusable and maintainable way.
Within your web app’s UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
Page Objects can be thought of as facing in two directions simultaneously. Facing towards the developer of a test, they represent the services offered by a particular page. Facing away from the developer, they should be the only thing that has a deep knowledge of the structure of the HTML of a page (or part of a page) It’s simplest to think of the methods on a Page Object as offering the "services" that a page offers rather than exposing the details and mechanics of the page. As an example, think of the inbox of any web-based email system.
The Page Object Pattern is an important technique, and Geb provides that support via its page and module constructs.
Step 1
Identify the Pages you will need to "drive". For this you need to identify:
- URL of page (we'll only need the variable part after the base url, so if you page is
http://demo.com/app/home
, your url will behome
and you base url will behttp://demo.com/app/
. This base url is a variable that can be set to any location, which allows you to use the same tests against a variety of instances like DEV, TEST, UAT etc.) - Identifiable elements that will allow you to determine that you are on the right page: typically this would be the html title, but can be any other element in the page.
Your basic page looks like this:
package pages
class HomePage extends BaseAppPage {
static at = { title == "Home" }
static url = 'home'
static content = {
}
}
Please note the page name, it needs to start with a Capital Letter and end with "Page".
Step 2
Identify the element in your page, which you want to use in your tests. Here we can hide some of the complexities of element identification and give our page elements unique and easy-to-understand names. Geb offers very powerful tools to make this happen even for very complex applications. Please refer to https://gebish.org/manual/current/#the-code-code-function(the Geb manual).
Identified elements will be added to the static content
section.
Example:
package pages
class HomePage extends BaseAppPage {
static at = { title == "Home" }
static url = 'home'`
static content = {
firstParagraph { $("div p", 0) }
somethingParagraph { $("div p", text: "something") }
pageTitle { $(title: "something") }
}
}
Addressing these elements in the test would now be easy, for instance:
assert firstParagraph.text() == "Hello"
It is also possible to use any CSS selector directly, for instance:
documentHeader { $("#wiki-wrapper > div.d-flex.flex-column.flex-md-row.gh-header > h1") }
This is often a first step to get something working, later you can refine the definition to something more robust that does not necessarily depends on the page being exactly like this for ever.
The following is an actual project example of page used in testing:
package pages
class AdminHomePage extends BaseAppPage {
static at = { title == "Industrial Camps Admin" }
static url = 'minesoperatorscreening/admin'
static content = {
toolbarTitle { $("div", class: "v-toolbar__title title", "data-test":"btn-header-title",text: contains('Industrial Camps Admin')) }
header_One { $("h1.my-8") }
header_Two { $("h3.mb-8") }
requestAccess { $("button", "data-test": "btn-base-secure-request-access")}
popUp { $("div.v-card").displayed }
okButton { $("button", "data-test": "btn-base-dialog-ok")}
}
}
Note that it is also possible to tack on actions to the element definition like popUp { $("div.v-card").displayed }'. This definition returns a boolean so we can use that to simply test by
assert popUp`.
Step 3
It is also possible to add functions to the page definition so that in our test scripts we can simply refer to the function. For instance, if I want to create a function that clicks on a button, I can do the following:
package pages
class AdminHomePage extends BaseAppPage {
static at = { title == "Industrial Camps Admin" }
static url = 'minesoperatorscreening/admin'
static content = {
toolbarTitle { $("div", class: "v-toolbar__title title", "data-test":"btn-header-title",text: contains('Industrial Camps Admin')) }
header_One { $("h1.my-8") }
header_Two { $("h3.mb-8") }
requestAccess { $("button", "data-test": "btn-base-secure-request-access")}
popUp { $("div.v-card").displayed }
okButton { $("button", "data-test": "btn-base-dialog-ok")}
}
void oK() {
okButton.click()
}
}
And then in the test script simply call `oK()'. This way you can hide a lot of complexity.
Advanced Steps
Modules are re-usable definitions of content that can be used across multiple pages. They are useful for modelling things like UI widgets that are used across multiple pages, or even for defining more complex UI elements in a page.
They are defined in a manner similar to pages, but extend Module.
class FormModule extends Module {
static content = {
button { $("input", type: "button") }
}
}
Pages can “include” modules using the following syntax…
class ModulePage extends BaseAppPage {
static content = {
form { module FormModule }
}
}
Details can be found in the Geb Manual.