Cursor rule
.cursor/rules/testing-guidelines.mdcTesting guidelines for NOWCRM
Cursor rules
Quality
73/100
Scores the file, not the repository.Length
1,752 words
44 headings · 30 code blocksRepository
26
— · pushed 116 days agoLast changed
3 days ago
First indexed 3 days ago.1234567# Testing Guidelines for NOWCRM89## Overview1011NOWCRM uses **Playwright** for end-to-end (E2E) testing. All tests follow the **Page Object Model (POM)** pattern for maintainability and reusability.1213## Test Structure1415### Directory Organization1617```18apps/nowcrm/tests/19├── *.spec.ts # Test specification files (numbered for execution order)20├── pages/ # Page Object Models (POMs)21│ ├── CommonPage.ts22│ ├── ContactsListPage.ts23│ └── ...24├── utils/ # Test utilities and helpers25│ ├── authHelper.ts26│ └── data.ts27├── setup/ # Setup and teardown scripts28│ ├── global-setup.ts29│ ├── create-users.ts30│ └── delete-users.ts31└── files/ # Test fixtures and data files32```3334### File Naming Conventions3536- **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`)3940## Page Object Model (POM) Pattern4142### Structure4344Every Page Object should follow this structure:4546```typescript47import { type Locator, type Page, expect } from '@playwright/test';4849export class PageName {50 readonly page: Page;5152 // Locators - declare as readonly53 readonly elementName: Locator;5455 constructor(page: Page) {56 this.page = page;57 // Initialize locators58 this.elementName = page.getByRole('button', { name: 'Button Name' });59 }6061 // Actions - async methods that perform interactions62 async performAction() {63 await expect(this.elementName).toBeVisible();64 await this.elementName.click();65 }6667 // Assertions - async methods that verify state68 async expectSomethingVisible(timeout: number = 5000) {69 await expect(this.elementName, 'Descriptive message').toBeVisible({ timeout });70 }71}72```7374### Locator Best Practices75761. **Prefer role-based selectors**:77```typescript78// ✅ Good - accessible and stable79this.createButton = page.getByRole('button', { name: 'Create' });80this.emailInput = page.getByRole('textbox', { name: 'Email' });8182// ❌ Avoid - fragile CSS selectors83this.createButton = page.locator('.btn-primary');84```85862. **Scope locators within dialogs/modals**:87```typescript88constructor(page: Page) {89 this.dialog = page.getByRole('dialog', { name: /Create Contact/i });90 // Scope inputs within dialog91 this.firstNameInput = this.dialog.getByRole('textbox', { name: 'First name' });92}93```94953. **Use descriptive locator names**:96```typescript97// ✅ Good98readonly userMenuTrigger: Locator;99readonly deleteMassActionMenuItem: Locator;100101// ❌ Avoid102readonly btn1: Locator;103readonly menuItem: Locator;104```105106### Action Methods107108- **Naming**: Use verb phrases (e.g., `clickCreateButton`, `fillAndSubmit`, `openUserMenu`)109- **Wait for visibility**: Always wait for elements before interacting110- **Return values**: Return relevant data when needed (e.g., created entity ID)111112```typescript113async clickCreateButton() {114 await expect(this.createButton, 'Create button should be visible').toBeVisible({ timeout: 20000 });115 await this.createButton.click();116}117118async 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```125126### Assertion Methods127128- **Naming**: Prefix with `expect` (e.g., `expectDashboardVisible`, `expectStatusMessage`)129- **Descriptive messages**: Always include meaningful error messages130- **Configurable timeouts**: Accept timeout parameters with sensible defaults131132```typescript133async 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}138139async expectDashboardVisible(timeout: number = 10000) {140 await expect(this.page, 'URL should indicate CRM dashboard')141 .toHaveURL(/\/crm$/, { timeout });142}143```144145## Test File Structure146147### Basic Template148149```typescript150import { test, expect } from '@playwright/test';151import { faker } from '@faker-js/faker';152153// Import Page Object Models154import { ContactsListPage } from './pages/ContactsListPage';155import { ContactCreateModal } from './pages/ContactCreateModal';156157// Import utilities158import { loginUser } from './utils/authHelper';159160test.describe('Feature Name', () => {161 let pageObject1: ContactsListPage;162 let pageObject2: ContactCreateModal;163164 test.beforeEach(async ({ page }) => {165 // Initialize POMs166 pageObject1 = new ContactsListPage(page);167 pageObject2 = new ContactCreateModal(page);168169 // Common setup (e.g., login)170 await loginUser(page);171 await pageObject1.goto();172 });173174 test('User can perform action', async () => {175 // Arrange - set up test data176 const testData = {177 firstName: faker.person.firstName(),178 email: faker.internet.email()179 };180181 // Act - perform actions182 await pageObject1.clickCreateButton();183 await pageObject2.fillAndSubmit(testData);184185 // Assert - verify results186 await pageObject2.expectCreationStatusMessage(testData.firstName);187 await expect(pageObject1.getRowLocator(testData.email))188 .toBeVisible({ timeout: 10000 });189 });190});191```192193### Test Organization1941951. **Use `test.describe` blocks** to group related tests1962. **Initialize POMs in `beforeEach`** for consistency1973. **Number test files** for execution order (e.g., `01Authentication.spec.ts`)1984. **One feature per describe block** (e.g., 'Contact Management', 'Authentication Flow')199200## Test Data Management201202### Using Faker for Test Data203204```typescript205import { faker } from '@faker-js/faker';206207// Generate unique test data208const 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```215216### Unique Identifiers217218- **Use timestamps or random strings** to ensure uniqueness:219```typescript220const uniqueEmail = `testuser+${Date.now()}@example.com`;221const uniqueListName = `List_${faker.string.alphanumeric(6)}`;222```223224### Test Credentials225226- **Store in environment variables** via `utils/data.ts`:227```typescript228export const testCredentials = {229 email: process.env.TEST_USER_EMAIL || 'testuser@example.com',230 password: process.env.TEST_USER_PASSWORD || 'StrongPassword123!',231};232```233234## Authentication and Setup235236### Global Setup237238- **Use `global-setup.ts`** for authentication state management239- **Save storage state** to avoid repeated logins:240```typescript241await page.context().storageState({ path: STORAGE_STATE_PATH });242```243244### Login Helper245246- **Create reusable login function** in `utils/authHelper.ts`:247```typescript248export 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```259260## Test Execution Patterns261262### Waiting Strategies2632641. **Use Playwright's auto-waiting**:265```typescript266// ✅ Good - Playwright waits automatically267await button.click();268269// ❌ Avoid - unnecessary manual waits270await page.waitForTimeout(1000);271await button.click();272```2732742. **Use explicit waits for async operations**:275```typescript276// ✅ Good - wait for specific condition277await expect(element).toBeVisible({ timeout: 10000 });278279// ✅ Good - wait for URL change280await expect(page).toHaveURL(/\/contacts\/\d+\/details/);281```2822833. **Use `waitForTimeout` sparingly** (only when necessary):284```typescript285// Only when waiting for async operations that can't be detected286await page.waitForTimeout(300); // Wait for dropdown to render287```288289### Error Handling290291- **Use try/finally blocks** for cleanup:292```typescript293test('User can perform action', async ({ page, request }) => {294 const uniqueEmail = `test+${Date.now()}@example.com`;295296 try {297 // Test logic298 await createTestUser(request, { email: uniqueEmail });299 // ... test steps ...300 } finally {301 // Cleanup302 await deleteUserFromStrapi(request, uniqueEmail);303 await request.delete('http://localhost:8025/api/v1/messages');304 }305});306```307308### Test Isolation309310- **Each test should be independent** - don't rely on test execution order311- **Clean up test data** after each test312- **Use unique identifiers** to avoid conflicts313314## Assertions315316### Best Practices3173181. **Always include descriptive messages**:319```typescript320// ✅ Good321await expect(contactRow, 'Contact row should contain correct email')322 .toContainText(contact.email);323324// ❌ Avoid325await expect(contactRow).toContainText(contact.email);326```3273282. **Use appropriate matchers**:329```typescript330await 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```3363373. **Set reasonable timeouts**:338```typescript339// Default timeout: 5000ms340await expect(element).toBeVisible();341342// Custom timeout for slow operations343await expect(element).toBeVisible({ timeout: 20000 });344```345346## Helper Functions347348### Reusable Test Helpers349350Create helper functions for common operations:351352```typescript353// In test file or utils354async 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```364365### External Service Helpers366367For services like Mailpit, create helper classes:368369```typescript370export class MailpitHelper {371 readonly request: APIRequestContext;372373 constructor(request: APIRequestContext) {374 this.request = request;375 }376377 async waitForEmails(recipient: string, subject: string, expectedCount = 2) {378 // Implementation379 }380}381```382383## Test Configuration384385### Playwright Config386387Key configuration patterns:388389```typescript390export default defineConfig({391 testDir: './tests',392 timeout: TIMEOUT, // Default: 30000393 globalSetup: require.resolve('./tests/setup/global-setup'),394 expect: {395 timeout: EXPECT_TIMEOUT, // Default: 5000396 },397 fullyParallel: false, // Set to false for sequential execution398 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```408409### Environment Variables410411Required environment variables:412413- `CRM_BASE_URL` - Base URL for the application414- `TEST_USER_EMAIL` - Test user email415- `TEST_USER_PASSWORD` - Test user password416- `STRAPI_TEST_ADMIN_EMAIL` - Strapi admin email417- `STRAPI_TEST_ADMIN_PASSWORD` - Strapi admin password418- `PLAYWRIGHT_WORKERS` - Number of workers (optional)419- `PLAYWRIGHT_RETRIES` - Number of retries (optional)420- `PLAYWRIGHT_TIMEOUT` - Test timeout (optional)421422## Test Maintenance423424### Handling Flaky Tests4254261. **Increase timeouts** for slow operations4272. **Add explicit waits** for async operations4283. **Use more stable locators** (role-based over CSS)4294. **Retry logic** for known flaky operations:430```typescript431let 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```443444### Skipping Tests445446- **Use `test.skip()`** for temporarily disabled tests:447```typescript448test.skip('should allow creating a journey with drag-and-drop', async () => {449 // Test implementation450});451```452453- **Use `test.fail()`** for tests that are expected to fail (document why):454```typescript455// 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 implementation458});459```460461## Code Style462463### Comments464465- **Add comments** explaining complex test logic466- **Document test steps** in multi-step tests:467```typescript468// Step 1: Navigate to the login page469await loginPage.goto();470471// Step 2: Fill in credentials472await loginPage.fillCredentials(email, password);473474// Step 3: Submit and verify475await loginPage.clickSignIn();476await commonPage.expectDashboardVisible();477```478479### Naming Conventions480481- **Test descriptions**: Use "User can..." or "should..." format482- **Helper functions**: Use descriptive verb phrases483- **Variables**: Use camelCase with descriptive names484485## Common Patterns486487### Row Operations488489```typescript490// Get row locator491const row = contactsListPage.getRowLocator(uniqueEmail);492493// Get row-specific elements494const checkbox = contactsListPage.getCheckboxForRow(row);495const link = contactsListPage.getLinkForRow(row, firstName);496const deleteButton = contactsListPage.getDeleteButtonForRow(row);497498// Interact with row499await checkbox.check();500await link.click();501await deleteButton.click();502```503504### Modal/Dialog Operations505506```typescript507// Wait for modal508await modal.waitForDialogVisible();509510// Fill form511await modal.fillAndSubmit(data);512513// Verify success514await modal.expectCreationStatusMessage(data.name);515```516517### Mass Actions518519```typescript520// Select items521await contactsListPage.getCheckboxForRow(row).check();522523// Open mass actions menu524await contactsListPage.openMassActionsMenu();525526// Perform action527await contactsListPage.clickDeleteMassAction();528await contactsListPage.clickDeleteConfirmMassAction();529```530531## Best Practices Summary5325331. ✅ **Use Page Object Model** for all page interactions5342. ✅ **Prefer role-based locators** over CSS selectors5353. ✅ **Use descriptive test names** and assertion messages5364. ✅ **Generate unique test data** using Faker5375. ✅ **Clean up test data** in finally blocks5386. ✅ **Use helper functions** for common operations5397. ✅ **Set appropriate timeouts** for async operations5408. ✅ **Keep tests independent** and isolated5419. ✅ **Document complex test logic** with comments54210. ✅ **Handle errors gracefully** with try/finally blocks543544## Anti-Patterns to Avoid5455461. ❌ **Hardcoded test data** - Use Faker or environment variables5472. ❌ **CSS selectors** - Prefer role-based or accessible selectors5483. ❌ **Unnecessary waits** - Use Playwright's auto-waiting5494. ❌ **Test dependencies** - Keep tests independent5505. ❌ **Missing cleanup** - Always clean up test data5516. ❌ **Vague assertions** - Include descriptive error messages5527. ❌ **Duplicate code** - Extract to helper functions or POMs5538. ❌ **Fragile locators** - Use stable, accessible selectors554
Also in nowtec/nowCRM
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| nowtec/nowCRM.cursor/rules/architecture.mdc · 26 | Cursor rules | arch | 54/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/code-style.mdc · 26 | Cursor rules | lint-formatstylearchdocs | 62/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/file-structure.mdc · 26 | Cursor rules | buildstylearch | 70/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/react-general-guidelines.mdc · 26 | Cursor rules | archuiperformancedo-not | 61/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/readme.mdc · 26 | Cursor rules | testlint-formatarchtypes+2 | 73/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/translations.mdc · 26 | Cursor rules | stylearchagent-behaviour | 62/100 | 3 days ago | |
| nowtec/nowCRM.cursor/rules/typescript-guidelines.mdc · 26 | Cursor rules | styletypesui | 58/100 | 3 days ago | |
| nowtec/nowCRMCLAUDE.md · 26 | CLAUDE.md | buildstyledeployment | 21/100 | 3 days ago |
Diff against .cursor/rules/architecture.mdc Diff against .cursor/rules/code-style.mdc Diff against .cursor/rules/file-structure.mdc Diff against .cursor/rules/react-general-guidelines.mdc Diff against .cursor/rules/readme.mdc Diff against .cursor/rules/translations.mdc Diff against .cursor/rules/typescript-guidelines.mdc Diff against CLAUDE.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 45 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 3 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 3 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 3 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 3 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 45 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 3 days ago |
