Skip to content

Commit fdaf695

Browse files
Chore: [AEA-6406] - timeout modal sync test (#712)
## Summary https://nhsd-jira.digital.nhs.uk/browse/AEA-6406 - Routine Change ### Details Changing the way we handle the timeout modal being counted, now using the server time rather than froint end timing which can be affected by browser throttling when the tab is minimised --------- Co-authored-by: Connor Avery <connor.avery2@nhs.net>
1 parent 0f5fdde commit fdaf695

2 files changed

Lines changed: 74 additions & 32 deletions

File tree

features/cpts_ui/timeout_sessions.feature

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,15 @@ Feature: Timedout session protections prohibit activity
99
When I set lastActivityTime to be 13 minutes ago
1010
And I fast forward 1 minute so that updateTracker event happens
1111
Then I should see the timeout session modal
12-
When I fast forward 3 minutes so that updateTracker event happens
12+
When I fast forward 2 minutes so that updateTracker event happens
1313
Then I am redirected to the logged out for inactivity page
14+
15+
@allure.tms:https://nhsd-jira.digital.nhs.uk/browse/AEA-6406
16+
@single_access @fake_time
17+
Scenario: Timeout modal appears correctly when browser was minimized during inactive period
18+
Given I am logged in as a user with a single access role
19+
And I am on the search for a prescription page
20+
When I minimise the browser window
21+
And I set lastActivityTime to be 13 minutes ago
22+
And I fast forward 1 minute so that updateTracker event happens
23+
Then I should see the timeout session modal

features/steps/cpts_ui/session_timeout_steps.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from pages.session_logged_out import SessionLoggedOutPage
1010
from pages.session_timeout_modal import SessionTimeoutModal
1111

12+
FAKE_TIME_TAG_ERROR = "Fake_time tag required in this scenario"
13+
1214

1315
@when("the session expires because of automatic timeout")
1416
def clear_active_session(context):
@@ -26,14 +28,19 @@ def verify_timeout_logged_out_page(context):
2628
expect(logged_out_page.timeout_description2).to_be_visible()
2729

2830

31+
@when("I minimise the browser window")
32+
def minimise_browser_window(context):
33+
context.active_page.evaluate("() => window.blur()")
34+
context.active_page.evaluate("() => document.dispatchEvent(new Event('visibilitychange'))")
35+
36+
2937
@when("I set lastActivityTime to be 13 minutes ago")
3038
def set_last_activity_time_13_minutes_ago(context):
3139
"""Call the test support endpoint to set lastActivityTime to 13 minutes in the past"""
3240
# pylint: disable=broad-exception-raised
3341
if "fake_time" not in context.tags:
34-
raise Exception("Fake_time tag required in this scenario. See README.md")
42+
raise ValueError(FAKE_TIME_TAG_ERROR)
3543

36-
# Wait a moment to ensure session is established after login
3744
context.active_page.wait_for_timeout(3000)
3845

3946
# Determine username based on scenario tags
@@ -53,14 +60,12 @@ def set_last_activity_time_13_minutes_ago(context):
5360
break
5461

5562
if not username:
56-
raise Exception("No valid account tag found for setting lastActivityTime")
63+
raise ValueError("No valid account tag found for setting lastActivityTime")
5764

5865
request_id = str(uuid.uuid4())
59-
print(f"Setting lastActivityTime to 13 minutes ago for {username}. Request ID: {request_id}")
6066

6167
payload = json.dumps({"username": username, "request_id": request_id})
6268

63-
# Call the lambda endpoint to set lastActivityTime to 13 minutes in the past
6469
response = requests.post(
6570
f"{context.cpts_ui_base_url}/api/test-support-fake-timer",
6671
data=payload,
@@ -73,9 +78,8 @@ def set_last_activity_time_13_minutes_ago(context):
7378

7479
if response.status_code != 200:
7580
print(f"Failed to set lastActivityTime. Response: {response.status_code} - {response.text}")
76-
raise Exception(f"Failed to set lastActivityTime: {response.status_code}")
81+
response.raise_for_status()
7782

78-
# fast forward clock by 13 minutes to make frontend think time has passed
7983
context.active_page.clock.fast_forward(13 * 60 * 1000)
8084

8185

@@ -84,48 +88,76 @@ def fast_forward_1_minute(context):
8488
"""Fast forward 1 minute to trigger the updateTracker periodic check"""
8589
# pylint: disable=broad-exception-raised
8690
if "fake_time" not in context.tags:
87-
raise Exception("Fake_time tag required in this scenario. See README.md")
91+
raise ValueError(FAKE_TIME_TAG_ERROR)
8892

89-
# Fast forward by 1 minute to trigger the 60-second interval
9093
context.active_page.clock.fast_forward(60 * 1000)
9194

92-
# Give a moment for the periodic check to execute and React to update
9395
context.active_page.wait_for_timeout(2000)
9496

9597

9698
@then("I should see the timeout session modal")
9799
def verify_timeout_session_modal(context):
98100
"""Verify the timeout session modal is displayed"""
99101
modal = SessionTimeoutModal(context.active_page)
100-
101102
context.active_page.wait_for_timeout(3000)
102103

103-
expect(modal.modal_container).to_be_visible(timeout=15000)
104-
expect(modal.stay_logged_in_button).to_be_visible(timeout=5000)
105-
expect(modal.logout_button).to_be_visible(timeout=5000)
106-
107-
108-
@when("I fast forward 3 minutes so that updateTracker event happens")
109-
def fast_forward_3_minutes(context):
104+
try:
105+
expect(modal.modal_container).to_be_visible(timeout=15000)
106+
expect(modal.stay_logged_in_button).to_be_visible(timeout=5000)
107+
expect(modal.logout_button).to_be_visible(timeout=5000)
108+
except (TimeoutError, AssertionError) as e:
109+
print(f"Modal detection failed: {e}")
110+
page_content = context.active_page.content()
111+
print(f"Page contains 'session-timeout-modal': {'session-timeout-modal' in page_content}")
112+
print(f"Page contains 'For your security': {'For your security' in page_content}")
113+
raise AssertionError("Timeout session modal was not found")
114+
115+
116+
def _capture_countdown_with_fallback(modal, page, description):
117+
try:
118+
countdown = modal.countdown_time.text_content(timeout=2000)
119+
return countdown
120+
except (TimeoutError, AttributeError) as e:
121+
print(f"Could not read countdown {description.lower()}: {e}")
122+
page_text = page.text_content("body")
123+
import re
124+
125+
countdown_match = re.search(r"sign you out in (\d+) seconds?", page_text)
126+
if countdown_match:
127+
countdown = f"{countdown_match.group(1)} seconds"
128+
return countdown
129+
return None
130+
131+
132+
@when("I fast forward 2 minutes so that updateTracker event happens")
133+
def fast_forward_2_minutes(context):
110134
"""Wait 2 minutes for natural session timeout"""
111135
# pylint: disable=broad-exception-raised
112136
if "fake_time" not in context.tags:
113-
raise Exception("Fake_time tag required in this scenario. See README.md")
137+
raise ValueError(FAKE_TIME_TAG_ERROR)
114138

115-
# Wait 2 minutes in real time for natural timeout
116-
context.active_page.wait_for_timeout(120000)
139+
modal = SessionTimeoutModal(context.active_page)
117140

141+
context.active_page.wait_for_timeout(120000)
118142

119-
@then("I am redirected to the logged out for inactivity page")
120-
def verify_timed_out_session_and_logged_out_page(context):
121-
"""Verify that the session has timed out and user is redirected to session-logged-out URL"""
143+
expected_logout_path = "session-logged-out"
144+
current_url = context.active_page.url
145+
if expected_logout_path in current_url:
146+
return
122147

123-
# Wait for navigation to complete
124-
context.active_page.wait_for_load_state("networkidle", timeout=10000)
148+
countdown_after = _capture_countdown_with_fallback(modal, context.active_page, "AFTER 2min wait")
149+
if not countdown_after:
150+
expected_logout_paths = ["session-logged-out", "logout"]
151+
current_url = context.active_page.url
152+
if any(path in current_url for path in expected_logout_paths):
153+
return
125154

126-
# Get current URL and verify it contains session-logged-out
127-
current_url = context.active_page.url
128155

129-
assert (
130-
"/session-logged-out" in current_url
131-
), f"Expected URL to contain '/session-logged-out', but got: {current_url}"
156+
@then("I am redirected to the logged out for inactivity page")
157+
def verify_timed_out_session_and_logged_out_page(context):
158+
"""Verify that the session has timed out and user is on the logged out page"""
159+
logged_out_page = SessionLoggedOutPage(context.active_page)
160+
expect(logged_out_page.timeout_session_container).to_be_visible()
161+
expect(logged_out_page.timeout_title).to_have_text("For your security, we have logged you out")
162+
expect(logged_out_page.timeout_description).to_be_visible()
163+
expect(logged_out_page.timeout_description2).to_be_visible()

0 commit comments

Comments
 (0)