first commit
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled

This commit is contained in:
2026-04-22 19:51:20 +07:00
commit 93d1b7c3d3
579 changed files with 99797 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
# Adopt ArrangeActAssert (AAA) Pattern for All Tests
## Status
Proposed
## Context
Our tests are currently written in different styles, which makes them harder to read, understand, and maintain.
To improve **readability** and make it easier to **debug failing tests**, we want to standardize the structure of tests by following the well-known **ArrangeActAssert (AAA)** pattern.
## Decision
We will adopt the AAA pattern for tests. Every test should follow this structure:
1. **Arrange**: Set up data, mocks, page state, or environment
2. **Act**: Perform the action being tested
3. **Assert**: Check the expected outcome
## Guidelines
- ✅ Multiple actions and assertions are **allowed** as long as they belong to a **single AAA flow**
- 🚫 **Repeating the full AAA structure in a single test is discouraged**, except for performancesensitive tests where setup cost is prohibitively high
- ✂️ If a test involves multiple unrelated behaviors, **split it into separate test cases**
- 🧼 Keep tests focused and predictable: one test = one scenario
## Example
```ts
test('user can view their post', async ({ page }) => {
// Arrange
const user = await userFactory.create();
const post = await postFactory.create({ userId: user.id });
// Act
await page.goto(`/posts/${post.id}`);
// Assert
await expect(page.getByText(post.title)).toBeVisible();
});
+101
View File
@@ -0,0 +1,101 @@
# Adopt Page Objects Pattern for E2E Test Organization
## Status
Proposed
## Context
Our Playwright tests currently interact directly with page elements using raw selectors and actions scattered throughout test files. This approach leads to several issues:
- **Code duplication**: The same selectors and interactions are repeated across multiple tests
- **Maintenance burden**: When UI changes, we need to update selectors in many places
- **Poor readability**: Tests are cluttered with low-level DOM interactions instead of focusing on business logic
- **Fragile tests**: Direct coupling between tests and implementation details makes tests brittle
To improve **maintainability**, **readability**, and **test stability**, we want to adopt the Page Objects pattern to encapsulate page-specific knowledge and provide a clean API for test interactions.
The Page Objects pattern was originally described by [Martin Fowler](https://martinfowler.com/bliki/PageObject.html) as a way to "wrap an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML."
## Decision
We will adopt the Page Objects pattern for organizing E2E tests. Every page or major UI component should have a corresponding page object class that:
1. **Encapsulates locators**: All element selectors are defined in one place
2. **Provides semantic methods**: Expose high-level actions like `login()`, `createPost()`, `navigateToSettings()`
3. **Abstracts implementation details**: Tests interact with business concepts, not DOM elements
4. **Centralizes page-specific logic**: Complex interactions and waits are handled within page objects
5. **Assertions live in test files**: Page Objects may include readiness guards (e.g., locator.waitFor({state: 'visible'})) before actions, business assertions (expect(...)) should be in tests
6. **Expose semantic locators, hide selectors**: Page Objects should surface public readonly Locators for tests to assert on, while keeping selector strings and construction internal
## Guidelines
Following both [Fowler's original principles](https://martinfowler.com/bliki/PageObject.html) and modern Playwright best practices:
-**One page object per logical page or major component** (e.g., `LoginPage`, `PostEditor`, `AdminDashboard`)
-**Model the structure that makes sense to the user**: not necessarily the HTML structure
-**Use descriptive method names** that reflect user actions (e.g., `fillPostTitle()` not `typeInTitleInput()`)
-**Return elements or data**: for assertions in tests (e.g., `getErrorMessage()` returns locator)
-**Include wait methods**: for page readiness and async operations (e.g., `waitForErrorMessage()`)
-**Chain related actions**: in fluent interfaces where it makes sense
-**Keep assertions in test files**: page objects should return data/elements, tests should assert
-**Handle concurrency issues** within page objects (async operations, loading states)
-**Expose Locators (read-only), not raw selector strings**: you can tests assert against public locators (Playwright encourages it, with helpers on assertion)
- `loginPage.saveButton.click` instead of `page.locator('[data-testid="save-button"]')`
-**Selector priority: prefer getByRole / getByLabel / data-testid over CSS or XPath.**: add data-testid attributes where needed for stability
-**Use guards, not assertions, in POM**: prefer locator.waitFor({state:'visible'})
- 🚫 **Don't include expectations/assertions** in page object methods (following Fowler's recommendation)
- 📁 **Organize in `/e2e/helpers/pages/` directory** with clear naming conventions
## Example
```ts
// e2e/helpers/pages/admin/LoginPage.ts
export class LoginPage extends BasePage {
public readonly emailInput = this.page.locator('[data-testid="email-input"]');
public readonly passwordInput = this.page.locator('[data-testid="password-input"]');
public readonly loginButton = this.page.locator('[data-testid="login-button"]');
public readonly errorMessage = this.page.locator('[data-testid="login-error"]');
constructor(page: Page) {
super(page);
this.pageUrl = '/login';
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async waitForErrorMessage() {
await this.errorMessage.waitFor({ state: 'visible' });
return this.errorMessage;
}
getErrorMessage() {
return this.errorMessage;
}
}
// In test file
test.describe('Login', () => {
test('invalid credentials', async ({page}) => {
// Arrange
const loginPage = new LoginPage(page);
// Act
await loginPage.goto();
await loginPage.login('invalid@email.com', 'wrongpassword');
const errorMessage = await loginPage.waitForErrorMessage();
// Assert
await expect(errorMessage).toHaveText('Invalid credentials');
});
}
```
## References
- [Page Object - Martin Fowler](https://martinfowler.com/bliki/PageObject.html) - Original pattern definition
- [Selenium Page Objects](https://selenium-python.readthedocs.io/page-objects.html) - Early implementation guidance
- [Playwright Page Object Model](https://playwright.dev/docs/pom) - Modern Playwright-specific approaches
+22
View File
@@ -0,0 +1,22 @@
# Architecture Decision Records (ADRs)
This directory contains Architecture Decision Records (ADRs) specific to the E2E test suite.
ADRs are short, version-controlled documents that capture important architectural and process decisions, along with the reasoning behind them.
They help document **why** something was decided — not just **what** was done — which improves transparency, consistency, and long-term maintainability.
Each ADR includes the following sections:
- `Status` `Proposed`, `Accepted`, `Rejected`, etc.
- `Context` Why the decision was needed
- `Decision` What was decided and why
- `Guidelines` (Optional) How the decision should be applied
- `Example` (Optional) Minimal working example to clarify intent
- `References` - (Optional) - Lists documents, links, or resources that informed or support the decision
## Guidelines for contributing
- We follow a simplified and slightly adapted version of the [Michael Nygard ADR format](https://github.com/joelparkerhenderson/architecture-decision-record/tree/main/locales/en/templates/decision-record-template-by-michael-nygard)
- Keep ADRs focused, short, and scoped to one decision
- Start with `Status: Proposed` and update to `Status: Accepted` after code review
- Use sequential filenames with a descriptive slug, for example: `0002-page-objects-pattern.md`