symfonia-ai-tools 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +489 -0
- package/bin/cli.mjs +35 -0
- package/lib/installer.mjs +495 -0
- package/lib/questions.mjs +332 -0
- package/lib/ui.mjs +76 -0
- package/lib/utils.mjs +231 -0
- package/package.json +26 -0
- package/templates/base/CLAUDE.md +34 -0
- package/templates/base/_ai/_guidelines_header.md +70 -0
- package/templates/base/_ai/context/README.md +20 -0
- package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
- package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
- package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
- package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
- package/templates/base/_ai/skills/README.md +80 -0
- package/templates/base/_ai/skills/TEMPLATE.md +106 -0
- package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
- package/templates/base/_ai/skills/debug/SKILL.md +93 -0
- package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
- package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
- package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
- package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
- package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
- package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
- package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
- package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
- package/templates/base/_claude/settings.local.json +37 -0
- package/templates/base/_cursor/rules/global.mdc +7 -0
- package/templates/base/_editorconfig +18 -0
- package/templates/base/_gemini/settings.json +3 -0
- package/templates/base/_github/copilot-instructions.md +1 -0
- package/templates/base/_github/pull_request_template.md +23 -0
- package/templates/base/_gitignore +22 -0
- package/templates/base/_junie/guidelines.md +1 -0
- package/templates/base/commit-instructions.md +92 -0
- package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
- package/templates/packs/docker/_guidelines.md +10 -0
- package/templates/packs/docker/pack.json +8 -0
- package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
- package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
- package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
- package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
- package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
- package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
- package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
- package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
- package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
- package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
- package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
- package/templates/packs/laravel/_guidelines.md +25 -0
- package/templates/packs/laravel/pack.json +6 -0
- package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
- package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
- package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
- package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
- package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
- package/templates/packs/playwright/_guidelines.md +12 -0
- package/templates/packs/playwright/pack.json +9 -0
- package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
- package/templates/packs/storybook/pack.json +6 -0
- package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
- package/templates/packs/vitest/pack.json +6 -0
- package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
- package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
- package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
- package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
- package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
- package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
- package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
- package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
- package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
- package/templates/packs/vue3/_guidelines.md +45 -0
- package/templates/packs/vue3/pack.json +11 -0
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playwright
|
|
3
|
+
description: Expert guidance for writing, maintaining, debugging, and reviewing Playwright E2E tests. Provides comprehensive knowledge about Page Object Model patterns, test organization, fixture management, data-testid selectors, tagging strategies, and best practices for a structured testing framework.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playwright E2E Testing - {{PROJECT_NAME}}
|
|
7
|
+
|
|
8
|
+
Expert skill for writing, maintaining, and reviewing end-to-end tests using Playwright in the {{PROJECT_NAME}} frontend project. This skill provides specialized knowledge about the project's testing architecture, conventions, and best practices.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Use this skill when the user:
|
|
13
|
+
|
|
14
|
+
- **Asks to CONVERT recorded test from `tmp/` to production** (from `playwright-record` skill)
|
|
15
|
+
- **AI Agent MUST**: Convert test AND run it to verify it works
|
|
16
|
+
- **AI Agent MUST**: Debug failures and fix issues
|
|
17
|
+
- **AI Agent MUST**: Re-run until test passes
|
|
18
|
+
- **AI Agent MUST NOT**: Mark conversion as done without running test
|
|
19
|
+
- Asks to write new Playwright E2E tests
|
|
20
|
+
- Needs help debugging or fixing existing Playwright tests
|
|
21
|
+
- Wants to understand test structure or Page Object Models
|
|
22
|
+
- Asks about test organization, naming conventions, or file structure
|
|
23
|
+
- Needs guidance on selectors (data-testid patterns)
|
|
24
|
+
- Wants to configure test execution (tags, browsers, workers)
|
|
25
|
+
- Requests test fixtures or test data management
|
|
26
|
+
- Asks about running tests locally or in Docker
|
|
27
|
+
- Needs help with authentication flows or session management
|
|
28
|
+
- Wants to review test code quality or refactor tests
|
|
29
|
+
|
|
30
|
+
**CRITICAL for AI Agents:**
|
|
31
|
+
- **NEVER** consider conversion complete without running the test
|
|
32
|
+
- **ALWAYS** use `make playwright-test` or `npx playwright test` to verify
|
|
33
|
+
- **ALWAYS** check exit code and output for failures
|
|
34
|
+
- **ALWAYS** debug and fix issues if test fails
|
|
35
|
+
- **ALWAYS** re-run until test passes (exit code 0)
|
|
36
|
+
|
|
37
|
+
## Converting Recorded Tests from tmp/
|
|
38
|
+
|
|
39
|
+
**CRITICAL WORKFLOW**: When converting tests recorded by `playwright-record` skill:
|
|
40
|
+
|
|
41
|
+
### Step 1: Check Existing Page Objects FIRST
|
|
42
|
+
|
|
43
|
+
**BEFORE creating ANY new Page Objects**, you MUST:
|
|
44
|
+
|
|
45
|
+
1. **Search** `{{PLAYWRIGHT_DIR}}/pages/` for existing POMs
|
|
46
|
+
2. **Check** `{{PLAYWRIGHT_DIR}}/pages/index.ts` for available exports
|
|
47
|
+
3. **Analyze** if existing POM can be reused or extended
|
|
48
|
+
4. **Only create new POM** if NO suitable one exists
|
|
49
|
+
|
|
50
|
+
**Example check**:
|
|
51
|
+
```bash
|
|
52
|
+
# Check if LoginPage exists
|
|
53
|
+
ls {{PLAYWRIGHT_DIR}}/pages/login/LoginPage.ts
|
|
54
|
+
|
|
55
|
+
# Check if a specific page object exists
|
|
56
|
+
ls {{PLAYWRIGHT_DIR}}/pages/{{MODULE_PATH}}/SomePage.ts
|
|
57
|
+
|
|
58
|
+
# Check index.ts exports
|
|
59
|
+
grep "export.*Page" {{PLAYWRIGHT_DIR}}/pages/index.ts
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Step 2: Reuse Existing POMs When Possible
|
|
63
|
+
|
|
64
|
+
**If Page Object EXISTS** -> **REUSE IT**:
|
|
65
|
+
```typescript
|
|
66
|
+
// CORRECT: Reuse existing LoginPage
|
|
67
|
+
import { LoginPage } from '../../pages/login/LoginPage';
|
|
68
|
+
|
|
69
|
+
test('My test', async ({ page }) => {
|
|
70
|
+
const loginPage = new LoginPage(page);
|
|
71
|
+
await loginPage.login(email, password);
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**If Page Object DOES NOT EXIST** -> **CREATE NEW ONE**:
|
|
76
|
+
```typescript
|
|
77
|
+
// CORRECT: Create new POM only if nothing exists
|
|
78
|
+
export class SomeModulePage {
|
|
79
|
+
// ... new implementation
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 3: Full Conversion Workflow (3 Sub-Steps)
|
|
84
|
+
|
|
85
|
+
#### Step 3A: Automatic JavaScript to TypeScript Conversion
|
|
86
|
+
|
|
87
|
+
**AI Agent MUST automatically transform raw Codegen output:**
|
|
88
|
+
|
|
89
|
+
**Before (Raw JavaScript from tmp/)**:
|
|
90
|
+
```javascript
|
|
91
|
+
test('test', async ({ page }) => {
|
|
92
|
+
await page.goto('{{BASE_URL}}/login');
|
|
93
|
+
await page.getByPlaceholder('Email').fill('test@example.com');
|
|
94
|
+
await page.getByRole('button', { name: 'Login' }).click();
|
|
95
|
+
// ... more raw interactions
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**After (Converted TypeScript with POMs)**:
|
|
100
|
+
```typescript
|
|
101
|
+
import { expect, test } from '@playwright/test';
|
|
102
|
+
import { LoginPage, DashboardPage } from '../../pages';
|
|
103
|
+
import { CredentialsLoader } from '../../fixtures/loader';
|
|
104
|
+
import { clearBrowserStorage } from '../../utils/test-helpers';
|
|
105
|
+
|
|
106
|
+
test.describe('05_ModuleName - Tests', { tag: ['@module-tag', '@verified'] }, () => {
|
|
107
|
+
let loginPage: LoginPage;
|
|
108
|
+
let dashboardPage: DashboardPage;
|
|
109
|
+
|
|
110
|
+
test.beforeEach(async ({ page }) => {
|
|
111
|
+
loginPage = new LoginPage(page);
|
|
112
|
+
dashboardPage = new DashboardPage(page);
|
|
113
|
+
|
|
114
|
+
await page.goto('/login');
|
|
115
|
+
await clearBrowserStorage(page);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('05_01_01: Test scenario description', async ({ page }) => {
|
|
119
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
120
|
+
|
|
121
|
+
await test.step('Login', async () => {
|
|
122
|
+
await loginPage.login(credentials.email.right, credentials.password.right);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await test.step('Navigate to target page', async () => {
|
|
126
|
+
await dashboardPage.navigateToModule();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await test.step('Verify success', async () => {
|
|
130
|
+
await expect(page.getByText('Success')).toBeVisible();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Conversion checklist**:
|
|
137
|
+
- Import proper types and POMs
|
|
138
|
+
- Add test.describe() with tags
|
|
139
|
+
- Initialize POMs in beforeEach
|
|
140
|
+
- Add clearBrowserStorage()
|
|
141
|
+
- Use CredentialsLoader for credentials
|
|
142
|
+
- Add test.step() for each logical action
|
|
143
|
+
- Use Page Object methods instead of raw interactions
|
|
144
|
+
- Add proper assertions (expect)
|
|
145
|
+
|
|
146
|
+
#### Step 3B: Update pages/index.ts for New POMs
|
|
147
|
+
|
|
148
|
+
**When creating a NEW Page Object**, update exports file:
|
|
149
|
+
|
|
150
|
+
**File**: `{{PLAYWRIGHT_DIR}}/pages/index.ts`
|
|
151
|
+
|
|
152
|
+
**Template**:
|
|
153
|
+
```typescript
|
|
154
|
+
// Add this line for new POM
|
|
155
|
+
export { NewPageName } from './module/NewPageName.ts';
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Example**:
|
|
159
|
+
```typescript
|
|
160
|
+
// existing
|
|
161
|
+
export { LoginPage } from './login/LoginPage';
|
|
162
|
+
export { DashboardPage } from './dashboard/DashboardPage';
|
|
163
|
+
|
|
164
|
+
// ADD NEW LIKE THIS:
|
|
165
|
+
export { SomeModulePage } from './some-module/SomeModulePage'; // <- NEW
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Why?** Allows clean imports in tests:
|
|
169
|
+
```typescript
|
|
170
|
+
// GOOD
|
|
171
|
+
import { LoginPage, SomeModulePage } from '../../pages';
|
|
172
|
+
|
|
173
|
+
// BAD - Don't do this
|
|
174
|
+
import { LoginPage } from '../../pages/login/LoginPage';
|
|
175
|
+
import { SomeModulePage } from '../../pages/some-module/SomeModulePage';
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Step 3C: Create Test Directory Structure If Needed
|
|
179
|
+
|
|
180
|
+
**If test module directory doesn't exist, create it:**
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# From project root
|
|
184
|
+
mkdir -p {{PLAYWRIGHT_DIR}}/tests/NN_Module/
|
|
185
|
+
|
|
186
|
+
# Example: Create a module directory
|
|
187
|
+
mkdir -p {{PLAYWRIGHT_DIR}}/tests/05_ModuleName/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**File placement**:
|
|
191
|
+
```
|
|
192
|
+
{{PLAYWRIGHT_DIR}}/tests/
|
|
193
|
+
├── 05_ModuleName/ # <- Module
|
|
194
|
+
│ ├── 05_01_01_Scenario_one.spec.ts # <- Test file
|
|
195
|
+
│ ├── 05_01_02_Scenario_two.spec.ts # <- Another test
|
|
196
|
+
│ └── 05_01_03_Scenario_three.spec.ts # <- Another test
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Naming Rules**:
|
|
200
|
+
- **First NN** (01-99): Main module number
|
|
201
|
+
- **Second NN** (01-99): Sub-module/category number
|
|
202
|
+
- **Third NN** (01-99): Specific test scenario number
|
|
203
|
+
- **Suffix**: Always `.spec.ts`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Project Context
|
|
208
|
+
|
|
209
|
+
**Framework**: Playwright for E2E testing
|
|
210
|
+
**Location**: `{{PLAYWRIGHT_DIR}}/`
|
|
211
|
+
**Architecture**: Page Object Model (POM) with modular organization
|
|
212
|
+
**Target**: {{PROJECT_NAME}} frontend application
|
|
213
|
+
**Environments**: Local development + Docker + CI/CD
|
|
214
|
+
|
|
215
|
+
### Key Technologies
|
|
216
|
+
|
|
217
|
+
- **@playwright/test** - Main testing framework
|
|
218
|
+
- **TypeScript** - Type-safe test code
|
|
219
|
+
- **Docker** - Containerized test execution
|
|
220
|
+
- **Multiple browsers** - Chromium (default), Firefox, Webkit, Mobile
|
|
221
|
+
|
|
222
|
+
### Configuration Files
|
|
223
|
+
|
|
224
|
+
The project uses several configuration files for environment settings and test data:
|
|
225
|
+
|
|
226
|
+
**1. `.env` file** (`{{PLAYWRIGHT_DIR}}/.env`):
|
|
227
|
+
- Environment configuration for test execution
|
|
228
|
+
- Key variables:
|
|
229
|
+
- `BASE_URL` - Application base URL (default: `{{BASE_URL}}`)
|
|
230
|
+
- `COMPANY_NAME` - Company/tenant to test
|
|
231
|
+
- `BROWSER` - Browser to use (chromium, firefox, webkit)
|
|
232
|
+
- `HEADLESS` - Run tests in headless mode (true/false)
|
|
233
|
+
- `WORKERS` - Number of parallel workers
|
|
234
|
+
- `RETRIES` - Number of test retries on failure
|
|
235
|
+
- `TIMEOUT` - Test timeout in milliseconds
|
|
236
|
+
|
|
237
|
+
**2. Credentials file** (`{{PLAYWRIGHT_DIR}}/credentials.env.json`):
|
|
238
|
+
- Secure credentials storage (**NOT in git**)
|
|
239
|
+
- Contains login credentials for different tenants/environments
|
|
240
|
+
- Structure:
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"tenantName": {
|
|
244
|
+
"email": { "right": "valid@email.com", "wrong": "invalid@email.com" },
|
|
245
|
+
"password": { "right": "correctPassword", "wrong": "wrongPassword" },
|
|
246
|
+
"username": { "right": "validUser", "wrong": "invalidUser" }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
- Loaded dynamically via `CredentialsLoader.getCredentials()`
|
|
251
|
+
|
|
252
|
+
**3. `playwright.config.ts` file** (`{{PLAYWRIGHT_DIR}}/playwright.config.ts`):
|
|
253
|
+
- Main Playwright configuration
|
|
254
|
+
- Loads environment variables from `.env` using `dotenv`
|
|
255
|
+
- Configures:
|
|
256
|
+
- Test directory (`testDir: './tests'`)
|
|
257
|
+
- Browser projects (Chromium, Firefox, Webkit, Mobile)
|
|
258
|
+
- Reporters (HTML, JSON, JUnit, Allure)
|
|
259
|
+
- Timeouts and retries
|
|
260
|
+
- Base URL and navigation settings
|
|
261
|
+
- Locale and timezone
|
|
262
|
+
- Screenshots and video recording
|
|
263
|
+
- Dynamic configuration based on environment variables
|
|
264
|
+
|
|
265
|
+
**Usage in tests**:
|
|
266
|
+
```typescript
|
|
267
|
+
// Load credentials from credentials file
|
|
268
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
269
|
+
// Uses COMPANY_NAME from .env to select tenant data
|
|
270
|
+
|
|
271
|
+
// Base URL is automatically used from playwright.config.ts
|
|
272
|
+
await page.goto('/login'); // Uses BASE_URL from .env
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Test Organization & Structure
|
|
276
|
+
|
|
277
|
+
### Directory Structure
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
{{PLAYWRIGHT_DIR}}/
|
|
281
|
+
├── tests/ # Test files (organized by module)
|
|
282
|
+
│ ├── 00_Setup/ # Setup and initialization tests
|
|
283
|
+
│ ├── 01_Login/ # Login module tests
|
|
284
|
+
│ │ ├── 01_01_01_Login_scenarios.spec.ts
|
|
285
|
+
│ │ └── 01_01_02_Login_verification.spec.ts
|
|
286
|
+
│ ├── 02_Settings/ # Settings module tests
|
|
287
|
+
│ ├── 03_ModuleA/ # Module A tests
|
|
288
|
+
│ ├── 10_ModuleB/ # Module B tests
|
|
289
|
+
│ └── 11_ModuleC/ # Module C tests
|
|
290
|
+
│ └── 01_SubModule/ # Sub-module
|
|
291
|
+
├── tmp/ # Temporary/recorded tests (gitignored)
|
|
292
|
+
│ └── playwright-test-*.js # Raw Codegen outputs
|
|
293
|
+
├── pages/ # Page Object Models
|
|
294
|
+
│ ├── login/LoginPage.ts
|
|
295
|
+
│ ├── dashboard/DashboardPage.ts
|
|
296
|
+
│ ├── components/ # Reusable component POMs
|
|
297
|
+
│ └── index.ts # Centralized exports
|
|
298
|
+
├── fixtures/ # Test data and fixtures
|
|
299
|
+
│ ├── loader.ts # CredentialsLoader for dynamic data
|
|
300
|
+
│ ├── users.ts # User fixtures
|
|
301
|
+
│ ├── tenants/ # Tenant-specific data
|
|
302
|
+
│ └── types/ # TypeScript type definitions
|
|
303
|
+
├── utils/ # Utility functions
|
|
304
|
+
│ ├── locators.ts # Selector helpers
|
|
305
|
+
│ ├── test-helpers.ts # Common test utilities
|
|
306
|
+
│ └── commands/ # Command utilities
|
|
307
|
+
│ └── auth/ # Authentication helpers
|
|
308
|
+
├── helpers/ # Global setup/teardown
|
|
309
|
+
│ ├── global-setup.ts
|
|
310
|
+
│ ├── global-teardown.ts
|
|
311
|
+
│ └── archive-reports.ts
|
|
312
|
+
├── playwright.config.ts # Main config (Chromium only - fast)
|
|
313
|
+
├── playwright.all-browsers.config.ts # Full browser coverage
|
|
314
|
+
├── playwright.chromium-only.config.ts # Explicit Chromium config
|
|
315
|
+
├── .env # Local environment variables
|
|
316
|
+
├── .gitignore # Git ignore rules (includes tmp/)
|
|
317
|
+
└── credentials.env.json # Secure credentials (not in git)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Test File Naming Convention
|
|
321
|
+
|
|
322
|
+
**Format**: `NN_NN_NN_Test_name.spec.ts`
|
|
323
|
+
|
|
324
|
+
- **NN** - Module/category/test number (hierarchical)
|
|
325
|
+
- **Test_name** - Descriptive name (using underscores)
|
|
326
|
+
- **Extension**: Always `.spec.ts`
|
|
327
|
+
|
|
328
|
+
**Examples**:
|
|
329
|
+
```
|
|
330
|
+
01_01_01_Login_scenarios.spec.ts
|
|
331
|
+
01_01_02_Login_verification.spec.ts
|
|
332
|
+
11_01_01_Add_item.spec.ts
|
|
333
|
+
11_01_02_Edit_item.spec.ts
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Hierarchy**:
|
|
337
|
+
- **First NN**: Main module (01 = Login, 02 = Settings, 11 = ModuleC)
|
|
338
|
+
- **Second NN**: Sub-module or category
|
|
339
|
+
- **Third NN**: Specific test scenario within category
|
|
340
|
+
|
|
341
|
+
## Test Writing Guidelines
|
|
342
|
+
|
|
343
|
+
### Basic Test Structure
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { expect, test } from '@playwright/test';
|
|
347
|
+
import { LoginPage } from '../../pages/login/LoginPage';
|
|
348
|
+
import { DashboardPage } from '../../pages/dashboard/DashboardPage';
|
|
349
|
+
import { CredentialsLoader } from '../../fixtures/loader';
|
|
350
|
+
import { clearBrowserStorage } from '../../utils/test-helpers';
|
|
351
|
+
|
|
352
|
+
test.describe('01_Login - tests', { tag: ['@login', '@verified', '@smoke'] }, () => {
|
|
353
|
+
let loginPage: LoginPage;
|
|
354
|
+
let dashboardPage: DashboardPage;
|
|
355
|
+
|
|
356
|
+
test.beforeEach(async ({ page }) => {
|
|
357
|
+
loginPage = new LoginPage(page);
|
|
358
|
+
dashboardPage = new DashboardPage(page);
|
|
359
|
+
|
|
360
|
+
// Navigate to login page
|
|
361
|
+
await page.goto('/login');
|
|
362
|
+
|
|
363
|
+
// Clear browser storage (localStorage, sessionStorage, cookies)
|
|
364
|
+
await clearBrowserStorage(page);
|
|
365
|
+
|
|
366
|
+
// Wait for page to be ready
|
|
367
|
+
await loginPage.waitForPageReady();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('01_01_01: Login - positive result', async ({ page }) => {
|
|
371
|
+
// Load test data from fixtures
|
|
372
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
373
|
+
|
|
374
|
+
// Use test.step for better reporting
|
|
375
|
+
await test.step('Enter email and password', async () => {
|
|
376
|
+
await loginPage.login(
|
|
377
|
+
credentials.email.right,
|
|
378
|
+
credentials.password.right
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
await test.step('Verify successful login', async () => {
|
|
383
|
+
await loginPage.verifySuccessfulLogin();
|
|
384
|
+
await expect(page).toHaveURL(/\/dashboard/);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('01_01_02: Login - wrong password', async ({ page }) => {
|
|
389
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
390
|
+
|
|
391
|
+
await test.step('Enter valid email', async () => {
|
|
392
|
+
await loginPage.fillEmail(credentials.email.right);
|
|
393
|
+
await loginPage.clickContinue();
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
await test.step('Enter wrong password', async () => {
|
|
397
|
+
await loginPage.passwordInputLocator.waitFor({ state: 'visible' });
|
|
398
|
+
await loginPage.fillPassword(credentials.password.wrong);
|
|
399
|
+
await loginPage.clickLoginButton();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
await test.step('Verify error message', async () => {
|
|
403
|
+
await loginPage.verifyErrorAlert();
|
|
404
|
+
await loginPage.verifyStillOnLoginPage();
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Key Patterns
|
|
411
|
+
|
|
412
|
+
1. **Use `test.describe()` with tags** - Group related tests with descriptive tags
|
|
413
|
+
2. **Initialize Page Objects in `beforeEach`** - Create fresh instances for each test
|
|
414
|
+
3. **Use `test.step()` for clarity** - Break tests into logical steps for better reporting
|
|
415
|
+
4. **Load data dynamically** - Use `CredentialsLoader` for credentials and test data
|
|
416
|
+
5. **Clear browser state** - Always use `clearBrowserStorage()` in `beforeEach`
|
|
417
|
+
6. **Wait for visibility** - Use `.waitFor({ state: 'visible' })` before interactions
|
|
418
|
+
|
|
419
|
+
## Page Object Model (POM)
|
|
420
|
+
|
|
421
|
+
### Creating a Page Object
|
|
422
|
+
|
|
423
|
+
**Location**: `{{PLAYWRIGHT_DIR}}/pages/[module]/[PageName]Page.ts`
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import type { Locator, Page } from '@playwright/test';
|
|
427
|
+
import { getByDataTestId } from '../../utils/locators';
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Page Object for Login page
|
|
431
|
+
* Handles email/password authentication flow
|
|
432
|
+
*/
|
|
433
|
+
export class LoginPage {
|
|
434
|
+
protected page: Page;
|
|
435
|
+
|
|
436
|
+
// Locators - private and readonly
|
|
437
|
+
private readonly emailInput: Locator;
|
|
438
|
+
private readonly passwordInput: Locator;
|
|
439
|
+
private readonly submitButton: Locator;
|
|
440
|
+
private readonly errorAlert: Locator;
|
|
441
|
+
|
|
442
|
+
constructor(page: Page) {
|
|
443
|
+
this.page = page;
|
|
444
|
+
|
|
445
|
+
// Initialize locators with fallbacks for legacy selectors
|
|
446
|
+
this.emailInput = page
|
|
447
|
+
.locator('[data-testid="loginViewEmailStepInput"], [data-testid="loginRightPanelEmailInput"]')
|
|
448
|
+
.first();
|
|
449
|
+
|
|
450
|
+
this.passwordInput = page
|
|
451
|
+
.locator('[data-testid="loginViewPasswordStepInput"]')
|
|
452
|
+
.first();
|
|
453
|
+
|
|
454
|
+
this.submitButton = page
|
|
455
|
+
.locator('[data-testid="loginViewSubmitButton"]')
|
|
456
|
+
.first();
|
|
457
|
+
|
|
458
|
+
this.errorAlert = page
|
|
459
|
+
.locator('[data-testid="loginViewErrorAlert"]')
|
|
460
|
+
.first();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Navigate to login page and wait for form to be visible
|
|
465
|
+
*/
|
|
466
|
+
async navigate(): Promise<void> {
|
|
467
|
+
await this.page.goto('/login');
|
|
468
|
+
await this.emailInput.waitFor({ state: 'visible', timeout: 10000 });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Fill email input field
|
|
473
|
+
*/
|
|
474
|
+
async fillEmail(email: string): Promise<void> {
|
|
475
|
+
await this.emailInput.waitFor({ state: 'visible' });
|
|
476
|
+
await this.emailInput.clear();
|
|
477
|
+
await this.emailInput.fill(email);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Fill password input field
|
|
482
|
+
*/
|
|
483
|
+
async fillPassword(password: string): Promise<void> {
|
|
484
|
+
await this.passwordInput.waitFor({ state: 'visible' });
|
|
485
|
+
await this.passwordInput.clear();
|
|
486
|
+
await this.passwordInput.fill(password);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Complete login flow with email and password
|
|
491
|
+
*/
|
|
492
|
+
async login(email: string, password: string): Promise<void> {
|
|
493
|
+
await this.fillEmail(email);
|
|
494
|
+
await this.submitButton.click();
|
|
495
|
+
await this.passwordInput.waitFor({ state: 'visible' });
|
|
496
|
+
await this.fillPassword(password);
|
|
497
|
+
await this.submitButton.click();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Verify successful login by checking URL redirect
|
|
502
|
+
*/
|
|
503
|
+
async verifySuccessfulLogin(): Promise<void> {
|
|
504
|
+
await this.page.waitForURL(/\/dashboard/, { timeout: 15000 });
|
|
505
|
+
await this.page.waitForLoadState('networkidle');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Verify error alert is visible
|
|
510
|
+
*/
|
|
511
|
+
async verifyErrorAlert(): Promise<void> {
|
|
512
|
+
await this.errorAlert.waitFor({ state: 'visible', timeout: 5000 });
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### POM Best Practices
|
|
518
|
+
|
|
519
|
+
1. **One class per page/component** - Keep POMs focused and single-responsibility
|
|
520
|
+
2. **Locators as private readonly** - Encapsulate selectors, expose methods
|
|
521
|
+
3. **Descriptive method names** - `fillEmail()`, `clickSubmitButton()`, `verifyErrorAlert()`
|
|
522
|
+
4. **Return void for actions** - Methods that perform actions return `Promise<void>`
|
|
523
|
+
5. **JSDoc comments** - Document public methods for better IDE support
|
|
524
|
+
6. **Export via index.ts** - Centralize exports in `pages/index.ts`
|
|
525
|
+
7. **Use `getByDataTestId()` helper** - For consistent data-testid selection
|
|
526
|
+
|
|
527
|
+
### Project Structure Example
|
|
528
|
+
|
|
529
|
+
**Page Objects** in the project:
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
{{PLAYWRIGHT_DIR}}/pages/
|
|
533
|
+
├── index.ts <- Centralized exports
|
|
534
|
+
├── login/
|
|
535
|
+
│ └── LoginPage.ts <- Login module POM
|
|
536
|
+
├── dashboard/
|
|
537
|
+
│ └── DashboardPage.ts <- Dashboard module POM
|
|
538
|
+
├── profile/
|
|
539
|
+
│ └── ProfilePage.ts <- Profile module POM (User profile interactions)
|
|
540
|
+
├── settings/
|
|
541
|
+
│ ├── GlobalSettingsPage.ts
|
|
542
|
+
│ ├── ModuleSettingsPage.ts
|
|
543
|
+
│ └── BrowserSettingsPage.ts
|
|
544
|
+
├── module-a/
|
|
545
|
+
│ ├── ModuleAPage.ts
|
|
546
|
+
│ ├── ModuleAFormPage.ts
|
|
547
|
+
│ ├── ModuleADetailsPage.ts
|
|
548
|
+
│ └── ModuleASettingsPage.ts
|
|
549
|
+
└── components/
|
|
550
|
+
├── SidebarComponent.ts
|
|
551
|
+
├── DataTableComponent.ts
|
|
552
|
+
└── ModalComponent.ts
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### pages/index.ts - Centralized Exports
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
/**
|
|
559
|
+
* Page Object Models index file
|
|
560
|
+
* Central export point for all page objects
|
|
561
|
+
*/
|
|
562
|
+
|
|
563
|
+
// Login pages
|
|
564
|
+
export { LoginPage } from './login/LoginPage';
|
|
565
|
+
// Dashboard pages
|
|
566
|
+
export { DashboardPage } from './dashboard/DashboardPage';
|
|
567
|
+
// Profile pages
|
|
568
|
+
export { ProfilePage } from './profile/ProfilePage';
|
|
569
|
+
// Settings pages
|
|
570
|
+
export { GlobalSettingsPage } from './settings/GlobalSettingsPage';
|
|
571
|
+
// ... more exports
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Profile Module Example
|
|
575
|
+
|
|
576
|
+
**File**: `{{PLAYWRIGHT_DIR}}/pages/profile/ProfilePage.ts`
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
import type { Locator, Page } from '@playwright/test';
|
|
580
|
+
import { expect } from '@playwright/test';
|
|
581
|
+
import { getByDataTestId } from '../../utils/locators';
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Page Object for User Profile page
|
|
585
|
+
* Handles user profile navigation and interactions
|
|
586
|
+
*/
|
|
587
|
+
export class ProfilePage {
|
|
588
|
+
protected page: Page;
|
|
589
|
+
|
|
590
|
+
// Avatar selector with fallbacks for different UI versions
|
|
591
|
+
private readonly avatarSelectors = [
|
|
592
|
+
'[data-testid="mainLayoutTopMenuMobileAvatar"]',
|
|
593
|
+
'[data-testid="desktopMenuAvatar"]',
|
|
594
|
+
'[data-testid="mobileMenuAvatar"]',
|
|
595
|
+
'[data-testid="userMenuDropdownIcon"]',
|
|
596
|
+
'[data-testid="mainLayoutTopMenuDesktopAvatar"]',
|
|
597
|
+
];
|
|
598
|
+
|
|
599
|
+
constructor(page: Page) {
|
|
600
|
+
this.page = page;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Navigate to user profile via avatar menu
|
|
605
|
+
* Tries multiple selectors for compatibility with different UI versions
|
|
606
|
+
*/
|
|
607
|
+
async navigateToUserProfile(): Promise<void> {
|
|
608
|
+
let avatarFound = false;
|
|
609
|
+
|
|
610
|
+
for (const selector of this.avatarSelectors) {
|
|
611
|
+
const avatarButton = this.page.locator(selector).first();
|
|
612
|
+
const isVisible = await avatarButton.isVisible({ timeout: 2000 }).catch(() => false);
|
|
613
|
+
if (isVisible) {
|
|
614
|
+
await avatarButton.click();
|
|
615
|
+
avatarFound = true;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (!avatarFound) {
|
|
621
|
+
throw new Error(`Could not find avatar button. Tried selectors: ${this.avatarSelectors.join(', ')}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const profileOption = getByDataTestId(this.page, 'profilePopoverItemProfile');
|
|
625
|
+
await profileOption.waitFor({ state: 'visible', timeout: 5000 });
|
|
626
|
+
await profileOption.click();
|
|
627
|
+
|
|
628
|
+
await this.page.waitForLoadState('networkidle');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Find and open a specific item by content
|
|
633
|
+
*/
|
|
634
|
+
async findAndOpenItem(itemContent: string): Promise<void> {
|
|
635
|
+
const itemElement = this.page.getByRole('paragraph').filter({ hasText: itemContent });
|
|
636
|
+
await itemElement.waitFor({ state: 'visible', timeout: 5000 });
|
|
637
|
+
await itemElement.click();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Add a comment to an item
|
|
642
|
+
*/
|
|
643
|
+
async addComment(commentText: string): Promise<void> {
|
|
644
|
+
const commentInput = this.page.getByRole('textbox', { name: /Write a message/i });
|
|
645
|
+
await commentInput.waitFor({ state: 'visible', timeout: 5000 });
|
|
646
|
+
await commentInput.click();
|
|
647
|
+
await commentInput.fill(commentText);
|
|
648
|
+
await commentInput.press('Enter');
|
|
649
|
+
await this.page.waitForTimeout(500);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Verify comment is visible
|
|
654
|
+
*/
|
|
655
|
+
async verifyCommentVisible(commentText: string): Promise<void> {
|
|
656
|
+
const commentElement = this.page.getByText(commentText, { exact: true });
|
|
657
|
+
await expect(commentElement).toBeVisible({ timeout: 5000 });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Test File Using Profile POM
|
|
663
|
+
|
|
664
|
+
**File**: `tests/02_Profile/02_01_01_Profile_Comments.spec.ts`
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
import { LoginPage, DashboardPage, ProfilePage } from '../../pages';
|
|
668
|
+
import { CredentialsLoader } from '../../fixtures/loader';
|
|
669
|
+
import { clearBrowserStorage } from '../../utils/test-helpers';
|
|
670
|
+
|
|
671
|
+
describe('02_Profile - Comments Workflow',
|
|
672
|
+
{ tag: ['@profile', '@messaging', '@verified'] },
|
|
673
|
+
() => {
|
|
674
|
+
let loginPage: LoginPage;
|
|
675
|
+
let dashboardPage: DashboardPage;
|
|
676
|
+
let profilePage: ProfilePage;
|
|
677
|
+
|
|
678
|
+
beforeEach(async ({ page }) => {
|
|
679
|
+
loginPage = new LoginPage(page);
|
|
680
|
+
dashboardPage = new DashboardPage(page);
|
|
681
|
+
profilePage = new ProfilePage(page);
|
|
682
|
+
|
|
683
|
+
// Load credentials from CredentialsLoader
|
|
684
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
685
|
+
|
|
686
|
+
// Login workflow
|
|
687
|
+
await loginPage.navigate();
|
|
688
|
+
await loginPage.fillEmail(credentials.email.right);
|
|
689
|
+
await loginPage.clickContinue();
|
|
690
|
+
await loginPage.fillPassword(credentials.password.right);
|
|
691
|
+
await loginPage.clickLoginButton();
|
|
692
|
+
await dashboardPage.navigate();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('02_01_01: should login and add comment in user profile',
|
|
696
|
+
async ({ page }) => {
|
|
697
|
+
// Profile navigation and comment addition
|
|
698
|
+
await profilePage.navigateToUserProfile();
|
|
699
|
+
await profilePage.findAndOpenItem('TestItem');
|
|
700
|
+
await profilePage.addComment('test comment');
|
|
701
|
+
await profilePage.verifyCommentVisible('test comment');
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Key Points for POM
|
|
707
|
+
|
|
708
|
+
1. **Location**: `pages/[module]/[PageName]Page.ts` (matches pattern `pages/[module]/[PageName]Page.ts`)
|
|
709
|
+
2. **Export**: Added to `pages/index.ts` for centralized import
|
|
710
|
+
3. **Fallback Selectors**: Handles multiple UI versions (desktop, mobile, different layouts)
|
|
711
|
+
4. **Semantic Methods**: `navigateToUserProfile()`, `addComment()`, `verifyCommentVisible()`
|
|
712
|
+
5. **Type Safety**: Full TypeScript with proper return types
|
|
713
|
+
6. **Documentation**: JSDoc for all public methods
|
|
714
|
+
|
|
715
|
+
## Selectors & data-testid
|
|
716
|
+
|
|
717
|
+
### Selector Priority
|
|
718
|
+
|
|
719
|
+
1. **`[data-testid="value"]`** - **PREFERRED** for all new elements
|
|
720
|
+
2. **`[test-id="value"]`** - Alternative (legacy support)
|
|
721
|
+
3. **`#id`** - For elements with unique IDs
|
|
722
|
+
4. **`input[type="email"]`** - For form elements
|
|
723
|
+
5. **`.class-name`** - Last resort only
|
|
724
|
+
|
|
725
|
+
### data-testid Naming Convention
|
|
726
|
+
|
|
727
|
+
**Format**: `moduleName-element-type`
|
|
728
|
+
|
|
729
|
+
**Rules**:
|
|
730
|
+
- Write in **English**
|
|
731
|
+
- Separate with **hyphens** (`-`)
|
|
732
|
+
- First part: **module name**
|
|
733
|
+
- Middle parts: **description**
|
|
734
|
+
- Last part: **element type** (input, button, checkbox, etc.)
|
|
735
|
+
|
|
736
|
+
**Examples**:
|
|
737
|
+
```html
|
|
738
|
+
<!-- Login module -->
|
|
739
|
+
<input data-testid="login-email-input" />
|
|
740
|
+
<input data-testid="login-password-input" />
|
|
741
|
+
<input data-testid="login-remember-me-checkbox" />
|
|
742
|
+
<button data-testid="login-submit-button" />
|
|
743
|
+
|
|
744
|
+
<!-- Tasks module -->
|
|
745
|
+
<button data-testid="tasks-add-new-button" />
|
|
746
|
+
<input data-testid="tasks-search-input" />
|
|
747
|
+
<select data-testid="tasks-status-filter-select" />
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### Using the Locator Helper
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
import { getByDataTestId } from '../../utils/locators';
|
|
754
|
+
|
|
755
|
+
// In Page Object
|
|
756
|
+
constructor(page: Page) {
|
|
757
|
+
this.page = page;
|
|
758
|
+
|
|
759
|
+
// Simple usage
|
|
760
|
+
this.submitButton = getByDataTestId(page, 'login-submit-button');
|
|
761
|
+
|
|
762
|
+
// With fallbacks for legacy selectors
|
|
763
|
+
this.emailInput = page
|
|
764
|
+
.locator('[data-testid="loginViewEmailStepInput"], [data-testid="loginRightPanelEmailInput"]')
|
|
765
|
+
.first();
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Helper function** (`utils/locators.ts`):
|
|
770
|
+
```typescript
|
|
771
|
+
export function getByDataTestId(page: Page, value: string) {
|
|
772
|
+
return page.locator(
|
|
773
|
+
`[data-testid="${value}"], [data-testid="${value}"], [data-testId="${value}"]`
|
|
774
|
+
).first();
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
## Test Data & Fixtures
|
|
779
|
+
|
|
780
|
+
### Using CredentialsLoader
|
|
781
|
+
|
|
782
|
+
**Location**: `fixtures/loader.ts`
|
|
783
|
+
|
|
784
|
+
The `CredentialsLoader` provides dynamic test data loading based on environment variables:
|
|
785
|
+
|
|
786
|
+
```typescript
|
|
787
|
+
import { CredentialsLoader } from '../../fixtures/loader';
|
|
788
|
+
|
|
789
|
+
// Get credentials for current tenant (from COMPANY_NAME env var)
|
|
790
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
791
|
+
|
|
792
|
+
// Available credential fields:
|
|
793
|
+
credentials.email.right // Valid email
|
|
794
|
+
credentials.email.wrong // Invalid email
|
|
795
|
+
credentials.password.right // Valid password
|
|
796
|
+
credentials.password.wrong // Invalid password
|
|
797
|
+
credentials.username.right // Valid username (login)
|
|
798
|
+
credentials.username.wrong // Invalid username
|
|
799
|
+
|
|
800
|
+
// Get all tenant data (users, departments, etc.)
|
|
801
|
+
const tenantData = await CredentialsLoader.getAllData();
|
|
802
|
+
|
|
803
|
+
// Get specific users
|
|
804
|
+
const users = await CredentialsLoader.getUsers();
|
|
805
|
+
|
|
806
|
+
// Get departments
|
|
807
|
+
const departments = await CredentialsLoader.getDepartments();
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Environment Variables
|
|
811
|
+
|
|
812
|
+
**File**: `.env` (local) or set via Docker/Makefile
|
|
813
|
+
|
|
814
|
+
```bash
|
|
815
|
+
# Tenant selection
|
|
816
|
+
COMPANY_NAME=default
|
|
817
|
+
|
|
818
|
+
# Base URL
|
|
819
|
+
BASE_URL={{BASE_URL}}
|
|
820
|
+
|
|
821
|
+
# Test execution
|
|
822
|
+
PLAYWRIGHT_WORKERS=4
|
|
823
|
+
PLAYWRIGHT_RETRIES=1
|
|
824
|
+
PLAYWRIGHT_GREP_TAGS=@verified
|
|
825
|
+
|
|
826
|
+
# Browser
|
|
827
|
+
PLAYWRIGHT_BROWSERS=chromium # chromium, firefox, webkit
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### Tenant Data Structure
|
|
831
|
+
|
|
832
|
+
**File**: `fixtures/tenants/default.ts`
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
const tenantData = {
|
|
836
|
+
name: 'Default Tenant',
|
|
837
|
+
code: 'default',
|
|
838
|
+
|
|
839
|
+
// Credentials loaded from credentials.env.json (secure)
|
|
840
|
+
// Not stored in repository!
|
|
841
|
+
|
|
842
|
+
users: {
|
|
843
|
+
admin: {
|
|
844
|
+
fullName: 'Admin User',
|
|
845
|
+
id: 1,
|
|
846
|
+
position: 'Administrator',
|
|
847
|
+
department: 'IT',
|
|
848
|
+
},
|
|
849
|
+
manager1: {
|
|
850
|
+
fullName: 'Manager One',
|
|
851
|
+
id: 2,
|
|
852
|
+
position: 'Department Manager',
|
|
853
|
+
department: 'Sales',
|
|
854
|
+
},
|
|
855
|
+
// ... more users
|
|
856
|
+
},
|
|
857
|
+
|
|
858
|
+
departments: {
|
|
859
|
+
// Department data
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
module.exports = tenantData;
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
### Secure Credentials
|
|
867
|
+
|
|
868
|
+
**File**: `credentials.env.json` (NOT in git, copy from `.example`)
|
|
869
|
+
|
|
870
|
+
```json
|
|
871
|
+
{
|
|
872
|
+
"default": {
|
|
873
|
+
"username": {
|
|
874
|
+
"right": "valid.user@example.com",
|
|
875
|
+
"wrong": "invalid@example.com"
|
|
876
|
+
},
|
|
877
|
+
"password": {
|
|
878
|
+
"right": "ValidPassword123!",
|
|
879
|
+
"wrong": "WrongPassword"
|
|
880
|
+
},
|
|
881
|
+
"email": {
|
|
882
|
+
"right": "test@example.com",
|
|
883
|
+
"wrong": "invalid@test.com"
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
## Test Tags & Filtering
|
|
890
|
+
|
|
891
|
+
### Available Tags
|
|
892
|
+
Tags: English language, prefixed with `@` for filtering
|
|
893
|
+
- **`@verified`** - Fully tested and verified tests (default for CI/CD)
|
|
894
|
+
- **`@smoke`** - Critical smoke tests (subset of @verified)
|
|
895
|
+
- **`@login`** - Login module tests
|
|
896
|
+
- **`@settings`** - Settings module tests
|
|
897
|
+
- **`@module-a`** - Module A tests
|
|
898
|
+
- **`@module-b`** - Module B tests
|
|
899
|
+
|
|
900
|
+
### Using Tags in Tests
|
|
901
|
+
|
|
902
|
+
```typescript
|
|
903
|
+
// Single tag
|
|
904
|
+
test.describe('Login tests', { tag: '@login' }, () => {
|
|
905
|
+
// tests
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
// Multiple tags
|
|
909
|
+
test.describe('Login tests', { tag: ['@login', '@verified', '@smoke'] }, () => {
|
|
910
|
+
// tests
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// Individual test tags (overrides describe tags)
|
|
914
|
+
test('Critical login flow', { tag: '@smoke' }, async ({ page }) => {
|
|
915
|
+
// test
|
|
916
|
+
});
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Running Tests by Tags
|
|
920
|
+
|
|
921
|
+
```bash
|
|
922
|
+
# Run all verified tests
|
|
923
|
+
npx playwright test --grep @verified
|
|
924
|
+
|
|
925
|
+
# Run smoke tests only
|
|
926
|
+
npx playwright test --grep @smoke
|
|
927
|
+
|
|
928
|
+
# Run specific module
|
|
929
|
+
npx playwright test --grep @login
|
|
930
|
+
|
|
931
|
+
# Multiple tags (OR logic)
|
|
932
|
+
npx playwright test --grep "@login|@smoke"
|
|
933
|
+
|
|
934
|
+
# Via Makefile
|
|
935
|
+
make playwright-test playwright_grep_tags="@login"
|
|
936
|
+
make playwright-test playwright_grep_tags="@smoke"
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
## Running Tests
|
|
940
|
+
|
|
941
|
+
### Local Execution
|
|
942
|
+
|
|
943
|
+
```bash
|
|
944
|
+
cd {{PLAYWRIGHT_DIR}}
|
|
945
|
+
|
|
946
|
+
# Quick tests - Chromium only
|
|
947
|
+
npx playwright test
|
|
948
|
+
|
|
949
|
+
# With UI (like Cypress)
|
|
950
|
+
npx playwright test --ui
|
|
951
|
+
|
|
952
|
+
# Debug mode
|
|
953
|
+
npx playwright test --debug
|
|
954
|
+
|
|
955
|
+
# Specific file
|
|
956
|
+
npx playwright test tests/01_Login/01_01_01_Login_scenarios.spec.ts
|
|
957
|
+
|
|
958
|
+
# All browsers
|
|
959
|
+
npx playwright test --config=playwright.all-browsers.config.ts
|
|
960
|
+
|
|
961
|
+
# Specific browser
|
|
962
|
+
npx playwright test --project=firefox
|
|
963
|
+
|
|
964
|
+
# Parallel execution (4 workers)
|
|
965
|
+
npx playwright test --workers=4
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
### Docker Execution (via Makefile)
|
|
969
|
+
|
|
970
|
+
**From project root**:
|
|
971
|
+
|
|
972
|
+
```bash
|
|
973
|
+
# Default - verified tests on Chromium
|
|
974
|
+
make playwright-test
|
|
975
|
+
|
|
976
|
+
# Custom URL
|
|
977
|
+
make playwright-test playwright_base_url="{{BASE_URL}}"
|
|
978
|
+
|
|
979
|
+
# Specific tags
|
|
980
|
+
make playwright-test playwright_grep_tags="@login"
|
|
981
|
+
|
|
982
|
+
# Specific file
|
|
983
|
+
make playwright-test playwright_spec_file="tests/01_Login/01_01_01_Login_scenarios.spec.ts"
|
|
984
|
+
|
|
985
|
+
# Different browser
|
|
986
|
+
make playwright-test playwright_browsers="firefox"
|
|
987
|
+
|
|
988
|
+
# Parallel execution (5 workers)
|
|
989
|
+
make playwright-test playwright_parallel_machines="5"
|
|
990
|
+
|
|
991
|
+
# Different tenant
|
|
992
|
+
make playwright-test playwright_client_name="tenant-name"
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
### Configuration Files
|
|
996
|
+
|
|
997
|
+
- **`playwright.config.ts`** - Default (Chromium only, fast)
|
|
998
|
+
- **`playwright.all-browsers.config.ts`** - All browsers (Chromium, Firefox, Webkit, Mobile)
|
|
999
|
+
- **`playwright.chromium-only.config.ts`** - Explicit Chromium only
|
|
1000
|
+
|
|
1001
|
+
## Authentication & Session Management
|
|
1002
|
+
|
|
1003
|
+
### Session Manager
|
|
1004
|
+
|
|
1005
|
+
**Location**: `utils/commands/auth/session.manager.ts`
|
|
1006
|
+
|
|
1007
|
+
Provides session caching for efficient test execution:
|
|
1008
|
+
|
|
1009
|
+
```typescript
|
|
1010
|
+
import { SessionManager } from '../../utils/commands/auth';
|
|
1011
|
+
|
|
1012
|
+
// In test
|
|
1013
|
+
test.beforeEach(async ({ page }) => {
|
|
1014
|
+
// Restore session if exists
|
|
1015
|
+
await SessionManager.restoreSession(page, 'user-session');
|
|
1016
|
+
|
|
1017
|
+
// If no session, login manually
|
|
1018
|
+
if (!await SessionManager.hasSession('user-session')) {
|
|
1019
|
+
const loginPage = new LoginPage(page);
|
|
1020
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
1021
|
+
await loginPage.login(credentials.email.right, credentials.password.right);
|
|
1022
|
+
|
|
1023
|
+
// Save session
|
|
1024
|
+
await SessionManager.saveSession(page, 'user-session');
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
### Clear Browser Storage
|
|
1030
|
+
|
|
1031
|
+
```typescript
|
|
1032
|
+
import { clearBrowserStorage } from '../../utils/test-helpers';
|
|
1033
|
+
|
|
1034
|
+
test.beforeEach(async ({ page }) => {
|
|
1035
|
+
await page.goto('/login');
|
|
1036
|
+
|
|
1037
|
+
// Clear localStorage, sessionStorage, cookies
|
|
1038
|
+
await clearBrowserStorage(page);
|
|
1039
|
+
});
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
## Reporting & Debugging
|
|
1043
|
+
|
|
1044
|
+
### Reports Location
|
|
1045
|
+
|
|
1046
|
+
- **HTML Report**: `playwright-report/index.html`
|
|
1047
|
+
- **JSON Report**: `reports/summary.json`
|
|
1048
|
+
- **Screenshots**: `reports/screenshots/`
|
|
1049
|
+
- **Videos**: `test-results/` (on failure)
|
|
1050
|
+
- **Archives**: `reports/archives/run-TIMESTAMP/`
|
|
1051
|
+
|
|
1052
|
+
### View Reports
|
|
1053
|
+
|
|
1054
|
+
```bash
|
|
1055
|
+
# Open HTML report
|
|
1056
|
+
npx playwright show-report
|
|
1057
|
+
|
|
1058
|
+
# Via Makefile
|
|
1059
|
+
make playwright-show-report
|
|
1060
|
+
|
|
1061
|
+
# Custom port
|
|
1062
|
+
npx playwright show-report --port=9324
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
### Screenshots & Videos
|
|
1066
|
+
|
|
1067
|
+
**Automatic**:
|
|
1068
|
+
- Screenshots: Taken for ALL tests (pass/fail)
|
|
1069
|
+
- Videos: Only on failure (local) or off (CI)
|
|
1070
|
+
- Traces: On first retry
|
|
1071
|
+
|
|
1072
|
+
**Manual screenshots**:
|
|
1073
|
+
```typescript
|
|
1074
|
+
await page.screenshot({ path: 'screenshot.png', fullPage: true });
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
## Common Utilities
|
|
1078
|
+
|
|
1079
|
+
### Test Helpers
|
|
1080
|
+
|
|
1081
|
+
**File**: `utils/test-helpers.ts`
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
// Clear browser storage
|
|
1085
|
+
await clearBrowserStorage(page);
|
|
1086
|
+
|
|
1087
|
+
// Wait for stable state (no animations)
|
|
1088
|
+
await waitForStable(page, '[data-testid="element"]');
|
|
1089
|
+
|
|
1090
|
+
// Timestamped screenshot
|
|
1091
|
+
await takeTimestampedScreenshot(page, 'login-error');
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### Locator Helpers
|
|
1095
|
+
|
|
1096
|
+
```typescript
|
|
1097
|
+
import { getByDataTestId } from '../../utils/locators';
|
|
1098
|
+
|
|
1099
|
+
// Get element by data-testid (with fallbacks)
|
|
1100
|
+
const button = getByDataTestId(page, 'submit-button');
|
|
1101
|
+
```
|
|
1102
|
+
|
|
1103
|
+
## Best Practices
|
|
1104
|
+
|
|
1105
|
+
### 1. Test Organization
|
|
1106
|
+
- Use hierarchical numbering (NN_NN_NN)
|
|
1107
|
+
- Group related tests in `test.describe()`
|
|
1108
|
+
- Use descriptive test names
|
|
1109
|
+
- Tag tests appropriately
|
|
1110
|
+
|
|
1111
|
+
### 2. Page Objects
|
|
1112
|
+
- One POM per page/component
|
|
1113
|
+
- Private readonly locators
|
|
1114
|
+
- Public methods for actions
|
|
1115
|
+
- Export via `pages/index.ts`
|
|
1116
|
+
|
|
1117
|
+
### 3. Selectors
|
|
1118
|
+
- Prefer `data-testid`
|
|
1119
|
+
- Follow naming convention (module-element-type)
|
|
1120
|
+
- Use `getByDataTestId()` helper
|
|
1121
|
+
- Provide fallbacks for legacy selectors
|
|
1122
|
+
|
|
1123
|
+
### 4. Test Data
|
|
1124
|
+
- Use `CredentialsLoader` for dynamic data
|
|
1125
|
+
- Never hardcode credentials
|
|
1126
|
+
- Store sensitive data in `credentials.env.json`
|
|
1127
|
+
- Use environment variables
|
|
1128
|
+
|
|
1129
|
+
### 5. Assertions
|
|
1130
|
+
- Use `test.step()` for clarity
|
|
1131
|
+
- Wait for visibility before assertions
|
|
1132
|
+
- Use specific expectations (`toHaveURL`, `toBeVisible`)
|
|
1133
|
+
- Verify state changes
|
|
1134
|
+
|
|
1135
|
+
### 6. Performance
|
|
1136
|
+
- Use Chromium-only config for fast feedback
|
|
1137
|
+
- Leverage parallel execution (workers)
|
|
1138
|
+
- Clear browser state in `beforeEach`
|
|
1139
|
+
- Use all-browsers config before release
|
|
1140
|
+
|
|
1141
|
+
### 7. Debugging
|
|
1142
|
+
- Use `--ui` mode for interactive debugging
|
|
1143
|
+
- Use `--debug` for step-by-step execution
|
|
1144
|
+
- Check screenshots in reports
|
|
1145
|
+
- Review trace files on failures
|
|
1146
|
+
|
|
1147
|
+
## Anti-Patterns (Avoid)
|
|
1148
|
+
|
|
1149
|
+
- Hardcoding credentials in tests
|
|
1150
|
+
- Using CSS class selectors instead of data-testid
|
|
1151
|
+
- Not using Page Objects (direct page interactions in tests)
|
|
1152
|
+
- Missing test tags
|
|
1153
|
+
- Not clearing browser storage in beforeEach
|
|
1154
|
+
- Not waiting for visibility before interactions
|
|
1155
|
+
- Mixing business logic with test code
|
|
1156
|
+
- Not using `test.step()` for multi-step tests
|
|
1157
|
+
- Inconsistent naming conventions
|
|
1158
|
+
|
|
1159
|
+
## Troubleshooting
|
|
1160
|
+
|
|
1161
|
+
### Common Issues
|
|
1162
|
+
|
|
1163
|
+
**1. Element not found**
|
|
1164
|
+
```typescript
|
|
1165
|
+
// Wrong
|
|
1166
|
+
await page.locator('.button').click();
|
|
1167
|
+
|
|
1168
|
+
// Correct
|
|
1169
|
+
await page.locator('[data-testid="submit-button"]').waitFor({ state: 'visible' });
|
|
1170
|
+
await page.locator('[data-testid="submit-button"]').click();
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
**2. Flaky tests**
|
|
1174
|
+
```typescript
|
|
1175
|
+
// Wait for network idle
|
|
1176
|
+
await page.waitForLoadState('networkidle');
|
|
1177
|
+
|
|
1178
|
+
// Wait for specific element
|
|
1179
|
+
await element.waitFor({ state: 'visible' });
|
|
1180
|
+
|
|
1181
|
+
// Use test.step() to identify failure point
|
|
1182
|
+
await test.step('Login', async () => {
|
|
1183
|
+
await loginPage.login(email, password);
|
|
1184
|
+
});
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1187
|
+
**3. Authentication issues**
|
|
1188
|
+
```typescript
|
|
1189
|
+
// Clear storage before each test
|
|
1190
|
+
test.beforeEach(async ({ page }) => {
|
|
1191
|
+
await clearBrowserStorage(page);
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
// Verify credentials are loaded
|
|
1195
|
+
const credentials = await CredentialsLoader.getCredentials();
|
|
1196
|
+
console.log('Using email:', credentials.email.right);
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
## Quick Reference
|
|
1200
|
+
|
|
1201
|
+
### Essential Commands
|
|
1202
|
+
|
|
1203
|
+
```bash
|
|
1204
|
+
# Local testing
|
|
1205
|
+
npx playwright test # Quick (Chromium only)
|
|
1206
|
+
npx playwright test --ui # GUI mode
|
|
1207
|
+
npx playwright test --debug # Debug mode
|
|
1208
|
+
|
|
1209
|
+
# Docker testing
|
|
1210
|
+
make playwright-test # Default (@verified)
|
|
1211
|
+
make playwright-test playwright_grep_tags="@smoke" # Smoke tests
|
|
1212
|
+
|
|
1213
|
+
# Reports
|
|
1214
|
+
npx playwright show-report # View HTML report
|
|
1215
|
+
make playwright-show-report # Via Makefile
|
|
1216
|
+
|
|
1217
|
+
# Cleanup
|
|
1218
|
+
make playwright-clean # Clean reports
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
### File Templates
|
|
1222
|
+
|
|
1223
|
+
**Test file**: `tests/NN_Module/NN_NN_NN_Test_name.spec.ts`
|
|
1224
|
+
**Page Object**: `pages/module/ModulePage.ts`
|
|
1225
|
+
**Fixtures**: `fixtures/tenants/tenant.ts`
|
|
1226
|
+
|
|
1227
|
+
### Key Imports
|
|
1228
|
+
|
|
1229
|
+
```typescript
|
|
1230
|
+
import { expect, test } from '@playwright/test';
|
|
1231
|
+
import { LoginPage } from '../../pages/login/LoginPage';
|
|
1232
|
+
import { CredentialsLoader } from '../../fixtures/loader';
|
|
1233
|
+
import { clearBrowserStorage } from '../../utils/test-helpers';
|
|
1234
|
+
import { getByDataTestId } from '../../utils/locators';
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
## Resources
|
|
1238
|
+
|
|
1239
|
+
- **Project README**: `{{PLAYWRIGHT_DIR}}/README.md`
|
|
1240
|
+
- **Docker Guide**: `{{PLAYWRIGHT_DIR}}/DEVOPS-DOCKER.md`
|
|
1241
|
+
- **Playwright Docs**: https://playwright.dev/docs/intro
|
|
1242
|
+
|
|
1243
|
+
---
|
|
1244
|
+
|
|
1245
|
+
**Remember**: Adapt this skill template to your specific project by replacing all `{{PLACEHOLDER}}` values with your project's actual paths, URLs, and naming conventions. Always follow the established patterns, naming conventions, and best practices documented here.
|