| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 23 | 44 | 0% |
| Commands | 0 | 0 | 0 | — |
| Section tags | 3 | 3 | 4 | 30% |
What each file covers
Sections
0 shared · 23 only in A · 44 only in B- − Twenty Development Rules
- − Rules Overview
- − Core Guidelines
- − Code Quality
- − React Development
- − Testing & Quality
- − Internationalization
- − How Rules Work
- − Automatic Attachment
- − Manual Reference
- − Rule Types Used
- − Development Commands
- − Frontend Commands
- − Backend Commands
- − Usage Guidelines
- − For Developers
- − For AI Assistants
- − Contributing to Rules
- − Adding New Rules
- − Updating Existing Rules
- − Rule Format Reference
- − Rule Title
- − Migration from Legacy Format
- + 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
- + Code Style
- + Comments
- + Naming Conventions
- + Common Patterns
- + Row Operations
- + Modal/Dialog Operations
- + Mass Actions
- + Best Practices Summary
- + Anti-Patterns to Avoid
Commands
neither file has anySection tags
3 shared · 3 only in A · 4 only in B- − lint-format
- − types
- − database
- + setup
- + code-style
- + security
- + docs
- test
- architecture
- do-not
Line diff
nowtec/nowCRM · .cursor/rules/readme.mdc
@@ −1 @@
1---
2description: NOWCRM development rules and best practices
3globs: []
4alwaysApply: true
5---
6# Twenty Development Rules
7
8This directory contains NOWCRM's development guidelines and best practices in the modern Cursor Rules format (MDC). These rules are automatically applied based on file patterns and provide context-aware guidance to AI assistants.
9
10## Rules Overview
11
12### Core Guidelines
13- **architecture.mdc** - Project overview, technology stack, and infrastructure setup (Always Applied)
14
15### Code Quality
16- **typescript-guidelines.mdc** - TypeScript best practices and conventions (Auto-attached to .ts/.tsx files)
17- **code-style.mdc** - General coding standards and style guide (Auto-attached to code files)
18- **file-structure.mdc** - File and directory organization patterns (Auto-attached to config files)
19
20### React Development
21- **react-general-guidelines.mdc** - Core React development principles (Auto-attached to React files)
22
23### Testing & Quality
24- **testing-guidelines.mdc** - Testing strategies and best practices (Auto-attached to test files)
25
26### Internationalization
27- **translations.mdc** - Translation workflow and i18n setup (Auto-attached to locale files)
28
29## How Rules Work
30
31### Automatic Attachment
32Rules are automatically included in your AI context based on file patterns (globs). When you work on TypeScript files, the TypeScript guidelines are automatically loaded.
33
34### Manual Reference
35You can manually reference any rule using the `@ruleName` syntax:
36- `@react-general-guidelines` - Load React best practices
37- `@testing-guidelines` - Get testing recommendations
38
39### Rule Types Used
40- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc)
41- **Auto Attached** - Loaded when matching file patterns are referenced
42- **Agent Requested** - Available for AI to include when relevant
43- **Manual** - Only included when explicitly mentioned
44
45## Development Commands
46
47### Frontend Commands
48
49todo:
50
51### Backend Commands
52
53todo:
54
55## Usage Guidelines
56
57### For Developers
58- Rules are automatically applied based on file context
59- Check rule descriptions to understand when they're activated
60- Use manual references (`@ruleName`) for additional context
61- Keep rules updated as the codebase evolves
62
63### For AI Assistants
64- Rules provide consistent guidance across conversations
65- Use rule context to maintain coding standards
66- Reference specific rules when making recommendations
67- Apply rule principles in code suggestions and reviews
68
69## Contributing to Rules
70
71### Adding New Rules
721. Create a new `.mdc` file in this directory
732. Include proper metadata headers with description and globs
743. Write clear, actionable guidelines with examples
754. Test the rule with relevant file patterns
765. Update this README if needed
77
78### Updating Existing Rules
791. Modify the rule content while preserving metadata
802. Test changes with affected file patterns
813. Ensure consistency with other rules
824. Update examples and best practices as needed
83
84## Rule Format Reference
85
86Each rule file uses the MDC format with metadata:
87
88```markdown
89---
90description: Brief description of the rule's purpose
91globs: ["**/*.ts", "**/*.tsx"] # File patterns for auto-attachment
92alwaysApply: false # Whether to always include this rule
93---
94
95# Rule Title
96
97Rule content in Markdown format...
98```
99
100## Migration from Legacy Format
101
102The rules have been migrated from the legacy `.md` format to the modern `.mdc` format, providing:
103- Better context awareness through file pattern matching
104- Improved organization with metadata headers
105- More flexible rule application strategies
106- Enhanced integration with Cursor's AI features
107
108For the most up-to-date version of these guidelines, always refer to the files in this directory.
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
@@ −1 +1 @@
11 ---
2−description: NOWCRM development rules and best practices
3−globs: []
4−alwaysApply: true
2+description: Testing guidelines for NOWCRM
3+globs: ["**/*.spec.ts", "**/*.test.ts", "**/tests/**", "**/playwright.config.ts"]
4+alwaysApply: false
55 ---
6−# Twenty Development Rules
76
8−This directory contains NOWCRM's development guidelines and best practices in the modern Cursor Rules format (MDC). These rules are automatically applied based on file patterns and provide context-aware guidance to AI assistants.
7+# Testing Guidelines for NOWCRM
98
10−## Rules Overview
9+## Overview
1110
12−### Core Guidelines
13−- **architecture.mdc** - Project overview, technology stack, and infrastructure setup (Always Applied)
11+NOWCRM uses **Playwright** for end-to-end (E2E) testing. All tests follow the **Page Object Model (POM)** pattern for maintainability and reusability.
1412
15−### Code Quality
16−- **typescript-guidelines.mdc** - TypeScript best practices and conventions (Auto-attached to .ts/.tsx files)
17−- **code-style.mdc** - General coding standards and style guide (Auto-attached to code files)
18−- **file-structure.mdc** - File and directory organization patterns (Auto-attached to config files)
13+## Test Structure
1914
20−### React Development
21−- **react-general-guidelines.mdc** - Core React development principles (Auto-attached to React files)
15+### Directory Organization
2216
23−### Testing & Quality
24−- **testing-guidelines.mdc** - Testing strategies and best practices (Auto-attached to test files)
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+```
2533
26−### Internationalization
27−- **translations.mdc** - Translation workflow and i18n setup (Auto-attached to locale files)
34+### File Naming Conventions
2835
29−## How Rules Work
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`)
3039
31−### Automatic Attachment
32−Rules are automatically included in your AI context based on file patterns (globs). When you work on TypeScript files, the TypeScript guidelines are automatically loaded.
40+## Page Object Model (POM) Pattern
3341
34−### Manual Reference
35−You can manually reference any rule using the `@ruleName` syntax:
36−- `@react-general-guidelines` - Load React best practices
37−- `@testing-guidelines` - Get testing recommendations
42+### Structure
3843
39−### Rule Types Used
40−- **Always Applied** - Loaded in every context (architecture.mdc, README.mdc)
41−- **Auto Attached** - Loaded when matching file patterns are referenced
42−- **Agent Requested** - Available for AI to include when relevant
43−- **Manual** - Only included when explicitly mentioned
44+Every Page Object should follow this structure:
4445
45−## Development Commands
46+```typescript
47+import { type Locator, type Page, expect } from '@playwright/test';
4648
47−### Frontend Commands
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+```
4873
49−todo:
74+### Locator Best Practices
5075
51−### Backend Commands
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' });
5281
53−todo:
82+// ❌ Avoid - fragile CSS selectors
83+this.createButton = page.locator('.btn-primary');
84+```
5485
55−## Usage Guidelines
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+```
5694
57−### For Developers
58−- Rules are automatically applied based on file context
59−- Check rule descriptions to understand when they're activated
60−- Use manual references (`@ruleName`) for additional context
61−- Keep rules updated as the codebase evolves
95+3. **Use descriptive locator names**:
96+```typescript
97+// ✅ Good
98+readonly userMenuTrigger: Locator;
99+readonly deleteMassActionMenuItem: Locator;
62100
63−### For AI Assistants
64−- Rules provide consistent guidance across conversations
65−- Use rule context to maintain coding standards
66−- Reference specific rules when making recommendations
67−- Apply rule principles in code suggestions and reviews
101+// ❌ Avoid
102+readonly btn1: Locator;
103+readonly menuItem: Locator;
104+```
68105
69−## Contributing to Rules
106+### Action Methods
70107
71−### Adding New Rules
72−1. Create a new `.mdc` file in this directory
73−2. Include proper metadata headers with description and globs
74−3. Write clear, actionable guidelines with examples
75−4. Test the rule with relevant file patterns
76−5. Update this README if needed
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)
77111
78−### Updating Existing Rules
79−1. Modify the rule content while preserving metadata
80−2. Test changes with affected file patterns
81−3. Ensure consistency with other rules
82−4. Update examples and best practices as needed
112+```typescript
113+async clickCreateButton() {
114+ await expect(this.createButton, 'Create button should be visible').toBeVisible({ timeout: 20000 });
115+ await this.createButton.click();
116+}
83117
84−## Rule Format Reference
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+```
85125
86−Each rule file uses the MDC format with metadata:
126+### Assertion Methods
87127
88−```markdown
89−---
90−description: Brief description of the rule's purpose
91−globs: ["**/*.ts", "**/*.tsx"] # File patterns for auto-attachment
92−alwaysApply: false # Whether to always include this rule
93−---
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
94131
95−# Rule Title
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+}
96138
97−Rule content in Markdown format...
139+async expectDashboardVisible(timeout: number = 10000) {
140+ await expect(this.page, 'URL should indicate CRM dashboard')
141+ .toHaveURL(/\/crm$/, { timeout });
142+}
98143 ```
99144
100−## Migration from Legacy Format
145+## Test File Structure
101146
102−The rules have been migrated from the legacy `.md` format to the modern `.mdc` format, providing:
103−- Better context awareness through file pattern matching
104−- Improved organization with metadata headers
105−- More flexible rule application strategies
106−- Enhanced integration with Cursor's AI features
147+### Basic Template
107148
108−For the most up-to-date version of these guidelines, always refer to the files in this directory.
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+
