Commit a7e16ee2 authored by Dan Davison's avatar Dan Davison

Add documentation surrounding [data-qa-selector]

Documentation was lacking for the [data-qa-selector]
method of defining methods vs .qa-selector method.
parent b921b2d1
...@@ -92,20 +92,25 @@ end ...@@ -92,20 +92,25 @@ end
The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements. The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements.
The `element` DSL method in turn declares an element for which a corresponding The `element` DSL method in turn declares an element for which a corresponding
`qa-element-name-dasherized` CSS class will need to be added to the view file. `data-qa-selector=element_name_snaked` data attribute will need to be added to the view file.
You can also define a value (String or Regexp) to match to the actual view You can also define a value (String or Regexp) to match to the actual view
code but **this is deprecated** in favor of the above method for two reasons: code but **this is deprecated** in favor of the above method for two reasons:
- Consistency: there is only one way to define an element - Consistency: there is only one way to define an element
- Separation of concerns: QA uses dedicated CSS classes instead of reusing code - Separation of concerns: QA uses dedicated `data-qa-*` attributes instead of reusing code
or classes used by other components (e.g. `js-*` classes etc.) or classes used by other components (e.g. `js-*` classes etc.)
```ruby ```ruby
view 'app/views/my/view.html.haml' do view 'app/views/my/view.html.haml' do
# Implicitly require `.qa-logout-button` CSS class to be present in the view
### Good ###
# Implicitly require the CSS selector `[data-qa-selector="logout_button"]` to be present in the view
element :logout_button element :logout_button
### Bad ###
## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop. ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Require `f.submit "Sign in"` to be present in `my/view.html.haml # Require `f.submit "Sign in"` to be present in `my/view.html.haml
element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
...@@ -129,24 +134,39 @@ view 'app/views/my/view.html.haml' do ...@@ -129,24 +134,39 @@ view 'app/views/my/view.html.haml' do
end end
``` ```
To add these elements to the view, you must change the rails View, partial, or vue component by adding a `qa-element-descriptor` class To add these elements to the view, you must change the rails View, partial, or vue component by adding a `data-qa-selector` attribute
for each element defined. for each element defined.
In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button` In our case, `data-qa-selector="login_field"`, `data-qa-selector="password_field"` and `data-qa-selector="sign_in_button"`
**app/views/my/view.html.haml** **app/views/my/view.html.haml**
```haml ```haml
= f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required.", data: { qa_selector: 'login_field' }
= f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required." = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
= f.submit "Sign in", class: "btn btn-success qa-sign-in-button" = f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' }
``` ```
Things to note: Things to note:
- The CSS class must be `kebab-cased` (separated with hyphens "`-`") - The name of the element and the qa_selector must match and be snake_cased
- If the element appears on the page unconditionally, add `required: true` to the element. See - If the element appears on the page unconditionally, add `required: true` to the element. See
[Dynamic element validation](dynamic_element_validation.md) [Dynamic element validation](dynamic_element_validation.md)
- You may see `.qa-selector` classes in existing Page Objects. We should prefer the [`data-qa-selector`](#data-qa-selector-vs-qa-selector)
method of definition over the `.qa-selector` CSS class
### `data-qa-selector` vs `.qa-selector`
> Introduced in GitLab 12.1
There are two supported methods of defining elements within a view.
1. `data-qa-selector` attribute
1. `.qa-selector` class
Any existing `.qa-selector` class should be considered deprecated
and we should prefer the `data-qa-selector` method of definition.
## Running the test locally ## Running the test locally
......
...@@ -101,7 +101,7 @@ it 'replaces an existing label if it has the same key' do ...@@ -101,7 +101,7 @@ it 'replaces an existing label if it has the same key' do
page.find('#content-body').click page.find('#content-body').click
page.refresh page.refresh
labels_block = page.find('.qa-labels-block') labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content('animal::dolphin') expect(labels_block).to have_content('animal::dolphin')
expect(labels_block).not_to have_content('animal::fox') expect(labels_block).not_to have_content('animal::fox')
...@@ -130,7 +130,7 @@ it 'keeps both scoped labels when adding a label with a different key' do ...@@ -130,7 +130,7 @@ it 'keeps both scoped labels when adding a label with a different key' do
page.find('#content-body').click page.find('#content-body').click
page.refresh page.refresh
labels_block = page.find('.qa-labels-block') labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content('animal::fox') expect(labels_block).to have_content('animal::fox')
expect(labels_block).to have_content('plant::orchid') expect(labels_block).to have_content('plant::orchid')
...@@ -139,7 +139,7 @@ it 'keeps both scoped labels when adding a label with a different key' do ...@@ -139,7 +139,7 @@ it 'keeps both scoped labels when adding a label with a different key' do
end end
``` ```
> Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called adding testability to the application and we will talk more about it later.) For example, the `labels_block` element uses the selector `.qa-labels-block`, which was added specifically for testing purposes. > Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called "testability"). For example, the `labels_block` element uses the CSS selector [`data-qa-selector="labels_block"`](page_objects.md#data-qa-selector-vs-qa-selector), which was added specifically for testing purposes.
Below are the steps that the test covers: Below are the steps that the test covers:
...@@ -168,7 +168,7 @@ end ...@@ -168,7 +168,7 @@ end
it 'replaces an existing label if it has the same key' do it 'replaces an existing label if it has the same key' do
select_label_and_refresh @new_label_same_scope select_label_and_refresh @new_label_same_scope
labels_block = page.find('.qa-labels-block') labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content(@new_label_same_scope) expect(labels_block).to have_content(@new_label_same_scope)
expect(labels_block).not_to have_content(@initial_label) expect(labels_block).not_to have_content(@initial_label)
...@@ -179,7 +179,7 @@ end ...@@ -179,7 +179,7 @@ end
it 'keeps both scoped label when adding a label with a different key' do it 'keeps both scoped label when adding a label with a different key' do
select_label_and_refresh @new_label_different_scope select_label_and_refresh @new_label_different_scope
labels_block = page.find('.qa-labels-block') labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_blocks).to have_content(@new_label_different_scope) expect(labels_blocks).to have_content(@new_label_different_scope)
expect(labels_blocks).to have_content(@initial_label) expect(labels_blocks).to have_content(@initial_label)
...@@ -305,7 +305,7 @@ module QA ...@@ -305,7 +305,7 @@ module QA
it 'correctly applies scoped labels depending on if they are from the same or a different scope' do it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope] select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
labels_block = page.all('.qa-labels-block') labels_block = page.all(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content(@new_label_same_scope) expect(labels_block).to have_content(@new_label_same_scope)
expect(labels_block).to have_content(@new_label_different_scope) expect(labels_block).to have_content(@new_label_different_scope)
...@@ -552,37 +552,36 @@ The `text_of_labels_block` method is a simple method that returns the `:labels_b ...@@ -552,37 +552,36 @@ The `text_of_labels_block` method is a simple method that returns the `:labels_b
#### Updates in the view (*.html.haml) and `dropdowns_helper.rb` files #### Updates in the view (*.html.haml) and `dropdowns_helper.rb` files
Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the Page Object. Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the [Page Objects].
In the [app/views/shared/issuable/_sidebar.html.haml](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/views/shared/issuable/_sidebar.html.haml) file, on [line 105 ](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L105), add an extra class `qa-edit-link-labels`. In [`app/views/shared/issuable/_sidebar.html.haml:105`](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/views/shared/issuable/_sidebar.html.haml#L105), add a `data: { qa_selector: 'edit_link_labels' }` data attribute.
The code should look like this: The code should look like this:
```haml ```haml
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right qa-edit-link-labels' = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: 'edit_link_labels' }
``` ```
In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L121), add an extra class `.qa-dropdown-menu-labels`. In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/views/shared/issuable/_sidebar.html.haml#L121), add a `data: { qa_selector: 'dropdown_menu_labels' }` data attribute.
The code should look like this: The code should look like this:
```haml ```haml
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.qa-dropdown-menu-labels .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height{ data: { qa_selector: 'dropdown_menu_labels' } }
``` ```
In the [`dropdowns_helper.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/helpers/dropdowns_helper.rb) file, on [line 94](https://gitlab.com/gitlab-org/gitlab-ee/blob/99e51a374f2c20bee0989cac802e4b5621f72714/app/helpers/dropdowns_helper.rb#L94), add an extra class `qa-dropdown-input-field`. In [`app/helpers/dropdowns_helper.rb:94`](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/helpers/dropdowns_helper.rb#L94), add a `data: { qa_selector: 'dropdown_input_field' }` data attribute.
The code should look like this: The code should look like this:
```ruby ```ruby
filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off' filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off', data: { qa_selector: 'dropdown_input_field' }
``` ```
> Classes starting with `qa-` are used for testing purposes only, and by defining such classes in the elements we add **testability** in the application. > `data-qa-*` data attributes and CSS classes starting with `qa-` are used solely for the purpose of QA and testing.
> By defining these, we add **testability** to the application.
> When defining a class like `qa-labels-block`, it is transformed into `:labels_block` for usage in the Page Objects. So, `qa-edit-link-labels` is transformed into `:edit_link_labels`, `qa-dropdown-menu-labels` is transformed into `:dropdown_menu_labels`, and `qa-dropdown-input-field` is transformed into `:dropdown_input_field`. Also, we use a [sanity test](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page#how-did-we-solve-fragile-tests-problem) to check that defined elements have their respective `qa-` selectors in the specified views. > When defining a data attribute like: `qa_selector: 'labels_block'`, it should match the element definition: `element :labels_block`. We use a [sanity test](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page#how-did-we-solve-fragile-tests-problem) to check that defined elements have their respective selectors in the specified views.
> We did not define the `qa-labels-block` class in the `app/views/shared/issuable/_sidebar.html.haml` file because it was already there to be used.
#### Updates in the `QA::Page::Base` class #### Updates in the `QA::Page::Base` class
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment