Creating a Page Model

We’ve been working on a test to open https:/www.google.com and to query for the word “example”. For the querying part, we need to identify the search input field and the button to submit the search form.

"query 'example'":
  actions:
    - set $".gLFyf.gsfi" to "example"
    - click $".FPdoLc.VlcLAe input[name=btnK]"

  assertions:
    - $page.title is "example - Google Search"

The test step works for now so that’s fine, right?

Well, those selectors are a bit messy and that’s not so fine. The selectors are hard to read (.gLFyf.gsfi doesn’t really shout “search input”) and may well need to be updated in many places when the page being tested changes.

Let’s create a page model to store all of these page properties. We can refer to the page model in our tests, making the tests clearer to read. And we can make sure that if something needs updating it only needs to be changed in one place.

Creating and Using a Page Model

A page model is a YAML object with url and elements properties. The url property holds the page URL. The elements section maps convenience names to ways of identifying elements.

# examples/page/google.com.yml
url: "https://www.google.com"
elements:
  search_input: $".gLFyf.gsfi"
  search_button: $".FPdoLc.VlcLAe input[name=btnK]"

For the time being those selectors are a little hard on the eyes but at least we need to only update them in one place.

# examples/step/google-assert-open-literal.yml
assertions:
  - $page.url is "https://www.google.com"
  - $page.title is "Google"
# examples/test/google-import-assert-open-with-page-model.yml
config:
  browsers:
    - chrome
  url: google_com.url

imports:
  steps:
    google_assert_open: "../step/google-assert-open-literal.yml"
  pages:
    google_com: "../page/google.com.yml"

"verify Google is open":
  use: google_assert_open

"query 'example'":
  actions:
    - set $google_com.elements.search_input to "example"
    - click $google_com.elements.search_button

  assertions:
    - $page.title is "example - Google Search"

We’ve added a pages section to the imports section to reference the page model we created. We’ve chosen an import name (google_com) and provided an import path (page/google.com.yml).

Within our assertions, we use the name of the page import to reference the elements that the page model defines.

See how click $google_com.elements.search_button is a lot easier on the brain than click $".FPdoLc.VlcLAe input[name=btnK]"?

Scoping Elements Within a Page Model

Both the search input field and the search button are within a form. And both elements have some properties other than class names that are probably less prone to change.

We can make our page model more robust.

# examples/page/google.com-selector-scoped.yml
url: "https://www.google.com"
elements:
  # finds "[name=q]" within the context of "form[action='/search']"
  # using CSS syntax
  search_input: $"form[action='/search'] input[name=q]"

  # finds "[type=submit]" within the context of "form[action='/search']"
  # using CSS syntax
  search_button: $"form[action='/search'] input[type=submit]"

That makes my brain hurt just a little bit less. But we’re still repeating the form[action='/search'] part of each of the selectors. We can do better!

We have given the $"form[action=/search]" identifier the name search_form. We can reference this element via the name we chose by prefixing the name with a dollar sign. Within our page model, $search_form is the same as saying $"form[action=/search]".

Combine this with the >> operator for a parent-child relationship and everything is much easier to read.

# examples/page/google.com-element-scoped-reference.yml
url: "https://www.google.com"
elements:
  search_form: $"form[action=/search]"

  # finds "[name=q]" within the context of search_form
  # using basil parent-child syntax
  search_input: $search_form >> $"[name=q]"

  # finds "[type=submit]" within the context of search_form
  # using basil parent-child syntax
  search_button: $search_form >> $"[type=submit]"