Skip to content

Commit ab033d1

Browse files
authored
New: [AEA-4809] - Add logout user flow (#214)
## Summary - ✨ New Feature ### Details Adds regression tests for NHSDigital/eps-prescription-tracker-ui#300
1 parent 23daf8b commit ab033d1

File tree

9 files changed

+253
-14
lines changed

9 files changed

+253
-14
lines changed

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ install-python:
2424
install-hooks: install-python
2525
poetry run pre-commit install --install-hooks --overwrite
2626

27+
install-playwright:
28+
playwright install
29+
playwright install-deps
30+
playwright install --force chrome
31+
2732
install-node:
2833
npm ci
2934

@@ -40,7 +45,7 @@ lint: lint-black lint-pyright lint-flake8
4045

4146
run-tests: guard-product guard-env
4247
echo "Running Regression Tests"
43-
poetry run python ./runner.py --product=$(product) --env=$(env) --tags=${tags}
48+
poetry run python ./runner.py --product=$(product) --env=$(env) --tags=$(tags)
4449

4550
check-licenses:
4651
scripts/check_python_licenses.sh

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ These tests will automate End-to-End regression testing for:
55
* [Electronic Prescription Service (EPS-FHIR)](https://digital.nhs.uk/developer/api-catalogue/electronic-prescription-service-fhir)
66
* [Prescriptions for Patients (PfP)](https://digital.nhs.uk/developer/api-catalogue/prescriptions-for-patients)
77
* [Prescription Status Update (PSU)](https://digital.nhs.uk/developer/api-catalogue/prescription-status-update-fhir/)
8-
*
8+
* [Clinical Prescription Tracker UI (CPT-UI)](https://github.com/NHSDigital/eps-prescription-tracker-ui)
99

1010
## General usage
1111
These tests are run automatically during deployment and shouldn't need to be touched unless performing debugging or
1212
adding/removing/changing test cases <br />
1313
If there are any test failures, this will report a failed build
14+
15+
When developing new features that need to be regression tested, you'll need to create a new PR for them on this repository. When you are happy with the tests and the feature, merge the regression tests first. This will create a new tagged release, which you should then reference in the counterpart feature pull request before merging the code.
16+
1417
## Setup
1518

1619
### Environment Variables
@@ -65,8 +68,14 @@ If your IDE supports it, you can directly run the .feature files within `/featur
6568
Make sure that your behave run configuration includes the `--product=` & `--env=` <B>These are mandatory</B>
6669

6770
### Method 3:
68-
Run the tests by calling the Make command `make run-tests`. This requires the parameters `product=` and `env=` to be passed in
69-
* This will run the tests without tags so will run everything
71+
Run the tests by calling the Make command `make run-tests`. This requires the parameters `product=` and `env=` to be passed in.
72+
Optionally, you can pass in tags to be run, for example `tags=cpt-ui` will run all CPT-UI-tagged tests.
73+
Further, if you want to actually see the tests being executed, you can pass a `HEADLESS=true` to the makefile.
74+
75+
For example:
76+
```
77+
product=cpts-ui env=internal-dev PULL_REQUEST_ID=pr-300 tags=cpt-ui HEADLESS=true make run-tests
78+
```
7079

7180
### Method 4 (Not Recommended):
7281
Run the tests by running `behave` in a command prompt or terminal window.

features/cpts_ui/logout.feature

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
@cpts_ui @logout @regression @blocker @smoke @ui
2+
@allure.tms:https://nhsd-jira.digital.nhs.uk/browse/AEA-4809
3+
Feature: The user is able to logout of the application
4+
5+
Background:
6+
Given I am logged in
7+
And I am on the select your role page
8+
9+
############################################################################
10+
# Logging out
11+
############################################################################
12+
Scenario: Display logout modal when user clicks logout
13+
When I click the logout button
14+
Then I see the logout confirmation modal
15+
16+
Scenario: User confirms logout
17+
Given the logout confirmation modal is displayed
18+
When I confirm the logout
19+
Then I see the logout successful page
20+
21+
Scenario: User can log back in from the logout successful page
22+
Given I am on the logout successful page
23+
When I click the "log back in" button
24+
Then I am on the login page
25+
26+
############################################################################
27+
# Closing the logout modal
28+
############################################################################
29+
Scenario: Close the modal with the cross icon
30+
Given the logout confirmation modal is displayed
31+
When I close the modal with the cross
32+
Then the logout confirmation modal is not displayed
33+
34+
Scenario: Close the modal with the cancel button
35+
Given the logout confirmation modal is displayed
36+
When I close the modal with the cancel button
37+
Then the logout confirmation modal is not displayed
38+
39+
Scenario: Close the modal by clicking outside the modal
40+
Given the logout confirmation modal is displayed
41+
When I close the modal by clicking outside the modal
42+
Then the logout confirmation modal is not displayed
43+
44+
Scenario: Close the modal by pressing the escape key
45+
Given the logout confirmation modal is displayed
46+
When I close the modal by hitting escape
47+
Then the logout confirmation modal is not displayed

features/environment.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
PULL_REQUEST_ID = os.getenv("PULL_REQUEST_ID")
7272
JWT_PRIVATE_KEY = os.getenv("JWT_PRIVATE_KEY")
7373
JWT_KID = os.getenv("JWT_KID")
74+
HEADLESS = os.getenv("HEADLESS", "True").lower() in ("true", "1", "yes")
7475

7576
CPTS_UI_PREFIX = "cpt-ui"
7677
EPS_FHIR_SUFFIX = "electronic-prescriptions"
@@ -131,7 +132,9 @@ def before_all(context):
131132
if product == "CPTS-UI":
132133
global _page
133134
playwright = sync_playwright().start()
134-
context.browser = playwright.chromium.launch(headless=True, channel="chrome")
135+
context.browser = playwright.chromium.launch(
136+
headless=HEADLESS, channel="chrome"
137+
)
135138

136139
eps_api_methods.calculate_eps_fhir_base_url(context)
137140
print("CPTS-UI: ", context.cpts_ui_base_url)

features/steps/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from cpts_ui.common_steps import * # noqa: F403,F401
12
from cpts_ui.search_for_a_prescription_steps import * # noqa: F403,F401
23
from cpts_ui.home_steps import * # noqa: F403,F401
34
from cpts_ui.select_your_role_steps import * # noqa: F403,F401
5+
from cpts_ui.logout_steps import * # noqa: F403,F401
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# pylint: disable=no-name-in-module
2+
from behave import given, then # pyright: ignore [reportAttributeAccessIssue]
3+
4+
from features.environment import MOCK_CIS2_LOGIN_ID_MULTIPLE_ACCESS_ROLES
5+
6+
7+
@then("I am logged out")
8+
def i_am_logged_out(context):
9+
# No cookies with names starting with "CognitoIdentityServiceProvider" should be present
10+
cookies = context.page.context.cookies()
11+
cognito_cookies = [
12+
cookie
13+
for cookie in cookies
14+
if cookie["name"].startswith("CognitoIdentityServiceProvider")
15+
]
16+
assert len(cognito_cookies) == 0
17+
18+
19+
@given("I am logged in")
20+
def login(context):
21+
# TODO: This /site/ is not generic. Also, the .html will need to be removed when the SPA is fixed
22+
context.page.goto(context.cpts_ui_base_url + "site/login.html")
23+
context.page.get_by_role("button", name="Log in with mock CIS2").click()
24+
context.page.get_by_label("Username").fill(MOCK_CIS2_LOGIN_ID_MULTIPLE_ACCESS_ROLES)
25+
context.page.get_by_role("button", name="Sign In").click()
26+
context.page.wait_for_url("**/selectyourrole.html")
27+
28+
# There should be cookies with names starting with "CognitoIdentityServiceProvider"
29+
cookies = context.page.context.cookies()
30+
cognito_cookies = [
31+
cookie
32+
for cookie in cookies
33+
if cookie["name"].startswith("CognitoIdentityServiceProvider")
34+
]
35+
assert len(cognito_cookies) > 0
36+
37+
38+
@then("I am on the login page")
39+
def i_am_on_login_page(context):
40+
# TODO: This needs to cover the .html for the broken SPA. Will need to be removed.
41+
assert context.page.url == context.cpts_ui_base_url + "site/login"
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# pylint: disable=no-name-in-module
2+
from behave import given, when, then # pyright: ignore [reportAttributeAccessIssue]
3+
from playwright.sync_api import expect
4+
5+
from pages.logout import Logout
6+
7+
8+
###############################################################################
9+
# GIVEN
10+
###############################################################################
11+
12+
13+
@given("the logout confirmation modal is displayed")
14+
def given_logout_modal_is_displayed(context):
15+
logout_page = Logout(context.page)
16+
logout_page.logout_modal_header_link.click()
17+
expect(logout_page.logout_modal_content).to_be_visible()
18+
19+
20+
@given("I am on the logout successful page")
21+
def given_on_logout_successful_page(context):
22+
context.execute_steps("Given the logout confirmation modal is displayed")
23+
context.execute_steps("When I confirm the logout")
24+
25+
logout_page = Logout(context.page)
26+
27+
expect(logout_page.logout_page_heading).to_be_visible()
28+
expect(logout_page.logout_page_content).to_be_visible()
29+
expect(logout_page.logout_page_login_link).to_be_visible()
30+
31+
32+
###############################################################################
33+
# WHEN
34+
###############################################################################
35+
36+
37+
@when("I click the logout button")
38+
def when_i_click_logout_button(context):
39+
logout_page = Logout(context.page)
40+
logout_page.logout_modal_header_link.click()
41+
42+
43+
@when("I confirm the logout")
44+
def when_i_confirm_the_logout(context):
45+
logout_page = Logout(context.page)
46+
logout_page.logout_modal_logout_button.click()
47+
48+
49+
@when('I click the "log back in" button')
50+
def when_i_click_log_back_in_button(context):
51+
logout_page = Logout(context.page)
52+
logout_page.logout_page_login_link.click()
53+
54+
55+
@when("I close the modal with the cross")
56+
def when_i_close_modal_with_cross(context):
57+
logout_page = Logout(context.page)
58+
logout_page.logout_modal_close_button.click()
59+
60+
61+
@when("I close the modal with the cancel button")
62+
def when_i_close_modal_with_cancel_button(context):
63+
logout_page = Logout(context.page)
64+
logout_page.logout_modal_cancel_button.click()
65+
66+
67+
@when("I close the modal by clicking outside the modal")
68+
def when_i_close_modal_with_overlay(context):
69+
logout_page = Logout(context.page)
70+
logout_page.logout_modal_overlay.click(force=True, position={"x": 0, "y": 0})
71+
72+
73+
@when("I close the modal by hitting escape")
74+
def when_i_close_modal_by_hitting_escape(context):
75+
logout_page = Logout(context.page)
76+
logout_page.page.keyboard.press("Escape")
77+
78+
79+
###############################################################################
80+
# THEN STEPS
81+
###############################################################################
82+
83+
84+
@then("I see the logout confirmation modal")
85+
def then_i_see_logout_confirmation_modal(context):
86+
logout_page = Logout(context.page)
87+
expect(logout_page.logout_modal_content.get_by_role("heading")).to_contain_text(
88+
"Are you sure you want to log out?"
89+
)
90+
expect(logout_page.logout_modal_content.get_by_role("paragraph")).to_contain_text(
91+
"Logging out will end your session."
92+
)
93+
94+
95+
@then("the logout confirmation modal is not displayed")
96+
def then_logout_confirmation_modal_not_displayed(context):
97+
logout_page = Logout(context.page)
98+
expect(logout_page.logout_modal_content).not_to_be_visible()
99+
100+
101+
@then("I see the logout successful page")
102+
def then_i_see_logout_successful_page(context):
103+
logout_page = Logout(context.page)
104+
expect(logout_page.logout_page_heading).to_be_visible()
105+
expect(logout_page.logout_page_content).to_be_visible()

features/steps/cpts_ui/select_your_role_steps.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
from playwright.sync_api import expect
44

55
from pages.select_your_role import SelectYourRole
6-
from features.environment import MOCK_CIS2_LOGIN_ID_MULTIPLE_ACCESS_ROLES
76

87

9-
@given("I am logged in")
10-
def login(context):
11-
context.page.goto(context.cpts_ui_base_url + "site/auth_demo.html")
12-
context.page.get_by_role("button", name="Log in with mock CIS2").click()
13-
context.page.get_by_label("Username").fill(MOCK_CIS2_LOGIN_ID_MULTIPLE_ACCESS_ROLES)
14-
context.page.get_by_role("button", name="Sign In").click()
15-
context.page.wait_for_url("**/selectyourrole.html")
8+
@when("I go to the select your role page")
9+
def i_go_to_the_select_your_role_page(context):
10+
# TODO: This site/*.html is not generic. Also, the .html will need to be removed when the SPA is fixed
11+
context.page.goto(context.cpts_ui_base_url + "site/selectyourrole.html")
12+
13+
14+
@given("I am on the select your role page")
15+
def i_am_on_select_your_role_page(context):
16+
i_go_to_the_select_your_role_page(context)
17+
select_your_role_page = SelectYourRole(context.page)
18+
expect(select_your_role_page.summary).to_be_visible()
1619

1720

1821
@then("I am on the select your role page")
19-
def i_am_on_the_select_your_role_page(context):
22+
def verify_on_select_your_role_page(context):
2023
select_your_role_page = SelectYourRole(context.page)
2124
expect(select_your_role_page.summary).to_be_visible()
2225

pages/logout.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from playwright.sync_api import Page
2+
3+
4+
class Logout:
5+
def __init__(self, page: Page):
6+
"""
7+
Initialise the Logout page object with locators and text constants.
8+
"""
9+
self.page = page
10+
11+
# The header link should always be visible
12+
self.logout_modal_header_link = page.get_by_test_id("eps_header_logout")
13+
14+
# Modal elements
15+
self.logout_modal_logout_button = page.get_by_role("button", name="Log out")
16+
self.logout_modal_cancel_button = page.get_by_role("button", name="Cancel")
17+
self.logout_modal_close_button = page.get_by_label("Close modal")
18+
self.logout_modal_overlay = page.get_by_test_id("eps-modal-overlay")
19+
self.logout_modal_content = page.get_by_test_id("eps-modal-content")
20+
21+
# Logout successful page elements
22+
self.logout_page_heading = page.get_by_role("heading", name="Logout successful")
23+
self.logout_page_content = page.get_by_text("You are now logged out of the")
24+
self.logout_page_login_link = page.get_by_role("link", name="Log in")

0 commit comments

Comments
 (0)