| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 1 | 43 | 2 | 2% |
| Commands | 0 | 0 | 0 | — |
| Section tags | 1 | 6 | 2 | 11% |
What each file covers
Sections
1 shared · 43 only in A · 2 only in B- − Testing Guidelines for NOWCRM
- − Overview
- − Test Structure
- − Directory Organization
- − File Naming Conventions
- − Page Object Model (POM) Pattern
- − Structure
- − Locator Best Practices
- − Action Methods
- − Assertion Methods
- − Test File Structure
- − Basic Template
- − Test Organization
- − Test Data Management
- − Using Faker for Test Data
- − Unique Identifiers
- − Test Credentials
- − Authentication and Setup
- − Global Setup
- − Login Helper
- − Test Execution Patterns
- − Waiting Strategies
- − Error Handling
- − Test Isolation
- − Assertions
- − Best Practices
- − Helper Functions
- − Reusable Test Helpers
- − External Service Helpers
- − Test Configuration
- − Playwright Config
- − Environment Variables
- − Test Maintenance
- − Handling Flaky Tests
- − Skipping Tests
- − Comments
- − Naming Conventions
- − Common Patterns
- − Row Operations
- − Modal/Dialog Operations
- − Mass Actions
- − Best Practices Summary
- − Anti-Patterns to Avoid
- + Services
- + CI/CD - build pipleline
- Code Style
Commands
neither file has anySection tags
1 shared · 6 only in A · 2 only in B- − setup
- − test
- − architecture
- − security
- − do-not
- − docs
- + build
- + deployment
- code-style
Line diff
nowtec/nowCRM · .cursor/rules/testing-guidelines.mdc
@@ −1 @@
1---
2description: Testing guidelines for NOWCRM
3globs: ["**/*.spec.ts", "**/*.test.ts", "**/tests/**", "**/playwright.config.ts"]
4alwaysApply: false
5---
6
7# Testing Guidelines for NOWCRM
8
9## Overview
10
11NOWCRM uses **Playwright** for end-to-end (E2E) testing. All tests follow the **Page Object Model (POM)** pattern for maintainability and reusability.
12
13## Test Structure
14
15### Directory Organization
16
17```
18apps/nowcrm/tests/
19├── *.spec.ts # Test specification files (numbered for execution order)
20├── pages/ # Page Object Models (POMs)
21│ ├── CommonPage.ts
22│ ├── ContactsListPage.ts
23│ └── ...
24├── utils/ # Test utilities and helpers
25│ ├── authHelper.ts
26│ └── data.ts
27├── setup/ # Setup and teardown scripts
28│ ├── global-setup.ts
29│ ├── create-users.ts
30│ └── delete-users.ts
31└── files/ # Test fixtures and data files
32```
33
34### File Naming Conventions
35
36- **Test files**: Use numbered prefixes for execution order (e.g., `01Authentication.spec.ts`, `02Contacts.spec.ts`)
37- **Page Objects**: Use descriptive names ending with `Page` or `Modal` (e.g., `ContactsListPage.ts`, `ContactCreateModal.ts`)
38- **Utilities**: Use descriptive names (e.g., `authHelper.ts`, `data.ts`)
39
40## Page Object Model (POM) Pattern
41
42### Structure
43
44Every Page Object should follow this structure:
45
46```typescript
47import { type Locator, type Page, expect } from '@playwright/test';
48
49export class PageName {
50 readonly page: Page;
51
52 // Locators - declare as readonly
53 readonly elementName: Locator;
54
55 constructor(page: Page) {
56 this.page = page;
57 // Initialize locators
58 this.elementName = page.getByRole('button', { name: 'Button Name' });
59 }
60
61 // Actions - async methods that perform interactions
62 async performAction() {
63 await expect(this.elementName).toBeVisible();
64 await this.elementName.click();
65 }
66
67 // Assertions - async methods that verify state
68 async expectSomethingVisible(timeout: number = 5000) {
69 await expect(this.elementName, 'Descriptive message').toBeVisible({ timeout });
70 }
71}
72```
73
74### Locator Best Practices
75
761. **Prefer role-based selectors**:
77```typescript
78// ✅ Good - accessible and stable
79this.createButton = page.getByRole('button', { name: 'Create' });
80this.emailInput = page.getByRole('textbox', { name: 'Email' });
81
82// ❌ Avoid - fragile CSS selectors
83this.createButton = page.locator('.btn-primary');
84```
85
862. **Scope locators within dialogs/modals**:
87```typescript
88constructor(page: Page) {
89 this.dialog = page.getByRole('dialog', { name: /Create Contact/i });
90 // Scope inputs within dialog
91 this.firstNameInput = this.dialog.getByRole('textbox', { name: 'First name' });
92}
93```
94
953. **Use descriptive locator names**:
96```typescript
97// ✅ Good
98readonly userMenuTrigger: Locator;
99readonly deleteMassActionMenuItem: Locator;
100
101// ❌ Avoid
102readonly btn1: Locator;
103readonly menuItem: Locator;
104```
105
106### Action Methods
107
108- **Naming**: Use verb phrases (e.g., `clickCreateButton`, `fillAndSubmit`, `openUserMenu`)
109- **Wait for visibility**: Always wait for elements before interacting
110- **Return values**: Return relevant data when needed (e.g., created entity ID)
111
112```typescript
113async clickCreateButton() {
114 await expect(this.createButton, 'Create button should be visible').toBeVisible({ timeout: 20000 });
115 await this.createButton.click();
116}
117
118async fillAndSubmit(data: ContactData) {
119 await this.firstNameInput.fill(data.firstName);
120 await this.lastNameInput.fill(data.lastName);
121 await this.emailInput.fill(data.email);
122 await this.createButton.click();
123}
124```
125
126### Assertion Methods
127
128- **Naming**: Prefix with `expect` (e.g., `expectDashboardVisible`, `expectStatusMessage`)
129- **Descriptive messages**: Always include meaningful error messages
130- **Configurable timeouts**: Accept timeout parameters with sensible defaults
131
132```typescript
133async expectStatusMessage(message: string, timeout: number = 20000) {
134 const messageLocator = this.page.getByText(message, { exact: true });
135 await expect(messageLocator, `Status message "${message}" should be visible`)
136 .toBeVisible({ timeout });
137}
138
139async expectDashboardVisible(timeout: number = 10000) {
140 await expect(this.page, 'URL should indicate CRM dashboard')
141 .toHaveURL(/\/crm$/, { timeout });
142}
143```
144
145## Test File Structure
146
147### Basic Template
148
149```typescript
150import { test, expect } from '@playwright/test';
151import { faker } from '@faker-js/faker';
152
153// Import Page Object Models
154import { ContactsListPage } from './pages/ContactsListPage';
155import { ContactCreateModal } from './pages/ContactCreateModal';
156
157// Import utilities
158import { loginUser } from './utils/authHelper';
159
160test.describe('Feature Name', () => {
161 let pageObject1: ContactsListPage;
162 let pageObject2: ContactCreateModal;
163
164 test.beforeEach(async ({ page }) => {
165 // Initialize POMs
166 pageObject1 = new ContactsListPage(page);
167 pageObject2 = new ContactCreateModal(page);
168
169 // Common setup (e.g., login)
170 await loginUser(page);
171 await pageObject1.goto();
172 });
173
174 test('User can perform action', async () => {
175 // Arrange - set up test data
176 const testData = {
177 firstName: faker.person.firstName(),
178 email: faker.internet.email()
179 };
180
181 // Act - perform actions
182 await pageObject1.clickCreateButton();
183 await pageObject2.fillAndSubmit(testData);
184
185 // Assert - verify results
186 await pageObject2.expectCreationStatusMessage(testData.firstName);
187 await expect(pageObject1.getRowLocator(testData.email))
188 .toBeVisible({ timeout: 10000 });
189 });
190});
191```
192
193### Test Organization
194
1951. **Use `test.describe` blocks** to group related tests
1962. **Initialize POMs in `beforeEach`** for consistency
1973. **Number test files** for execution order (e.g., `01Authentication.spec.ts`)
1984. **One feature per describe block** (e.g., 'Contact Management', 'Authentication Flow')
199
200## Test Data Management
201
202### Using Faker for Test Data
203
204```typescript
205import { faker } from '@faker-js/faker';
206
207// Generate unique test data
208const contact = {
209 firstName: faker.person.firstName(),
210 lastName: faker.person.lastName(),
211 email: faker.internet.email({ provider: `test.${faker.string.alphanumeric(5)}.pw` }),
212 address: faker.location.streetAddress(),
213};
214```
215
216### Unique Identifiers
217
218- **Use timestamps or random strings** to ensure uniqueness:
219```typescript
220const uniqueEmail = `testuser+${Date.now()}@example.com`;
221const uniqueListName = `List_${faker.string.alphanumeric(6)}`;
222```
223
224### Test Credentials
225
226- **Store in environment variables** via `utils/data.ts`:
227```typescript
228export const testCredentials = {
229 email: process.env.TEST_USER_EMAIL || 'testuser@example.com',
230 password: process.env.TEST_USER_PASSWORD || 'StrongPassword123!',
231};
232```
233
234## Authentication and Setup
235
236### Global Setup
237
238- **Use `global-setup.ts`** for authentication state management
239- **Save storage state** to avoid repeated logins:
240```typescript
241await page.context().storageState({ path: STORAGE_STATE_PATH });
242```
243
244### Login Helper
245
246- **Create reusable login function** in `utils/authHelper.ts`:
247```typescript
248export async function loginUser(
249 page: Page,
250 postLoginUrlRegex: RegExp = /\/crm$/
251): Promise<void> {
252 await page.goto('/en/auth');
253 await page.getByRole('textbox', { name: 'Email' }).fill(testCredentials.email);
254 await page.getByRole('textbox', { name: 'Password' }).fill(testCredentials.password);
255 await page.getByRole('button', { name: 'Sign in' }).click();
256 await expect(page).toHaveURL(postLoginUrlRegex, { timeout: 15000 });
257}
258```
259
260## Test Execution Patterns
261
262### Waiting Strategies
263
2641. **Use Playwright's auto-waiting**:
265```typescript
266// ✅ Good - Playwright waits automatically
267await button.click();
268
269// ❌ Avoid - unnecessary manual waits
270await page.waitForTimeout(1000);
271await button.click();
272```
273
2742. **Use explicit waits for async operations**:
275```typescript
276// ✅ Good - wait for specific condition
277await expect(element).toBeVisible({ timeout: 10000 });
278
279// ✅ Good - wait for URL change
280await expect(page).toHaveURL(/\/contacts\/\d+\/details/);
281```
282
2833. **Use `waitForTimeout` sparingly** (only when necessary):
284```typescript
285// Only when waiting for async operations that can't be detected
286await page.waitForTimeout(300); // Wait for dropdown to render
287```
288
289### Error Handling
290
291- **Use try/finally blocks** for cleanup:
292```typescript
293test('User can perform action', async ({ page, request }) => {
294 const uniqueEmail = `test+${Date.now()}@example.com`;
295
296 try {
297 // Test logic
298 await createTestUser(request, { email: uniqueEmail });
299 // ... test steps ...
300 } finally {
301 // Cleanup
302 await deleteUserFromStrapi(request, uniqueEmail);
303 await request.delete('http://localhost:8025/api/v1/messages');
304 }
305});
306```
307
308### Test Isolation
309
310- **Each test should be independent** - don't rely on test execution order
311- **Clean up test data** after each test
312- **Use unique identifiers** to avoid conflicts
313
314## Assertions
315
316### Best Practices
317
3181. **Always include descriptive messages**:
319```typescript
320// ✅ Good
321await expect(contactRow, 'Contact row should contain correct email')
322 .toContainText(contact.email);
323
324// ❌ Avoid
325await expect(contactRow).toContainText(contact.email);
326```
327
3282. **Use appropriate matchers**:
329```typescript
330await expect(element).toBeVisible({ timeout: 10000 });
331await expect(element).toHaveText('Expected Text');
332await expect(element).toContainText('Partial Text');
333await expect(page).toHaveURL(/\/crm$/);
334await expect(locator).toHaveCount(1);
335```
336
3373. **Set reasonable timeouts**:
338```typescript
339// Default timeout: 5000ms
340await expect(element).toBeVisible();
341
342// Custom timeout for slow operations
343await expect(element).toBeVisible({ timeout: 20000 });
344```
345
346## Helper Functions
347
348### Reusable Test Helpers
349
350Create helper functions for common operations:
351
352```typescript
353// In test file or utils
354async function createContactViaUI(data: ContactData) {
355 await contactsListPage.clickCreateButton();
356 await contactCreateModal.waitForDialogVisible();
357 await contactCreateModal.fillAndSubmit(data);
358 await contactCreateModal.expectCreationStatusMessage(data.firstName);
359 await contactsListPage.goto();
360 await expect(contactsListPage.getRowLocator(data.email))
361 .toBeVisible({ timeout: 10000 });
362}
363```
364
365### External Service Helpers
366
367For services like Mailpit, create helper classes:
368
369```typescript
370export class MailpitHelper {
371 readonly request: APIRequestContext;
372
373 constructor(request: APIRequestContext) {
374 this.request = request;
375 }
376
377 async waitForEmails(recipient: string, subject: string, expectedCount = 2) {
378 // Implementation
379 }
380}
381```
382
383## Test Configuration
384
385### Playwright Config
386
387Key configuration patterns:
388
389```typescript
390export default defineConfig({
391 testDir: './tests',
392 timeout: TIMEOUT, // Default: 30000
393 globalSetup: require.resolve('./tests/setup/global-setup'),
394 expect: {
395 timeout: EXPECT_TIMEOUT, // Default: 5000
396 },
397 fullyParallel: false, // Set to false for sequential execution
398 retries: CI ? 1 : 0,
399 workers: CI ? 1 : WORKERS,
400 use: {
401 baseURL: CRM_BASE_URL,
402 trace: 'on-first-retry',
403 screenshot: 'only-on-failure',
404 video: 'on-first-retry',
405 },
406});
407```
408
409### Environment Variables
410
411Required environment variables:
412
413- `CRM_BASE_URL` - Base URL for the application
414- `TEST_USER_EMAIL` - Test user email
415- `TEST_USER_PASSWORD` - Test user password
416- `STRAPI_TEST_ADMIN_EMAIL` - Strapi admin email
417- `STRAPI_TEST_ADMIN_PASSWORD` - Strapi admin password
418- `PLAYWRIGHT_WORKERS` - Number of workers (optional)
419- `PLAYWRIGHT_RETRIES` - Number of retries (optional)
420- `PLAYWRIGHT_TIMEOUT` - Test timeout (optional)
421
422## Test Maintenance
423
424### Handling Flaky Tests
425
4261. **Increase timeouts** for slow operations
4272. **Add explicit waits** for async operations
4283. **Use more stable locators** (role-based over CSS)
4294. **Retry logic** for known flaky operations:
430```typescript
431let langSelected = false;
432for (let i = 0; i < 3; i++) {
433 try {
434 await langOption.click();
435 langSelected = true;
436 break;
437 } catch (err) {
438 if (i === 2) throw err;
439 await this.page.waitForTimeout(100);
440 }
441}
442```
443
444### Skipping Tests
445
446- **Use `test.skip()`** for temporarily disabled tests:
447```typescript
448test.skip('should allow creating a journey with drag-and-drop', async () => {
449 // Test implementation
450});
451```
452
453- **Use `test.fail()`** for tests that are expected to fail (document why):
454```typescript
455// This test is marked as expected to fail due to a known application bug.
456test.fail('User can edit a list name (expected failure due to edit bug)', async () => {
457 // Test implementation
458});
459```
460
461## Code Style
462
463### Comments
464
465- **Add comments** explaining complex test logic
466- **Document test steps** in multi-step tests:
467```typescript
468// Step 1: Navigate to the login page
469await loginPage.goto();
470
471// Step 2: Fill in credentials
472await loginPage.fillCredentials(email, password);
473
474// Step 3: Submit and verify
475await loginPage.clickSignIn();
476await commonPage.expectDashboardVisible();
477```
478
479### Naming Conventions
480
481- **Test descriptions**: Use "User can..." or "should..." format
482- **Helper functions**: Use descriptive verb phrases
483- **Variables**: Use camelCase with descriptive names
484
485## Common Patterns
486
487### Row Operations
488
489```typescript
490// Get row locator
491const row = contactsListPage.getRowLocator(uniqueEmail);
492
493// Get row-specific elements
494const checkbox = contactsListPage.getCheckboxForRow(row);
495const link = contactsListPage.getLinkForRow(row, firstName);
496const deleteButton = contactsListPage.getDeleteButtonForRow(row);
497
498// Interact with row
499await checkbox.check();
500await link.click();
501await deleteButton.click();
502```
503
504### Modal/Dialog Operations
505
506```typescript
507// Wait for modal
508await modal.waitForDialogVisible();
509
510// Fill form
511await modal.fillAndSubmit(data);
512
513// Verify success
514await modal.expectCreationStatusMessage(data.name);
515```
516
517### Mass Actions
518
519```typescript
520// Select items
521await contactsListPage.getCheckboxForRow(row).check();
522
523// Open mass actions menu
524await contactsListPage.openMassActionsMenu();
525
526// Perform action
527await contactsListPage.clickDeleteMassAction();
528await contactsListPage.clickDeleteConfirmMassAction();
529```
530
531## Best Practices Summary
532
5331. ✅ **Use Page Object Model** for all page interactions
5342. ✅ **Prefer role-based locators** over CSS selectors
5353. ✅ **Use descriptive test names** and assertion messages
5364. ✅ **Generate unique test data** using Faker
5375. ✅ **Clean up test data** in finally blocks
5386. ✅ **Use helper functions** for common operations
5397. ✅ **Set appropriate timeouts** for async operations
5408. ✅ **Keep tests independent** and isolated
5419. ✅ **Document complex test logic** with comments
54210. ✅ **Handle errors gracefully** with try/finally blocks
543
544## Anti-Patterns to Avoid
545
5461. ❌ **Hardcoded test data** - Use Faker or environment variables
5472. ❌ **CSS selectors** - Prefer role-based or accessible selectors
5483. ❌ **Unnecessary waits** - Use Playwright's auto-waiting
5494. ❌ **Test dependencies** - Keep tests independent
5505. ❌ **Missing cleanup** - Always clean up test data
5516. ❌ **Vague assertions** - Include descriptive error messages
5527. ❌ **Duplicate code** - Extract to helper functions or POMs
5538. ❌ **Fragile locators** - Use stable, accessible selectors
554
nowtec/nowCRM · CLAUDE.md
@@ +1 @@
1# Code Style
2Read coding guidelines completed
3.cursor/rules
4
5
6# Services
7See all the services under **apps** directory
8
9
10# CI/CD - build pipleline
11.github/workflows/main.yaml - creates release, builds services, pushed to Github registry, sends notification to Telegram
@@ −1 +1 @@
1−---
2−description: Testing guidelines for NOWCRM
3−globs: ["**/*.spec.ts", "**/*.test.ts", "**/tests/**", "**/playwright.config.ts"]
4−alwaysApply: false
5−---
1+# Code Style
2+Read coding guidelines completed
3+.cursor/rules
64
7−# Testing Guidelines for NOWCRM
85
9−## Overview
6+# Services
7+See all the services under **apps** directory
108
11−NOWCRM uses **Playwright** for end-to-end (E2E) testing. All tests follow the **Page Object Model (POM)** pattern for maintainability and reusability.
129
13−## Test Structure
14−
15−### Directory Organization
16−
17−```
18−apps/nowcrm/tests/
19−├── *.spec.ts # Test specification files (numbered for execution order)
20−├── pages/ # Page Object Models (POMs)
21−│ ├── CommonPage.ts
22−│ ├── ContactsListPage.ts
23−│ └── ...
24−├── utils/ # Test utilities and helpers
25−│ ├── authHelper.ts
26−│ └── data.ts
27−├── setup/ # Setup and teardown scripts
28−│ ├── global-setup.ts
29−│ ├── create-users.ts
30−│ └── delete-users.ts
31−└── files/ # Test fixtures and data files
32−```
33−
34−### File Naming Conventions
35−
36−- **Test files**: Use numbered prefixes for execution order (e.g., `01Authentication.spec.ts`, `02Contacts.spec.ts`)
37−- **Page Objects**: Use descriptive names ending with `Page` or `Modal` (e.g., `ContactsListPage.ts`, `ContactCreateModal.ts`)
38−- **Utilities**: Use descriptive names (e.g., `authHelper.ts`, `data.ts`)
39−
40−## Page Object Model (POM) Pattern
41−
42−### Structure
43−
44−Every Page Object should follow this structure:
45−
46−```typescript
47−import { type Locator, type Page, expect } from '@playwright/test';
48−
49−export class PageName {
50− readonly page: Page;
51−
52− // Locators - declare as readonly
53− readonly elementName: Locator;
54−
55− constructor(page: Page) {
56− this.page = page;
57− // Initialize locators
58− this.elementName = page.getByRole('button', { name: 'Button Name' });
59− }
60−
61− // Actions - async methods that perform interactions
62− async performAction() {
63− await expect(this.elementName).toBeVisible();
64− await this.elementName.click();
65− }
66−
67− // Assertions - async methods that verify state
68− async expectSomethingVisible(timeout: number = 5000) {
69− await expect(this.elementName, 'Descriptive message').toBeVisible({ timeout });
70− }
71−}
72−```
73−
74−### Locator Best Practices
75−
76−1. **Prefer role-based selectors**:
77−```typescript
78−// ✅ Good - accessible and stable
79−this.createButton = page.getByRole('button', { name: 'Create' });
80−this.emailInput = page.getByRole('textbox', { name: 'Email' });
81−
82−// ❌ Avoid - fragile CSS selectors
83−this.createButton = page.locator('.btn-primary');
84−```
85−
86−2. **Scope locators within dialogs/modals**:
87−```typescript
88−constructor(page: Page) {
89− this.dialog = page.getByRole('dialog', { name: /Create Contact/i });
90− // Scope inputs within dialog
91− this.firstNameInput = this.dialog.getByRole('textbox', { name: 'First name' });
92−}
93−```
94−
95−3. **Use descriptive locator names**:
96−```typescript
97−// ✅ Good
98−readonly userMenuTrigger: Locator;
99−readonly deleteMassActionMenuItem: Locator;
100−
101−// ❌ Avoid
102−readonly btn1: Locator;
103−readonly menuItem: Locator;
104−```
105−
106−### Action Methods
107−
108−- **Naming**: Use verb phrases (e.g., `clickCreateButton`, `fillAndSubmit`, `openUserMenu`)
109−- **Wait for visibility**: Always wait for elements before interacting
110−- **Return values**: Return relevant data when needed (e.g., created entity ID)
111−
112−```typescript
113−async clickCreateButton() {
114− await expect(this.createButton, 'Create button should be visible').toBeVisible({ timeout: 20000 });
115− await this.createButton.click();
116−}
117−
118−async fillAndSubmit(data: ContactData) {
119− await this.firstNameInput.fill(data.firstName);
120− await this.lastNameInput.fill(data.lastName);
121− await this.emailInput.fill(data.email);
122− await this.createButton.click();
123−}
124−```
125−
126−### Assertion Methods
127−
128−- **Naming**: Prefix with `expect` (e.g., `expectDashboardVisible`, `expectStatusMessage`)
129−- **Descriptive messages**: Always include meaningful error messages
130−- **Configurable timeouts**: Accept timeout parameters with sensible defaults
131−
132−```typescript
133−async expectStatusMessage(message: string, timeout: number = 20000) {
134− const messageLocator = this.page.getByText(message, { exact: true });
135− await expect(messageLocator, `Status message "${message}" should be visible`)
136− .toBeVisible({ timeout });
137−}
138−
139−async expectDashboardVisible(timeout: number = 10000) {
140− await expect(this.page, 'URL should indicate CRM dashboard')
141− .toHaveURL(/\/crm$/, { timeout });
142−}
143−```
144−
145−## Test File Structure
146−
147−### Basic Template
148−
149−```typescript
150−import { test, expect } from '@playwright/test';
151−import { faker } from '@faker-js/faker';
152−
153−// Import Page Object Models
154−import { ContactsListPage } from './pages/ContactsListPage';
155−import { ContactCreateModal } from './pages/ContactCreateModal';
156−
157−// Import utilities
158−import { loginUser } from './utils/authHelper';
159−
160−test.describe('Feature Name', () => {
161− let pageObject1: ContactsListPage;
162− let pageObject2: ContactCreateModal;
163−
164− test.beforeEach(async ({ page }) => {
165− // Initialize POMs
166− pageObject1 = new ContactsListPage(page);
167− pageObject2 = new ContactCreateModal(page);
168−
169− // Common setup (e.g., login)
170− await loginUser(page);
171− await pageObject1.goto();
172− });
173−
174− test('User can perform action', async () => {
175− // Arrange - set up test data
176− const testData = {
177− firstName: faker.person.firstName(),
178− email: faker.internet.email()
179− };
180−
181− // Act - perform actions
182− await pageObject1.clickCreateButton();
183− await pageObject2.fillAndSubmit(testData);
184−
185− // Assert - verify results
186− await pageObject2.expectCreationStatusMessage(testData.firstName);
187− await expect(pageObject1.getRowLocator(testData.email))
188− .toBeVisible({ timeout: 10000 });
189− });
190−});
191−```
192−
193−### Test Organization
194−
195−1. **Use `test.describe` blocks** to group related tests
196−2. **Initialize POMs in `beforeEach`** for consistency
197−3. **Number test files** for execution order (e.g., `01Authentication.spec.ts`)
198−4. **One feature per describe block** (e.g., 'Contact Management', 'Authentication Flow')
199−
200−## Test Data Management
201−
202−### Using Faker for Test Data
203−
204−```typescript
205−import { faker } from '@faker-js/faker';
206−
207−// Generate unique test data
208−const contact = {
209− firstName: faker.person.firstName(),
210− lastName: faker.person.lastName(),
211− email: faker.internet.email({ provider: `test.${faker.string.alphanumeric(5)}.pw` }),
212− address: faker.location.streetAddress(),
213−};
214−```
215−
216−### Unique Identifiers
217−
218−- **Use timestamps or random strings** to ensure uniqueness:
219−```typescript
220−const uniqueEmail = `testuser+${Date.now()}@example.com`;
221−const uniqueListName = `List_${faker.string.alphanumeric(6)}`;
222−```
223−
224−### Test Credentials
225−
226−- **Store in environment variables** via `utils/data.ts`:
227−```typescript
228−export const testCredentials = {
229− email: process.env.TEST_USER_EMAIL || 'testuser@example.com',
230− password: process.env.TEST_USER_PASSWORD || 'StrongPassword123!',
231−};
232−```
233−
234−## Authentication and Setup
235−
236−### Global Setup
237−
238−- **Use `global-setup.ts`** for authentication state management
239−- **Save storage state** to avoid repeated logins:
240−```typescript
241−await page.context().storageState({ path: STORAGE_STATE_PATH });
242−```
243−
244−### Login Helper
245−
246−- **Create reusable login function** in `utils/authHelper.ts`:
247−```typescript
248−export async function loginUser(
249− page: Page,
250− postLoginUrlRegex: RegExp = /\/crm$/
251−): Promise<void> {
252− await page.goto('/en/auth');
253− await page.getByRole('textbox', { name: 'Email' }).fill(testCredentials.email);
254− await page.getByRole('textbox', { name: 'Password' }).fill(testCredentials.password);
255− await page.getByRole('button', { name: 'Sign in' }).click();
256− await expect(page).toHaveURL(postLoginUrlRegex, { timeout: 15000 });
257−}
258−```
259−
260−## Test Execution Patterns
261−
262−### Waiting Strategies
263−
264−1. **Use Playwright's auto-waiting**:
265−```typescript
266−// ✅ Good - Playwright waits automatically
267−await button.click();
268−
269−// ❌ Avoid - unnecessary manual waits
270−await page.waitForTimeout(1000);
271−await button.click();
272−```
273−
274−2. **Use explicit waits for async operations**:
275−```typescript
276−// ✅ Good - wait for specific condition
277−await expect(element).toBeVisible({ timeout: 10000 });
278−
279−// ✅ Good - wait for URL change
280−await expect(page).toHaveURL(/\/contacts\/\d+\/details/);
281−```
282−
283−3. **Use `waitForTimeout` sparingly** (only when necessary):
284−```typescript
285−// Only when waiting for async operations that can't be detected
286−await page.waitForTimeout(300); // Wait for dropdown to render
287−```
288−
289−### Error Handling
290−
291−- **Use try/finally blocks** for cleanup:
292−```typescript
293−test('User can perform action', async ({ page, request }) => {
294− const uniqueEmail = `test+${Date.now()}@example.com`;
295−
296− try {
297− // Test logic
298− await createTestUser(request, { email: uniqueEmail });
299− // ... test steps ...
300− } finally {
301− // Cleanup
302− await deleteUserFromStrapi(request, uniqueEmail);
303− await request.delete('http://localhost:8025/api/v1/messages');
304− }
305−});
306−```
307−
308−### Test Isolation
309−
310−- **Each test should be independent** - don't rely on test execution order
311−- **Clean up test data** after each test
312−- **Use unique identifiers** to avoid conflicts
313−
314−## Assertions
315−
316−### Best Practices
317−
318−1. **Always include descriptive messages**:
319−```typescript
320−// ✅ Good
321−await expect(contactRow, 'Contact row should contain correct email')
322− .toContainText(contact.email);
323−
324−// ❌ Avoid
325−await expect(contactRow).toContainText(contact.email);
326−```
327−
328−2. **Use appropriate matchers**:
329−```typescript
330−await expect(element).toBeVisible({ timeout: 10000 });
331−await expect(element).toHaveText('Expected Text');
332−await expect(element).toContainText('Partial Text');
333−await expect(page).toHaveURL(/\/crm$/);
334−await expect(locator).toHaveCount(1);
335−```
336−
337−3. **Set reasonable timeouts**:
338−```typescript
339−// Default timeout: 5000ms
340−await expect(element).toBeVisible();
341−
342−// Custom timeout for slow operations
343−await expect(element).toBeVisible({ timeout: 20000 });
344−```
345−
346−## Helper Functions
347−
348−### Reusable Test Helpers
349−
350−Create helper functions for common operations:
351−
352−```typescript
353−// In test file or utils
354−async function createContactViaUI(data: ContactData) {
355− await contactsListPage.clickCreateButton();
356− await contactCreateModal.waitForDialogVisible();
357− await contactCreateModal.fillAndSubmit(data);
358− await contactCreateModal.expectCreationStatusMessage(data.firstName);
359− await contactsListPage.goto();
360− await expect(contactsListPage.getRowLocator(data.email))
361− .toBeVisible({ timeout: 10000 });
362−}
363−```
364−
365−### External Service Helpers
366−
367−For services like Mailpit, create helper classes:
368−
369−```typescript
370−export class MailpitHelper {
371− readonly request: APIRequestContext;
372−
373− constructor(request: APIRequestContext) {
374− this.request = request;
375− }
376−
377− async waitForEmails(recipient: string, subject: string, expectedCount = 2) {
378− // Implementation
379− }
380−}
381−```
382−
383−## Test Configuration
384−
385−### Playwright Config
386−
387−Key configuration patterns:
388−
389−```typescript
390−export default defineConfig({
391− testDir: './tests',
392− timeout: TIMEOUT, // Default: 30000
393− globalSetup: require.resolve('./tests/setup/global-setup'),
394− expect: {
395− timeout: EXPECT_TIMEOUT, // Default: 5000
396− },
397− fullyParallel: false, // Set to false for sequential execution
398− retries: CI ? 1 : 0,
399− workers: CI ? 1 : WORKERS,
400− use: {
401− baseURL: CRM_BASE_URL,
402− trace: 'on-first-retry',
403− screenshot: 'only-on-failure',
404− video: 'on-first-retry',
405− },
406−});
407−```
408−
409−### Environment Variables
410−
411−Required environment variables:
412−
413−- `CRM_BASE_URL` - Base URL for the application
414−- `TEST_USER_EMAIL` - Test user email
415−- `TEST_USER_PASSWORD` - Test user password
416−- `STRAPI_TEST_ADMIN_EMAIL` - Strapi admin email
417−- `STRAPI_TEST_ADMIN_PASSWORD` - Strapi admin password
418−- `PLAYWRIGHT_WORKERS` - Number of workers (optional)
419−- `PLAYWRIGHT_RETRIES` - Number of retries (optional)
420−- `PLAYWRIGHT_TIMEOUT` - Test timeout (optional)
421−
422−## Test Maintenance
423−
424−### Handling Flaky Tests
425−
426−1. **Increase timeouts** for slow operations
427−2. **Add explicit waits** for async operations
428−3. **Use more stable locators** (role-based over CSS)
429−4. **Retry logic** for known flaky operations:
430−```typescript
431−let langSelected = false;
432−for (let i = 0; i < 3; i++) {
433− try {
434− await langOption.click();
435− langSelected = true;
436− break;
437− } catch (err) {
438− if (i === 2) throw err;
439− await this.page.waitForTimeout(100);
440− }
441−}
442−```
443−
444−### Skipping Tests
445−
446−- **Use `test.skip()`** for temporarily disabled tests:
447−```typescript
448−test.skip('should allow creating a journey with drag-and-drop', async () => {
449− // Test implementation
450−});
451−```
452−
453−- **Use `test.fail()`** for tests that are expected to fail (document why):
454−```typescript
455−// This test is marked as expected to fail due to a known application bug.
456−test.fail('User can edit a list name (expected failure due to edit bug)', async () => {
457− // Test implementation
458−});
459−```
460−
461−## Code Style
462−
463−### Comments
464−
465−- **Add comments** explaining complex test logic
466−- **Document test steps** in multi-step tests:
467−```typescript
468−// Step 1: Navigate to the login page
469−await loginPage.goto();
470−
471−// Step 2: Fill in credentials
472−await loginPage.fillCredentials(email, password);
473−
474−// Step 3: Submit and verify
475−await loginPage.clickSignIn();
476−await commonPage.expectDashboardVisible();
477−```
478−
479−### Naming Conventions
480−
481−- **Test descriptions**: Use "User can..." or "should..." format
482−- **Helper functions**: Use descriptive verb phrases
483−- **Variables**: Use camelCase with descriptive names
484−
485−## Common Patterns
486−
487−### Row Operations
488−
489−```typescript
490−// Get row locator
491−const row = contactsListPage.getRowLocator(uniqueEmail);
492−
493−// Get row-specific elements
494−const checkbox = contactsListPage.getCheckboxForRow(row);
495−const link = contactsListPage.getLinkForRow(row, firstName);
496−const deleteButton = contactsListPage.getDeleteButtonForRow(row);
497−
498−// Interact with row
499−await checkbox.check();
500−await link.click();
501−await deleteButton.click();
502−```
503−
504−### Modal/Dialog Operations
505−
506−```typescript
507−// Wait for modal
508−await modal.waitForDialogVisible();
509−
510−// Fill form
511−await modal.fillAndSubmit(data);
512−
513−// Verify success
514−await modal.expectCreationStatusMessage(data.name);
515−```
516−
517−### Mass Actions
518−
519−```typescript
520−// Select items
521−await contactsListPage.getCheckboxForRow(row).check();
522−
523−// Open mass actions menu
524−await contactsListPage.openMassActionsMenu();
525−
526−// Perform action
527−await contactsListPage.clickDeleteMassAction();
528−await contactsListPage.clickDeleteConfirmMassAction();
529−```
530−
531−## Best Practices Summary
532−
533−1. ✅ **Use Page Object Model** for all page interactions
534−2. ✅ **Prefer role-based locators** over CSS selectors
535−3. ✅ **Use descriptive test names** and assertion messages
536−4. ✅ **Generate unique test data** using Faker
537−5. ✅ **Clean up test data** in finally blocks
538−6. ✅ **Use helper functions** for common operations
539−7. ✅ **Set appropriate timeouts** for async operations
540−8. ✅ **Keep tests independent** and isolated
541−9. ✅ **Document complex test logic** with comments
542−10. ✅ **Handle errors gracefully** with try/finally blocks
543−
544−## Anti-Patterns to Avoid
545−
546−1. ❌ **Hardcoded test data** - Use Faker or environment variables
547−2. ❌ **CSS selectors** - Prefer role-based or accessible selectors
548−3. ❌ **Unnecessary waits** - Use Playwright's auto-waiting
549−4. ❌ **Test dependencies** - Keep tests independent
550−5. ❌ **Missing cleanup** - Always clean up test data
551−6. ❌ **Vague assertions** - Include descriptive error messages
552−7. ❌ **Duplicate code** - Extract to helper functions or POMs
553−8. ❌ **Fragile locators** - Use stable, accessible selectors
554−
10+# CI/CD - build pipleline
11+.github/workflows/main.yaml - creates release, builds services, pushed to Github registry, sends notification to Telegram
