Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions .cursor/rules/e2e-rules.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
---
globs: core-web/apps/dotcms-ui-e2e/**/*.spec.ts
alwaysApply: false
---

# Page Object Model (POM) Conventions for dotCMS E2E Tests

## Overview

This project follows the **Page Object Model (POM)** pattern for all E2E tests. This ensures maintainability, reusability, and clear separation of concerns.

## Directory Structure

```
src/
├── pages/ # Page Objects (one class per page)
│ ├── login.page.ts
│ ├── dashboard.page.ts
│ └── content.page.ts
├── components/ # Reusable UI components
│ ├── sideMenu.component.ts
│ ├── header.component.ts
│ └── modal.component.ts
├── tests/ # Test files that use Page Objects
│ ├── login/
│ ├── content/
│ └── navigation/
├── utils/ # Shared utilities and helpers
│ └── utils.ts
└── config/ # Configuration files
└── environments.ts
```

## POM Rules

### 1. Page Objects

- **One class per page** - Each page has its own Page Object class
- **Encapsulate all page interactions** - All `page.fill()`, `page.click()`, etc. should be in Page Objects
- **Return meaningful data** - Methods should return relevant information when needed
- **Handle environment differences** - Page Objects should adapt to different environments (dev/ci)
- **ALWAYS use data-testid selectors** - Use `page.getByTestId()` instead of `page.locator()` with CSS selectors

### 2. Components

- **Reusable UI elements** - Components that appear across multiple pages
- **Self-contained logic** - Each component manages its own state and interactions
- **Composable** - Components can be used within Page Objects
- **ALWAYS use data-testid selectors** - Use `page.getByTestId()` for all element interactions

### 3. Tests

- **Use Page Objects only** - Never interact directly with the DOM in tests
- **Descriptive test names** - Clear, readable test descriptions
- **One test per scenario** - Each test should verify one specific behavior
- **Use test data files** - Centralize test data in separate files

## Selector Rules

### ✅ ALWAYS Use data-testid

```typescript
// CORRECT - Use data-testid selectors
await this.page.getByTestId("userNameInput").click();
await this.page.getByTestId("userNameInput").fill(username);
await this.page.getByTestId("password").fill(password);
await this.page.getByTestId("submitButton").click();
```

### ❌ NEVER Use CSS selectors

```typescript
// WRONG - Don't use CSS selectors
await this.page.locator('input[id="userId"]').fill(username);
await this.page.locator('button[id="loginButton"]').click();
await this.page.locator(".login-form input").fill(username);
```

### Why data-testid?

1. **More stable** - Not affected by CSS class changes or styling updates
2. **More specific** - Designed specifically for testing
3. **Better performance** - Playwright's `getByTestId()` is optimized
4. **Clearer intent** - Makes it obvious the element is for testing

## Code Examples

### ✅ Correct POM Implementation

```typescript
// pages/login.page.ts
export class LoginPage {
constructor(private page: Page) {}

async login(username: string, password: string): Promise<void> {
const currentEnv = process.env["CURRENT_ENV"] || "dev";
const loginUrl =
currentEnv === "ci" ? "/login/" : "/dotAdmin/#/public/login";

await this.page.goto(loginUrl);
await this.page.waitForLoadState();

// Use data-testid selectors
await this.page.getByTestId("userNameInput").click();
await this.page.getByTestId("userNameInput").fill(username);
await this.page.getByTestId("userNameInput").press("Tab");
await this.page.getByTestId("password").fill(password);
await this.page.getByTestId("submitButton").click();
}

async isLoggedIn(): Promise<boolean> {
const currentUrl = this.page.url();
return (
!currentUrl.includes("/login/") && !currentUrl.includes("/public/login")
);
}
}

// tests/login/login.spec.ts
test("User can login with valid credentials", async ({ page }) => {
const loginPage = new LoginPage(page);

await loginPage.login("admin@dotcms.com", "admin");

expect(await loginPage.isLoggedIn()).toBe(true);
});
```

### ❌ Incorrect Implementation

