To support the planned growth of our test suite and ensure it remains "mindful," scalable, and maintainable, we will evolve its architecture.
Our goal is to create a highly readable and reusable structure that separates behavioral definitions from implementation logic. We will achieve this by integrating Behavior-Driven Development (BDD) and introducing a layered-action architecture.
To make our tests understandable for all stakeholders (including product owners) and to link them directly to our Software Testing Protocol, we will adopt Gherkin/Cucumber. Playwright can be integrated with a Cucumber runner (@cucumber/cucumber), allowing us to define our tests in plain-English .feature files.
Example .feature file:
Feature: Measurement Tools
As a radiologist, I need to draw measurements to assess a study.
Scenario: Drawing a new Length measurement
Given a study is loaded in the viewport
When I select the "Length" tool from the toolbar
And I draw a line on the viewport
Then I should see the measurement in the measurement panelTo implement these BDD scenarios, we will expand our current POM structure into a more granular, layered architecture: Steps -> Actions -> Page.
This separation makes our code incredibly reusable and easy to debug.
-
Layer 1: Pages (Page Objects)
- Purpose: This is our current
playwright/pagesdirectory. Its responsibility is to define element locators and atomic interactions on a specific page or component. - Example:
lengthToolButton.click(),measurementPanel.getRowCount(). - Rule: Page Objects should never contain business logic or workflows. They only know how to find and interact with elements.
- Purpose: This is our current
-
Layer 2: Actions (Workflow Layer)
- Purpose: This is a new layer that consumes the "Page" objects. Actions represent reusable business workflows or tasks that a user can perform. They chain together multiple atomic "Page" interactions.
- Example: An
actions/measurement.tsfile might have a function:async function drawLengthMeasurement(page, from, to) { await toolbarPage.clickTool('Length'); await viewportPage.drag(from, to); }
- Rule: This layer handles the logic of a workflow, while the "Page" layer handles the interaction.
-
Layer 3: Steps (Step Definitions)
- Purpose: This is the "glue" layer that connects the Gherkin
.featurefiles to our code. Each "Step" (Given, When, Then) will call one or more "Actions". - Example: A
steps/measurement.steps.tsfile:When('I select the {string} tool from the toolbar', async (toolName) => { await toolbarActions.selectTool(toolName); }); Then('I should see the measurement in the measurement panel', async () => { await measurementPanelActions.verifyMeasurementExists(); });
- Purpose: This is the "glue" layer that connects the Gherkin
This flow (Feature -> Steps -> Actions -> Page) ensures that when a UI element changes, we only have to update the "Page" layer. If a workflow changes, we only update the "Action" layer. This makes the entire system highly scalable and robust.