Beginner’s guide to writing end-to-end tests

This tutorial walks you through the creation of end-to-end (e2e) tests forGitLab Community EditionandGitLab Enterprise Edition.

By the end of this tutorial, you can:

  • Determine whether an end-to-end test is needed.
  • Understand the directory structure withinqa/.
  • Write a basic end-to-end test that validates login features.
  • Develop any missingpage objectlibraries.

Before you write a test

Before you write tests, yourGitLab Development Kit (GDK)must be configured to run the specs. The end-to-end tests:

  • Are contained within theqa/directory.
  • Should be independent andidempotent.
  • Createresources(such as project, issue, user) on an ad-hoc basis.
  • Test the UI and API interfaces, and use the API to efficiently set up the UI tests.

Determine if end-to-end tests are needed

Check the code coverage of a specific feature before writing end-to-end tests for theGitLabproject. Does sufficient test coverage exist at the unit, feature, or integration levels? If you answeredyes,then youdon’tneed an end-to-end test.

For information about the distribution of tests per level in GitLab, seeTesting Levels.

  • See theHow to test at the correct level?section of theTesting levelsdocument.
  • Review how often the feature changes. Stable features that don’t change very often might not be worth covering with end-to-end tests if they are already covered in lower level tests.
  • Finally, discuss the proposed test with the developers involved in implementing the feature and the lower-level tests.
caution
Check theGitLabcoverage project for previously written tests for this feature. To analyze code coverage, you must understand which application files implement specific features.

In this tutorial we’re writing a login end-to-end test, even though it has been sufficiently covered by lower-level testing, because it’s the first step for most end-to-end flows, and is easiest to understand.

Identify the DevOps stage

The GitLab QA end-to-end tests are organized by the differentstages in the DevOps lifecycle.Determine where the test should be placed bystage,determine which feature the test belongs to, and then place it in a subdirectory under the stage.

DevOps lifecycle by stages

If the test is Enterprise Edition only, the test is created in thefeatures/eedirectory, but follow the same DevOps lifecycle format.

Create a skeleton test

In the first part of this tutorial we are testing login, which is owned by the Manage stage. Insideqa/specs/features/browser_ui/1_manage/login,create a filebasic_login_spec.rb.

The outercontextblock

See theRSpec.describeouter block

caution
The outercontextwas deprecatedin13.2in adherence to RSpec 4.0 specifications. UseRSpec.describeinstead.

The outerRSpec.describeblock

Specs have an outerRSpec.describeindicating the DevOps stage.

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do

end
end

Thedescribeblock

Inside of our outerRSpec.describe,describe the feature to test. In this case,Login.

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do
describe'Login'do

end
end
end

Theproduct_groupmetadata

Assignproduct_groupmetadata and specify what product group this test belongs to. In this case,authentication_and_authorization.

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do
describe'Login',product_group::authenticationdo

end
end
end

Theitblocks (examples)

Every test suite contains at least oneitblock (example). A good way to start writing end-to-end tests is to write test case descriptions asitblocks:

moduleQA
RSpec.describe'Manage'do
describe'Login',product_group::authenticationdo
it'can login'do

end

it'can logout'do

end
end
end
end

Write the test

An important question is “What do we test?” and even more importantly, “How do we test?”

Begin by logging in.

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do
describe'Login',product_group::authenticationdo
it'can login'do
Flow::Login.sign_in

end

it'can logout'do
Flow::Login.sign_in

end
end
end
end
note
For more information on Flows, seeFlows

Afterrunning the spec,our test should login and end; then we should answer the question “What do we test?”

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do
describe'Login',product_group::authenticationdo
it'can login'do
Flow::Login.sign_in

Page::Main::Menu.performdo|menu|
expect(menu).tobe_signed_in
end
end

it'can logout'do
Flow::Login.sign_in

Page::Main::Menu.performdo|menu|
menu.sign_out

expect(menu).not_tobe_signed_in
end
end
end
end
end