```typescript
// DON'T DO THIS - Direct DOM interaction in tests
test("User can login", async ({ page }) => {
await page.goto("/login/");
await page.fill('input[id="userId"]', "admin@dotcms.com");
await page.fill('input[id="password"]', "admin");
await page.click('button[id="loginButton"]');
});

// DON'T DO THIS - Using CSS selectors in Page Objects
export class LoginPage {
async login(username: string, password: string) {
await this.page.locator('input[id="userId"]').fill(username);
await this.page.locator('input[id="password"]').fill(password);
await this.page.locator('button[id="loginButton"]').click();
}
}
```

## Environment Handling

### Page Objects should handle environment differences:

```typescript
export class LoginPage {
private getLoginUrl(): string {
const currentEnv = process.env["CURRENT_ENV"] || "dev";
return currentEnv === "ci" ? "/login/" : "/dotAdmin/#/public/login";
}
}
```

## Test Data Management

### Centralize test data:

```typescript
// tests/login/credentialsData.ts
export const validCredentials = [
{ username: "admin@dotcms.com", password: "admin" },
{ username: "test@dotcms.com", password: "test" },
];

export const invalidCredentials = [
{ username: "wrong@dotcms.com", password: "wrong" },
];
```

## Naming Conventions

- **Page Objects**: `[PageName].page.ts` (e.g., `login.page.ts`)
- **Components**: `[ComponentName].component.ts` (e.g., `sideMenu.component.ts`)
- **Test Files**: `[FeatureName].spec.ts` (e.g., `login.spec.ts`)
- **Test Data**: `[FeatureName]Data.ts` (e.g., `credentialsData.ts`)

## Best Practices

1. **Keep Page Objects focused** - Each Page Object should handle one page only
2. **Use meaningful method names** - `login()`, `navigateToContent()`, `verifyUserIsLoggedIn()`
3. **Handle waits properly** - Use appropriate waits in Page Objects
4. **Return useful data** - Methods should return information that tests need
5. **Keep tests simple** - Tests should be easy to read and understand
6. **Use TypeScript** - Leverage type safety for better maintainability
7. **ALWAYS use data-testid** - Never use CSS selectors, always use `page.getByTestId()`

## Migration Guidelines

When migrating tests from Maven E2E:

1. **Identify pages** - Create Page Objects for each page
2. **Extract components** - Identify reusable UI components
3. **Centralize data** - Move test data to dedicated files
4. **Update tests** - Refactor tests to use Page Objects
5. **Handle environments** - Ensure Page Objects work in both dev and ci modes
6. **Convert selectors** - Replace all CSS selectors with data-testid selectors

## Using Playwright Codegen

When using Playwright's codegen to generate tests:

1. **Run codegen**: `npx playwright codegen http://localhost:8080/dotAdmin/#/public/login`
2. **Copy the generated selectors** - Use the `getByTestId()` calls from codegen
3. **Update Page Objects** - Replace old selectors with the new data-testid selectors
4. **Test the changes** - Verify the new selectors work correctly

---

**Remember**:

- Always use Page Objects for E2E tests
- Never interact directly with the DOM in test files
- ALWAYS use `data-testid` selectors with `page.getByTestId()`
- Never use CSS selectors like `page.locator('input[id="..."]')`
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ dotsecure
/core-web/out*/
dist-docs/
**/test-reports/
core-web/apps/dotcms-ui-e2e/playwright-report
core-web/apps/dotcms-ui-e2e/test-results

#env
/core-web/**/.env
Expand Down
8 changes: 8 additions & 0 deletions core-web/.cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"nx-mcp": {
"command": "npx",
"args": ["-y", "nx-mcp@latest"]
}
}
}
3 changes: 0 additions & 3 deletions core-web/apps/dotcdn/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ export default {
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {},
coverageReporters: [['lcovonly', { file: 'TEST-dotcdn.lcov' }]],
reporters: [
'default',
['github-actions', { silent: false }],
[
'jest-junit',
{
Expand Down
Loading