RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/nowtec-nowcrm-cursor-rules-testing-guidelines ↔ nowtec-nowcrm-claude

Comparison

A · Cursor rules · nowtec/nowCRMB · CLAUDE.md · nowtec/nowCRM
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections14322%
Commands000—
Section tags16211%

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 any

Section tags

1 shared · 6 only in A · 2 only in B
  • − setup
  • − test
  • − architecture
  • − security
  • − do-not
  • − docs
  • + build
  • + deployment
  •   code-style

Line diff

+7 added−550 removed4 unchanged0.7% identical
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
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack