Playwright tests¶
We use Playwright for end-to-end tests of the frontend. Start with the official installation, assertions and best practices pages. For running tests in the pipeline see Playwright in GitLab CI.
Structure¶
Tests live in the playwright/ folder of the platform repository. It is a separate Yarn workspace (@easy/playwright) with its own package.json. The configuration is playwright/playwright.config.ts; only Chrome is configured, do not add other browsers without agreeing it with your lead.
BASE_URL sets the application under test and defaults to http://localhost:3000. If you cannot run the application locally, point BASE_URL at a testing application. Do not commit changes to playwright.config.ts without discussing them with your lead.
| Path | Content |
|---|---|
playwright/tests |
Test files (*.spec.ts), grouped by feature. |
playwright/pages |
Page objects, *.page.ts. base.page.ts holds methods shared by all pages, layout_page/ the elements shared across the application. |
playwright/fixtures |
JSON files describing entities. A file with an id describes an entity that exists in the testing database; other files fill inputs. |
playwright/constants |
Constants used only by tests (timeouts and similar). |
playwright/types |
Enumerations and shared types. |
playwright/utils, playwright/utils.ts |
Helpers without Playwright logic, plus shared helpers such as autologin. |
Page objects follow the Page Object Pattern: locators and the methods that interact with a page, modal or form live in *.page.ts, not in tests.
import { Page } from "@playwright/test";
export class BasePage {
readonly page: Page;
readonly submitBtn: string | null;
constructor(page: Page, submitBtn: string | null = null) {
this.page = page;
this.submitBtn = submitBtn;
}
}
Test file structure¶
Each test file has:
test.describewith a short description of what the file tests,test.beforeAll()ortest.beforeEach()for setup,test()blocks, each with a short description,test.afterAll()ortest.afterEach()for cleanup.
import { test, Page } from "@playwright/test";
test.describe.serial("Create and work with the projects", () => {
let page: Page;
test.beforeAll(async ({ browser }) => {
const pageContext = await browser.newContext();
page = await pageContext.newPage();
await autologin(page);
});
test("Create a new project #task_id", async () => {
// test body
});
test.afterAll(async () => {
await page.close();
});
});
Selectors with data-cy¶
testIdAttribute is set to data-cy, so page.getByTestId("...") targets data-cy. Follow the best practices and these rules:
- Use
idandclassselectors only in combination with adata-cyattribute. - Do not target elements by HTML tags (
div,img,a) unless combined withdata-cy. - Avoid complex selectors such as
div.mydiv > ul > li > a.target. - Never assert on localised strings.
- Make
data-cyvalues specific and semantic, preferably in thesubject__action--targetpattern, for examplebutton__open--crm-index.
When the backend renders the HTML, pass the attribute from the view:
link_to(l(:label), url, class: "customize-button", "data-cy": "button__customize_page")
# or
link_to(l(:label), url, class: "customize-button", data: { cy: "button__customize_page" })
Adding data-cy is the only application change a test branch may contain.
Run tests locally¶
Playwright runs against a running application server.
- Create a database for the tests and point the application at it, for example with
MYSQL_DATABASE=playwright_testin.env.development.local. - Import the dump of the shared testing database (
e2e-testing-source.easysoftware.com). Do not edit that database without permission from QA. -
Install and migrate:
-
Start the server. In production mode
FORCE_HTTPlets Puma accept plain HTTP. -
Install the dependencies and run the tests from the repository root:
Other scripts from the root package.json: yarn playwright:headed, yarn playwright:ui and yarn playwright:report. Run a single file with yarn playwright tests/login.spec.ts.
Change tests¶
- Create or reuse an Easy8 task that tracks the change.
- Branch as
testautomation/task_id-short-description(for exampletestautomation/001-created-a-new-project-test). This format is reserved for QA. - End each test description with the task ID:
test("Created a project #002", ...). - Keep test files under 250 lines; put the overflow in a new file.
- Commit with the commit message convention, including the task reference.
- A merge request needs two approvals. Your lead tells you where to post it.
Writing stable tests¶
- After a reload, redirect or opening a new page, wait for the page to be ready.
navigateToUrlinplaywright/pages/base.page.tsdoes it withwaitForDocumentReady. -
Use
waitForResponseinsidePromise.alltogether with the action that triggers the request: -
Do not read text with
innerText; it returns the current text and breaks when the content re-renders. UsetoHaveText, which waits: -
When you run tests against a deployment,
throttleNetworkfrombase.page.tsslows the network so you can check stability. Remove the call before merging.