What do we test?

  1. Can we sign in?
  2. Can we sign out?

How do we test?

  1. Check if the user avatar appears in the left sidebar.
  2. Check if the user avatardoes notappear in the left sidebar.

Behind the scenes,be_signed_inis apredicate matcherthatimplements checking the user avatar.

De-duplicate your code

Refactor your test to use abeforeblock for test setup, since it’s duplicating a call tosign_in.

# frozen_string_literal: true

moduleQA
RSpec.describe'Manage'do
describe'Login',product_group::authenticationdo
beforedo
Flow::Login.sign_in
end

it'can login'do
Page::Main::Menu.performdo|menu|
expect(menu).tobe_signed_in
end
end

it'can logout'do
Page::Main::Menu.performdo|menu|
menu.sign_out

expect(menu).not_tobe_signed_in
end
end
end
end
end

Thebeforeblock is essentially abefore(:each)and is run before each example, ensuring we now sign in at the beginning of each test.

Test setup using resources and page objects

Next, let’s test something other than Login. Let’s test Issues, which are owned by the Plan stage and the Project Management Group, socreate a fileinqa/specs/features/browser_ui/2_plan/issuecalledissues_spec.rb.

# frozen_string_literal: true

moduleQA
RSpec.describe'Plan'do
describe'Issues',product_group::project_managementdo
let(:issue){create(:issue)}

beforedo
Flow::Login.sign_in
issue.visit!
end

it'can close an issue'do
Page::Project::Issue::Show.performdo|show|
show.click_close_issue_button

expect(show).tobe_closed
end
end
end
end
end

Note the following important points:

  • At the start of our example, we are at thepage/issue/show.rbpage.
  • Our test fabricates only what it needs, when it needs it.
  • The issue is fabricated through the API to save time.
  • GitLab preferslet()over instance variables. Seebest practices.
  • be_closedis not implemented inpage/project/issue/show.rbyet, but is implemented in the next step.

The issue is fabricated as aResource,which is a GitLab entity you can create through the UI or API. Other examples include:

Write the page object

APage Objectis a class in our suite that represents a page within GitLab. TheLoginpage would be one example. Since our page object for theIssue Showpage already exists, add theclosed?method.

modulePage::Project::Issue
classShow
view'app/views/projects/issues/show.html.haml'do
element'closed-status-box'
end

defclosed?
has_element?('closed-status-box')
end
end
end

Next, define the elementclosed-status-boxwithin your view, so your Page Object can see it.

-#=> app/views/projects/issues/show.html.haml
.issuable-status-box.status-box.status-box-issue-closed{...,data:{testid:'closed-status-box'}}

Run the spec

Before running the spec, make sure that:

  • GDK is installed.
  • GDK is running locally on port 3000.
  • No additionalRSpec metadata tagshave been applied.
  • Your working directory isqa/within your GDK GitLab installation.
  • Your GitLab instance-level settings are default. If you changed the default settings, some tests might have unexpected results.
  • Because the GDK requires a password change on first login, you must include the GDK password forrootuser

To run the spec, run the following command:

GITLAB_PASSWORD=<GDK root password> bundleexecrspec <test_file>

Where<test_file>is:

  • qa/specs/features/browser_ui/1_manage/login/log_in_spec.rbwhen running the Login example.
  • qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rbwhen running the Issue example.

Additional information on test execution and possible options are described in“QA framework README”

Preparing test for code review

Before submitting the test for code review, there are a few housecleaning tasks to do:

  1. Ensure that the test name follows the recommendednaming convention.
  2. Ensure that the spec islinked to a test case.
  3. Ensure that the spec has the correctproduct_groupmetadata. SeeProduct sections, stages, groups, and categoriesfor the comprehensive list of groups.
  4. Ensure that the relevantRSpec metadataare added to the spec.
  5. Ensure the page object elements are named according to therecommended naming convention.

End-to-end test merge request template

When submitting a new end-to-end test, use the“New End to End Test”merge request description template for additional steps that are required prior a successful merge.