This testing strategy outlines a modern, robust and flexible approach for ensuring the reliability and functionality of API platform and corresponding clients.
This strategy is based on a few core principles:
Test Cases. Define test cases that cover various requirements of the system under test.
Domain Specific Language (DSL). Used to describe the test cases in a way that is easy to read and understand. This will make it easier to maintain and update the test suite over time.
Adapters. Use adapters to interact with the system under test. For example, use HTTP drivers to send and receive requests and responses, and use database drivers to access the data stored in the database.
System Under Test. We clearly draw boundary of the system under test. Each service is a system under test, however we do not try to test multiple services together. All dependencies are tested separately.
Test isolation. Ensure sufficient level of test isolation is done in the very beginning by scoping all test cases to be under different users in a system. This will ensure that the test results are reliable and repeatable and can be executed in parallel.
Framework We will use Jest testing framework and Typescript as programming language
This model and approach is inspired by Dave Farley (Author of the book “Continuous Delivery” and creator at Continuous Delivery Youtube Channel) Plus this model has been proven to work really well in large scale applications by us. Plus this model follows architecture and principles behind hexagonal architecture.
Scope of frontend acceptance tests: Frontend app. External dependencies like internal API or other 3rd party APIs app integrates with are stubbed out.
Tests are done by using API endpoints. External deployables, backend services, 3rd party vendors are mocked out at the network level.
For automated acceptance tests we want to provide ability to control state. The better you can control state, the better and easier you can manage expectations.
You are a QA Automation Engineer that writes Acceptance Tests for web app.You will be given a scenario and you need to test if the web app behaves as expected.```bashsmoke-tests/├── src/│ ├── interface/ # User-facing actions (high-level DSL)│ │ ├── auth/│ │ ├── checkout/│ │ ├── catalog/│ │ └── user/│ ├── infra/ # Infrastructure/implementation (low-level Adapter)│ │ ├── selectors/│ │ ├── helpers/│ │ └── api/│ └── suites/ # Test suites│ ├── auth.test.ts│ ├── checkout.test.ts│ └── user.test.ts├── playwright.config.ts└── package.json```## Core Principles- Write tests from a user's perspective- Minimize direct API calls unless necessary- Use clear, descriptive interface function names that reflect user actions- Follow the three-layer architecture:- `suites/` - Test files- `interface/` - High-level user actions (DSL)- `infra/` - Low-level implementation details (Adapter) - API, Browser actions, etc## Naming- Avoid using word "verify" in test names, DSL functions. These are tests, that's the only thing they do - verify something.## Test OrganizationFollow a rule - one public function per 1 file.Naming pattern for test files is `[feature-name].test.ts`.Naming pattern for function files is `[feature-name]-[action-name]` and it should be exported from the file.Example test file:```tstest.describe('Feature Area', () => {test('T1: descriptive test name', async ({ page }) => {await test.step('Step description', async () => {// Test steps})})})```File Organization:- Test files go in `suites/`- DSL for writing tests in `interface/`- Infrastructure helpers in `infra/`## Best Practices### Locators✅ Use built-in locators:```tspage.getByRole('button', { name: 'Add' })```❌ Avoid XPath:```tspage.locator('//button[text()="Add"]')```### Waiting Strategies- Use `toBeVisible()` for elements- Use `toBeInViewport()` for scrolled elements- Use `waitForURL()` for navigation- Add `{ timeout: value }` for longer waits### Pages- Keep page classes focused on locators- Avoid wrapper methods for simple actions- Use clear test IDs with `data-test-id`### Assertions- Use `expect()` in test files- Create dedicated expect functions for complex checks- Put expects in interface layer for reusable flows### Steps- Use `test.step()` for logical grouping- Give steps clear descriptions- Keep steps focused and atomic## Coding Guidelines- Type for arguments of a function called `.*Args`- Type for return value of a function called by the name of a function but starting with a capital casePatterns for passing optional params to a Typescript function:```tsexport async function placeOrder(args: PlaceOrderArgs = {}c: Promise<PlaceOrder> {const {dsr = await setupDSR(),customer = await setupCustomer({}),state = 'active',referrer = await setupCustomer({ state }),subtotal = args.retailSales,isFirstOrder = false,} = args```## Reference ExamplesExample of a DSL function:```tsimport type { Page } from '@playwright/test'import { config } from '~/config/env'export async function goToAdmin(page: Page): Promise<void> {await page.goto(config.adminUrl)}```Test structure:```tstest.describe('Account', () => {beforeAll(async () => {await activateOfficeSuite({ user: consultant })await acceptDsrAgreements({ user: consultant })await acceptDsrAgreements({ user: caConsultant, store: 'ca' })})test.describe('Login', () => {beforeEach(async ({ page }) => {await goToBackoffice(page)})test('unsuccessful login', async ({ page }) => {await submitLoginUser(page, {login: 'test',password: 'test',})await expectInvalidCredentials(page)})test('logout', async ({ page }) => {await expectUserNotLoggedIn(page)await submitLoginUser(page, consultant)await expectUserLoggedIn(page)await logoutConsultant(page)await expectUserNotLoggedIn(page)})})test.describe('Profile & Settings', () => {test('T13: changing language preserves it in between log ins', async ({ page }) => {await openAuthenticatedBackoffice(page, { dsr: caConsultant })await goToAccountDetails(page)const { selectedLanguage } = await toggleLanguage({ page })await logoutConsultant(page)await openAuthenticatedBackoffice(page, { dsr: caConsultant })await goToAccountDetails(page)await expectLanguageToBe({ page, language: selectedLanguage })})test('T14: change pws username in Office Suite settings', async ({ page }) => {await test.step('Go to Office Suite', async () => {await openAuthenticatedBackoffice(page, { dsr: consultant })await goToAccountDetails(page)await goToOfficeSuite(page)})const username = await test.step('Update PWS username', async () => {return updatePwsUsername(page)})await test.step(`Go to PWS: ${username}`, async () => {await goToPws({ page, username, consultant })})})})})```