Skip to content

Commit 330f44c

Browse files
committed
Merge remote-tracking branch 'origin' into feature/BCSS-20606-new-regression-release-seek-further-data
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
2 parents cd0b962 + 36ba642 commit 330f44c

File tree

255 files changed

+25632
-2575
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

255 files changed

+25632
-2575
lines changed

.gitleaksignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,9 @@ cd9c0efec38c5d63053dd865e5d4e207c0760d91:docs/guides/Perform_static_analysis.md:
221221
751ddbd178e91293c20282df62e8d3fe1086310a:utils/resources/axe.js:ipv4:32080
222222
751ddbd178e91293c20282df62e8d3fe1086310a:utils/resources/axe.js:ipv4:32089
223223
751ddbd178e91293c20282df62e8d3fe1086310a:utils/resources/axe.js:ipv4:32103
224+
cae84544e2852202f5d0abb9ad18f9d83a0c6d80:subject_criteria_builder/criteria.json:generic-api-key:4210
225+
203cd8811be75d33859eb7c7cc8a48beb5a2b8b2:subject_criteria_builder/criteria.json:generic-api-key:4210
226+
60468a7d6f3ff3259616757be85d6f07e62d00b6:subject_criteria_builder/criteria.json:generic-api-key:4210
227+
145171b9e9d169acb136fc527708c997c9ad9f61:subject_criteria_builder/criteria.json:generic-api-key:4210
228+
2ccb5e91033934d4818c1efa2eb1696b5511ebc9:subject_criteria_builder/criteria.json:generic-api-key:4210
229+
145171b9e9d169acb136fc527708c997c9ad9f61:subject_criteria_builder/criteria.json:generic-api-key:4210

Makefile

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,22 @@ include scripts/init.mk
55

66
# ==============================================================================
77

8-
# NOTE: This project currently only uses this Makefile as part of CI/CD checks.
8+
# This allows the setup of Playwright by using a single command ready to use, and checks
9+
# if the user is in a virtual environment before running the setup.
10+
11+
.PHONY: check-venv
12+
check-venv: # Checks if in a Python venv / VIRTUALENV before installing a bunch of dependencies
13+
@python -c "import sys, os; \
14+
venv = (hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix) or 'VIRTUAL_ENV' in os.environ); \
15+
import sys; sys.exit(0 if venv else 1)"
16+
17+
setup-playwright: # Install Playwright and associated packages, and create local.env
18+
@echo "Checking for virtual environment..."
19+
$(MAKE) check-venv || (echo "ERROR: Not in a virtual environment, please create before running this command!"; exit 1)
20+
21+
@echo "Install Playwright"
22+
pip install -r requirements.txt
23+
playwright install
24+
25+
@echo "Setup local.env file"
26+
python setup_env_file.py

README.md

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# BCSS Playwright Test Suite
22

3-
[![CI/CD Pull Request](https://github.com/nhs-england-tools/repository-template/actions/workflows/cicd-1-pull-request.yaml/badge.svg)](https://github.com/nhs-england-tools/playwright-python-blueprint/actions/workflows/cicd-1-pull-request.yaml)
3+
[![CI/CD Pull Request](https://github.com/nhs-england-tools/playwright-python-blueprint/actions/workflows/cicd-1-pull-request.yaml/badge.svg)](https://github.com/nhs-england-tools/playwright-python-blueprint/actions/workflows/cicd-1-pull-request.yaml)
44

55
This repository contains the automated UI test suite for the BCSS application, built using [Playwright Python](https://playwright.dev/python/). It provides a structured framework and reusable utilities to support consistent, maintainable test development across the project.
66

@@ -48,7 +48,8 @@ Note: This project is actively maintained and evolving.
4848
- [3. Best Practices](#3-best-practices)
4949
- [4. Blueprint Utilities](#4-blueprint-utilities)
5050
- [5. BCSS Project Specific Utilities](#5-bcss-project-specific-utilities)
51-
- [Contributing](#contributing)
51+
- [Using the Jira Upload Script](#using-the-jira-upload-script)
52+
- [Contributing](#contributing)
5253
- [Contacts](#contacts)
5354
- [Licence](#licence)
5455

@@ -379,7 +380,57 @@ These utilities have been created specifically for the bcss playwright project:
379380
| [Table Utility](.docs/utility-guides/TableUtil.md) | Provides helper methods for interacting with HTML tables, including row selection, column indexing, and data extraction. |
380381
| [User Tools](.docs/utility-guides/UserTools.md) | Manages test user credentials via `users.json`, supports login automation, and retrieves user metadata for role-based testing. |
381382

382-
### Contributing
383+
## Using the Jira Upload Script
384+
385+
Included with this code is a Jira Upload utility that will allow for the uploading of artifacts from test runs to
386+
a Jira ticket directly. The script itself ([`jira_upload.py`](jira_upload.py)) can be invoked using the following
387+
command:
388+
389+
```shell
390+
python jira_upload.py
391+
```
392+
393+
For this to work, you need to set the follow environment variables (which you can do via local.env):
394+
395+
| Key | Required | Description |
396+
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
397+
| `JIRA_URL` | Yes | The Jira instance url to connect to |
398+
| `JIRA_PROJECT_KEY` | Yes | The project key for the Jira project to upload to |
399+
| `JIRA_API_KEY` | Yes | The Jira API key for your user, which can be generated in Jira via Profile > Personal Access Tokens |
400+
| `JIRA_TICKET_REFERENCE` | No | The Jira ticket you want to default to, if required. Can be left blank to use branch-based referencing |
401+
402+
This command will do the following actions:
403+
404+
1. Work out the Jira ticket to upload to and confirm it is a valid reference, by using the following logic:
405+
1. If a `--jira-ref` value has been provided, use that value.
406+
2. If a `JIRA_TICKET_REFERENCE` environment variable exists, use that value.
407+
3. If none of the above, check if you are in a feature branch and if so, compiles the Jira ticket reference by combining the project key and the end of the feature branch (when in the format `feature/<shortcode>-<jira_ticket_number>`).
408+
2. Check the `test-results/` directory (or custom directory if specified) for appropriate files under 10MB (Jira's file limit), specifically:
409+
1. HTML files (e.g. `report.html` generated by `pytest`).
410+
2. Trace Files (e.g. `test_name/trace.zip` generated by Playwright).
411+
3. Screenshots (e.g. `test_screenshot.png` generated by Playwright).
412+
4. CSV Files (e.g. `results.csv` generated during test execution from the UI).
413+
3. Prompt the user to confirm that they are updating the correct ticket and the correct files are being uploaded. If files already exist on the ticket with a matching name, a unique name will be provided unless `--overwrite-files` is provided.
414+
4. If `y` is selected, upload the files and add a comment (unless `--no-comment` is provided) to Jira outlining the files uploaded and if possible, the environment information from the test run (unless `--no-env-data` is provided).
415+
416+
You can also pass in the following arguments which will have the noted effects:
417+
418+
| Argument | Description |
419+
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
420+
| `--jira-ref <Jira Reference>` | The Jira ticket to upload to. Will take precedence over auto-deriving from branch name and the set environment variable. |
421+
| `--results-dir <Directory>` | The directory to point to. If not set, points to `test-results/` (the default directory for test results in this repository). |
422+
| `--no-html` | Don't include HTML files in the upload. |
423+
| `--no-trace` | Don't include Trace files (.zip) in the upload. |
424+
| `--no-csv` | Don't include CSV files in the upload. |
425+
| `--no-screenshots` | Don't include screenshots (.png) in the upload. |
426+
| `--no-comment` | Don't add a Jira comment highlighting the results. |
427+
| `--no-env-data` | Don't include environment data in the Jira comment (if getting environment data has been configured). |
428+
| `--overwrite-files` | If a filename exists on the ticket that matches those in the results directory, overwrite them. |
429+
| `--auto-confirm` | Will not ask if you want to proceed if provided, and will assume that yes has been pressed. |
430+
431+
Further information on the available actions for this logic can be found in the [Jira Confluence Utility utility guide](./docs/utility-guides/JiraConfluenceUtil.md).
432+
433+
## Contributing
383434

384435
Further guidance on contributing to this project can be found in our [contribution](./CONTRIBUTING.md) page.
385436

classes/address/address.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class Address:
6+
"""
7+
Represents a postal address with up to five address lines and a postcode.
8+
Provides methods to format the address as a string.
9+
"""
10+
11+
address_line1: str = ""
12+
address_line2: str = ""
13+
address_line3: str = ""
14+
address_line4: str = ""
15+
address_line5: str = ""
16+
post_code: str = ""
17+
18+
def set_address_line(self, line_number: int, address_line: str) -> None:
19+
"""
20+
Sets the specified address line (1-5) to the given value.
21+
22+
Args:
23+
line_number (int): The address line number (1-5).
24+
address_line (str): The value to set for the address line.
25+
26+
Raises:
27+
ValueError: If line_number is not between 1 and 5.
28+
"""
29+
if not 1 <= line_number <= 5:
30+
raise ValueError(
31+
f"Invalid line number {line_number}, must be between 1 and 5"
32+
)
33+
34+
setattr(self, f"address_line{line_number}", address_line)
35+
36+
def __str__(self) -> str:
37+
"""
38+
Returns the formatted address as a single string.
39+
"""
40+
address_parts = [
41+
self.address_line1,
42+
self.address_line2,
43+
self.address_line3,
44+
self.address_line4,
45+
self.address_line5,
46+
self.post_code,
47+
]
48+
return ", ".join([part for part in address_parts if part])
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from enum import Enum
2+
from typing import Optional
3+
4+
5+
class AppointmentSlotType(Enum):
6+
"""
7+
Enum representing appointment slot types, mapped to valid value IDs and descriptions.
8+
Provides utility methods for lookup by description (case-sensitive and insensitive) and by valid value ID.
9+
"""
10+
11+
COLONOSCOPY_ASSESSMENT = (209028, "Colonoscopy Assessment")
12+
BOWEL_SCOPE_AUTOMATIC = (200526, "FS Automatic")
13+
BOWEL_SCOPE_MANUAL = (200527, "FS Manual")
14+
POSITIVE_ASSESSMENT = (6016, "Positive Assessment")
15+
POST_INVESTIGATION = (6017, "Post-Investigation")
16+
SURVEILLANCE = (20061, "Surveillance")
17+
18+
def __init__(self, valid_value_id: int, description: str) -> None:
19+
self._valid_value_id = valid_value_id
20+
self._description = description
21+
22+
@property
23+
def valid_value_id(self) -> int:
24+
"""
25+
Returns the valid value ID for the appointment slot type.
26+
"""
27+
return self._valid_value_id
28+
29+
@property
30+
def description(self) -> str:
31+
"""
32+
Returns the description for the appointment slot type.
33+
"""
34+
return self._description
35+
36+
@classmethod
37+
def by_description(cls, description: str) -> Optional["AppointmentSlotType"]:
38+
"""
39+
Returns the enum member matching the given description (case-sensitive).
40+
"""
41+
for member in cls:
42+
if member.description == description:
43+
return member
44+
return None
45+
46+
@classmethod
47+
def by_description_case_insensitive(
48+
cls, description: str
49+
) -> Optional["AppointmentSlotType"]:
50+
"""
51+
Returns the enum member matching the given description (case-insensitive).
52+
"""
53+
desc_lower = description.lower()
54+
for member in cls:
55+
if member.description.lower() == desc_lower:
56+
return member
57+
return None
58+
59+
@classmethod
60+
def by_valid_value_id(cls, valid_value_id: int) -> Optional["AppointmentSlotType"]:
61+
"""
62+
Returns the enum member matching the given valid value ID.
63+
"""
64+
for member in cls:
65+
if member.valid_value_id == valid_value_id:
66+
return member
67+
return None
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from enum import Enum
2+
from typing import Optional
3+
4+
5+
class AppointmentStatusType(Enum):
6+
"""
7+
Enum representing appointment status types, mapped to valid value IDs and descriptions.
8+
"""
9+
10+
ATTENDED = (6121, "Attended")
11+
BOOKED = (6120, "Booked")
12+
CANCELLED = (6122, "Cancelled")
13+
DID_NOT_ATTEND = (6123, "Did Not Attend")
14+
15+
def __init__(self, valid_value_id: int, description: str) -> None:
16+
self._valid_value_id = valid_value_id
17+
self._description = description
18+
19+
@property
20+
def valid_value_id(self) -> int:
21+
"""
22+
Returns the valid value ID for the appointment status type.
23+
"""
24+
return self._valid_value_id
25+
26+
@property
27+
def description(self) -> str:
28+
"""
29+
Returns the description for the appointment status type.
30+
"""
31+
return self._description
32+
33+
@classmethod
34+
def by_description(cls, description: str) -> Optional["AppointmentStatusType"]:
35+
"""
36+
Returns the enum member matching the given description (case-sensitive).
37+
"""
38+
for member in cls:
39+
if member.description == description:
40+
return member
41+
return None
42+
43+
@classmethod
44+
def by_description_case_insensitive(
45+
cls, description: str
46+
) -> Optional["AppointmentStatusType"]:
47+
"""
48+
Returns the enum member matching the given description (case-insensitive).
49+
"""
50+
desc_lower = description.lower()
51+
for member in cls:
52+
if member.description.lower() == desc_lower:
53+
return member
54+
return None
55+
56+
@classmethod
57+
def by_valid_value_id(
58+
cls, valid_value_id: int
59+
) -> Optional["AppointmentStatusType"]:
60+
"""
61+
Returns the enum member matching the given valid value ID.
62+
"""
63+
for member in cls:
64+
if member.valid_value_id == valid_value_id:
65+
return member
66+
return None

classes/appointment_status_type.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

classes/appointments_slot_type.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)