agents-cli-automation 1.0.8 → 1.0.10
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/package.json +1 -1
- package/src/templates/playwright-agent-ts.md +477 -68
package/package.json
CHANGED
|
@@ -1,69 +1,478 @@
|
|
|
1
|
-
name:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
1
|
+
name: Create Playwright Framework - UI Testing (TypeScript)
|
|
2
|
+
description: Creates a production-ready Playwright UI automation framework in TypeScript with fixtures, configs, and test examples.
|
|
3
|
+
argument-hint: "framework requirements, e.g., 'TypeScript with UI tests'"
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Playwright UI Testing Framework - TypeScript
|
|
8
|
+
|
|
9
|
+
This agent creates a production-ready Playwright framework in TypeScript optimized for UI automation and developer DX.
|
|
10
|
+
|
|
11
|
+
## Capabilities
|
|
12
|
+
- **TypeScript** - typed page objects, helpers, and fixtures
|
|
13
|
+
- First-class `@playwright/test` integration with `playwright.config.ts`
|
|
14
|
+
- UI, component, and end-to-end testing
|
|
15
|
+
- **Playwright BDD** (optional) - Gherkin scenarios with TypeScript step definitions
|
|
16
|
+
- **Page Object Model (POM)** - Typed page classes
|
|
17
|
+
- **Fixtures** - Pre-built browser & page fixtures
|
|
18
|
+
- **Parallel execution** - Tests run concurrently for speed
|
|
19
|
+
- Test data support (JSON, CSV, YAML)
|
|
20
|
+
- Form helpers for complex interactions
|
|
21
|
+
- Scalable folder structure with best practices
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
- Node.js 18+ installed
|
|
25
|
+
- npm or yarn
|
|
26
|
+
- TypeScript knowledge (basic)
|
|
27
|
+
|
|
28
|
+
## Setup Instructions
|
|
29
|
+
|
|
30
|
+
### 1. Install Dependencies (Chromium Only)
|
|
31
|
+
```bash
|
|
32
|
+
npm install
|
|
33
|
+
npm install --save-dev typescript ts-node @types/node @playwright/test playwright
|
|
34
|
+
npm install --save-dev playwright-bdd csv-parser js-yaml @types/csv-parser
|
|
35
|
+
npx playwright install chromium
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Update package.json
|
|
39
|
+
Add TypeScript-related scripts and keep Playwright commands:
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"scripts": {
|
|
43
|
+
"start": "node server.js",
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"test": "npx playwright test",
|
|
46
|
+
"test:headed": "npx playwright test --headed",
|
|
47
|
+
"test:debug": "npx playwright test --debug",
|
|
48
|
+
"test:ui": "npx playwright show-report",
|
|
49
|
+
"test:bdd": "npx playwright test --project=ui",
|
|
50
|
+
"test:bdd:report": "npx playwright test --project=ui --reporter=json"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Playwright Configuration (playwright.config.ts)
|
|
56
|
+
```typescript
|
|
57
|
+
import 'dotenv/config';
|
|
58
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
59
|
+
import { defineBddConfig } from 'playwright-bdd';
|
|
60
|
+
|
|
61
|
+
// BDD config for UI tests
|
|
62
|
+
export const bddConfig = defineBddConfig({
|
|
63
|
+
features: ['tests/ui/features/**/*.feature'],
|
|
64
|
+
steps: [
|
|
65
|
+
'tests/ui/steps/**/*.steps.ts',
|
|
66
|
+
'tests/fixtures/pages.fixture.ts',
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export default defineConfig({
|
|
71
|
+
testDir: './tests',
|
|
72
|
+
timeout: 20 * 1000,
|
|
73
|
+
expect: {
|
|
74
|
+
timeout: 10 * 1000,
|
|
75
|
+
},
|
|
76
|
+
retries: process.env.CI ? 1 : 0,
|
|
77
|
+
reporter: [
|
|
78
|
+
['html', { open: 'never' }],
|
|
79
|
+
['list'],
|
|
80
|
+
],
|
|
81
|
+
use: {
|
|
82
|
+
trace: 'retain-on-failure',
|
|
83
|
+
screenshot: 'only-on-failure',
|
|
84
|
+
video: 'retain-on-failure'
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
projects: [
|
|
88
|
+
// =======================
|
|
89
|
+
// UI BDD TESTS
|
|
90
|
+
// =======================
|
|
91
|
+
{
|
|
92
|
+
name: 'ui',
|
|
93
|
+
testDir: './.features-gen', // folder where Playwright-BDD generates .feature.spec.ts
|
|
94
|
+
testMatch: '**/*.feature.spec.*', // match .ts or .js files
|
|
95
|
+
use: {
|
|
96
|
+
...devices['Desktop Chrome'],
|
|
97
|
+
headless: true,
|
|
98
|
+
baseURL: process.env.UI_BASE_URL,
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// =======================
|
|
104
|
+
// API TESTS
|
|
105
|
+
// =======================
|
|
106
|
+
{
|
|
107
|
+
name: 'api',
|
|
108
|
+
testMatch: /.*\.api\.spec\.ts/,
|
|
109
|
+
use: {
|
|
110
|
+
browserName: undefined,
|
|
111
|
+
baseURL: process.env.API_BASE_URL,
|
|
112
|
+
extraHTTPHeaders: {
|
|
113
|
+
'Content-Type': 'application/json',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// =======================
|
|
119
|
+
// DB TESTS (NO BROWSER)
|
|
120
|
+
// =======================
|
|
121
|
+
{
|
|
122
|
+
name: 'db',
|
|
123
|
+
testMatch: /.*\.db\.spec\.ts/,
|
|
124
|
+
use: {
|
|
125
|
+
browserName: undefined,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Project Structure (TypeScript)
|
|
133
|
+
```
|
|
134
|
+
project/
|
|
135
|
+
├── tests/
|
|
136
|
+
│ ├── fixtures/
|
|
137
|
+
│ │ └── base.fixture.ts # Shared fixtures (typed)
|
|
138
|
+
│ ├── pages/
|
|
139
|
+
│ │ ├── BasePage.ts # Base page object
|
|
140
|
+
│ │ ├── LoginPage.ts # Login page object
|
|
141
|
+
│ │ ├── InventoryPage.ts # Inventory page object
|
|
142
|
+
│ │ └── FormPage.ts # Form page object
|
|
143
|
+
│ ├── utils/ # Utility functions
|
|
144
|
+
│ │ ├── dataReader.ts
|
|
145
|
+
│ │ ├── formHelper.ts
|
|
146
|
+
│ │ └── testData.ts
|
|
147
|
+
│ ├── data/ # Test data files
|
|
148
|
+
│ │ ├── users.json
|
|
149
|
+
│ │ ├── formData.csv
|
|
150
|
+
│ │ └── config.yaml
|
|
151
|
+
│ ├── features/ # BDD Gherkin scenarios
|
|
152
|
+
│ │ ├── login.feature
|
|
153
|
+
│ │ └── shopping.feature
|
|
154
|
+
│ ├── steps/ # BDD Step definitions (TypeScript)
|
|
155
|
+
│ │ ├── loginSteps.ts
|
|
156
|
+
│ │ └── shoppingSteps.ts
|
|
157
|
+
│ ├── .features-gen/ # Generated Playwright-BDD tests (.feature.spec.ts)
|
|
158
|
+
├── playwright.config.ts # Playwright config (TypeScript)
|
|
159
|
+
├── tsconfig.json
|
|
160
|
+
└── package.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
### 6. Data Reader Utility (tests/utils/dataReader.ts)
|
|
166
|
+
```typescript
|
|
167
|
+
import fs from 'fs';
|
|
168
|
+
import path from 'path';
|
|
169
|
+
import csv from 'csv-parser';
|
|
170
|
+
import yaml from 'js-yaml';
|
|
171
|
+
import { fileURLToPath } from 'url';
|
|
172
|
+
|
|
173
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
174
|
+
const DATA_DIR = path.join(path.resolve(__dirname, '..'), 'data');
|
|
175
|
+
|
|
176
|
+
export type JsonObject = Record<string, unknown>;
|
|
177
|
+
|
|
178
|
+
export class DataReader {
|
|
179
|
+
static readJSON<T = any>(filename: string): T {
|
|
180
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
181
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
182
|
+
return JSON.parse(data) as T;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static readYAML<T = any>(filename: string): T {
|
|
186
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
187
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
188
|
+
return yaml.load(data) as T;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static async readCSV(filename: string): Promise<Record<string, string>[]> {
|
|
192
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const records: Record<string, string>[] = [];
|
|
195
|
+
fs.createReadStream(filePath)
|
|
196
|
+
.pipe(csv())
|
|
197
|
+
.on('data', (data: Record<string, string>) => records.push(data))
|
|
198
|
+
.on('end', () => resolve(records))
|
|
199
|
+
.on('error', reject);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static getRecordByKey<T extends Record<string, any>>(data: T[] | undefined, key: string, value: any) {
|
|
204
|
+
if (Array.isArray(data)) {
|
|
205
|
+
return data.find(record => record[key] === value);
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 7. Form Helper Utility (tests/utils/formHelper.ts)
|
|
213
|
+
```typescript
|
|
214
|
+
import type { Page } from '@playwright/test';
|
|
215
|
+
|
|
216
|
+
export class FormHelper {
|
|
217
|
+
readonly page: Page;
|
|
218
|
+
constructor(page: Page) {
|
|
219
|
+
this.page = page;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async fillInput(selector: string, value: string) {
|
|
223
|
+
await this.page.fill(selector, value);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async fillTextArea(selector: string, value: string) {
|
|
227
|
+
await this.page.locator(selector).fill('');
|
|
228
|
+
await this.page.locator(selector).type(value, { delay: 50 });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async selectDropdown(dropdownSelector: string, optionText: string) {
|
|
232
|
+
await this.page.click(dropdownSelector);
|
|
233
|
+
await this.page.click(`text=\"${optionText}\"`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async selectRadioButton(labelText: string) {
|
|
237
|
+
const label = this.page.locator(`label:has-text(\"${labelText}\")`);
|
|
238
|
+
const radioId = await label.getAttribute('for');
|
|
239
|
+
if (radioId) await this.page.locator(`#${radioId}`).click();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async checkCheckbox(labelText: string) {
|
|
243
|
+
const label = this.page.locator(`label:has-text(\"${labelText}\")`);
|
|
244
|
+
const checkboxId = await label.getAttribute('for');
|
|
245
|
+
if (checkboxId) {
|
|
246
|
+
const checkbox = this.page.locator(`#${checkboxId}`);
|
|
247
|
+
if (!(await checkbox.isChecked())) await checkbox.click();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async checkMultiple(labels: string[]) {
|
|
252
|
+
for (const label of labels) await this.checkCheckbox(label);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async getInputValue(selector: string) {
|
|
256
|
+
return await this.page.inputValue(selector);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async isCheckboxChecked(labelText: string) {
|
|
260
|
+
const label = this.page.locator(`label:has-text(\"${labelText}\")`);
|
|
261
|
+
const checkboxId = await label.getAttribute('for');
|
|
262
|
+
return checkboxId ? await this.page.locator(`#${checkboxId}`).isChecked() : false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 8. Test Data Manager (tests/utils/testData.ts)
|
|
268
|
+
```typescript
|
|
269
|
+
import { DataReader } from './dataReader';
|
|
270
|
+
|
|
271
|
+
export class TestDataManager {
|
|
272
|
+
static loadUsers() {
|
|
273
|
+
return DataReader.readJSON<{ users: any[] }>('users.json');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static async loadFormData() {
|
|
277
|
+
return await DataReader.readCSV('formData.csv');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static loadConfig() {
|
|
281
|
+
return DataReader.readYAML('config.yaml');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static getUserByUsername(username: string) {
|
|
285
|
+
const data = this.loadUsers();
|
|
286
|
+
return DataReader.getRecordByKey(data.users, 'username', username);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static async getFormDataByScenario(scenarioName: string) {
|
|
290
|
+
const data = await this.loadFormData();
|
|
291
|
+
return DataReader.getRecordByKey(data, 'scenario', scenarioName);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 9. Base Page (tests/pages/BasePage.ts)
|
|
297
|
+
```typescript
|
|
298
|
+
import type { Page } from '@playwright/test';
|
|
299
|
+
|
|
300
|
+
export class BasePage {
|
|
301
|
+
readonly page: Page;
|
|
302
|
+
constructor(page: Page) {
|
|
303
|
+
this.page = page;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async navigateTo(url: string) {
|
|
307
|
+
await this.page.goto(url);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async click(selector: string) {
|
|
311
|
+
await this.page.click(selector);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async fill(selector: string, text: string) {
|
|
315
|
+
await this.page.fill(selector, text);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async getText(selector: string) {
|
|
319
|
+
return await this.page.textContent(selector);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async waitForElement(selector: string, timeout = 5000) {
|
|
323
|
+
await this.page.waitForSelector(selector, { timeout });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async isVisible(selector: string) {
|
|
327
|
+
return await this.page.locator(selector).isVisible();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 10. Page Object - Login (tests/pages/LoginPage.ts)
|
|
333
|
+
```typescript
|
|
334
|
+
import { BasePage } from './BasePage';
|
|
335
|
+
|
|
336
|
+
export class LoginPage extends BasePage {
|
|
337
|
+
readonly usernameField = '[data-test="username"]';
|
|
338
|
+
readonly passwordField = '[data-test="password"]';
|
|
339
|
+
readonly loginButton = '[data-test="login-button"]';
|
|
340
|
+
|
|
341
|
+
constructor(page: import('@playwright/test').Page) {
|
|
342
|
+
super(page);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async goto() {
|
|
346
|
+
await this.page.goto('https://www.saucedemo.com/');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async login(username: string, password: string) {
|
|
350
|
+
await this.page.fill(this.usernameField, username);
|
|
351
|
+
await this.page.fill(this.passwordField, password);
|
|
352
|
+
await this.page.click(this.loginButton);
|
|
353
|
+
await this.page.waitForURL('**/inventory.html');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async isLoaded() {
|
|
357
|
+
return await this.page.locator(this.usernameField).isVisible();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 11. Fixtures Setup (tests/fixtures/base.fixture.ts)
|
|
363
|
+
```typescript
|
|
364
|
+
import { test as base, expect } from '@playwright/test';
|
|
365
|
+
import { LoginPage } from '../pages/LoginPage';
|
|
366
|
+
import { InventoryPage } from '../pages/InventoryPage';
|
|
367
|
+
import { FormPage } from '../pages/FormPage';
|
|
368
|
+
|
|
369
|
+
export const test = base.extend({
|
|
370
|
+
loginPage: async ({ page }, use) => {
|
|
371
|
+
const loginPage = new LoginPage(page);
|
|
372
|
+
await use(loginPage);
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
inventoryPage: async ({ page }, use) => {
|
|
376
|
+
const inventoryPage = new InventoryPage(page);
|
|
377
|
+
await use(inventoryPage);
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
formPage: async ({ page }, use) => {
|
|
381
|
+
const formPage = new FormPage(page);
|
|
382
|
+
await use(formPage);
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
authenticatedUser: async ({ page, loginPage }, use) => {
|
|
386
|
+
await loginPage.goto();
|
|
387
|
+
await loginPage.login('standard_user', 'secret_sauce');
|
|
388
|
+
await use({ page, loginPage });
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
export { expect };
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 12. Simple Test Example
|
|
396
|
+
This template focuses on Playwright-BDD generated tests (`tests/.features-gen`).
|
|
397
|
+
|
|
398
|
+
### 13. BDD Feature Example (tests/features/login.feature)
|
|
399
|
+
```gherkin
|
|
400
|
+
Feature: Login to SauceDemo
|
|
401
|
+
As a user
|
|
402
|
+
I want to login to the application
|
|
403
|
+
So that I can access the inventory
|
|
404
|
+
|
|
405
|
+
Scenario: User logs in with valid credentials
|
|
406
|
+
Given I navigate to the SauceDemo application
|
|
407
|
+
When I login with username "standard_user" and password "secret_sauce"
|
|
408
|
+
Then I should see the inventory page
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 14. Test Data Files
|
|
412
|
+
|
|
413
|
+
**tests/data/users.json**
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"users": [
|
|
417
|
+
{
|
|
418
|
+
"id": "1",
|
|
419
|
+
"username": "standard_user",
|
|
420
|
+
"password": "secret_sauce",
|
|
421
|
+
"email": "user@example.com",
|
|
422
|
+
"firstName": "John",
|
|
423
|
+
"lastName": "Doe"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Note: Playwright-BDD generates test files into `tests/.features-gen` (via `npx bddgen`) and the `ui` project in `playwright.config.ts` points at `./.features-gen`. Run BDD generation before running the `ui` project.
|
|
430
|
+
|
|
431
|
+
**tests/data/config.yaml**
|
|
432
|
+
```yaml
|
|
433
|
+
app:
|
|
434
|
+
url: https://www.saucedemo.com/
|
|
435
|
+
timeout: 30000
|
|
436
|
+
users:
|
|
437
|
+
standard:
|
|
438
|
+
username: standard_user
|
|
439
|
+
password: secret_sauce
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
# Run Tests
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# Run all tests in parallel
|
|
66
446
|
npm test
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
447
|
+
|
|
448
|
+
# Run with headed browser
|
|
449
|
+
npm run test:headed
|
|
450
|
+
|
|
451
|
+
# Debug mode
|
|
452
|
+
npm run test:debug
|
|
453
|
+
|
|
454
|
+
# Run BDD scenarios (uses ts-node)
|
|
455
|
+
npm run test:bdd
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
# BDD generation + run
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
# Generate Playwright-BDD test files (outputs to tests/.features-gen)
|
|
462
|
+
npx bddgen
|
|
463
|
+
|
|
464
|
+
# Run only the UI BDD project (Playwright picks up generated files in .features-gen)
|
|
465
|
+
npx playwright test --project=ui
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Best Practices
|
|
469
|
+
|
|
470
|
+
- **Page Object Model**: Encapsulate selectors and flows in typed page classes
|
|
471
|
+
- **Fixtures**: Use `base.fixture.ts` for shared setup/teardown
|
|
472
|
+
- **Test Data**: Externalize in JSON/CSV/YAML and load via `DataReader`
|
|
473
|
+
- **Form Helpers**: Keep complex interactions in `FormHelper` with strong types
|
|
474
|
+
- **BDD Support**: Add Gherkin scenarios and TypeScript step defs when needed
|
|
475
|
+
- **Parallel Execution**: Configure `workers` in `playwright.config.ts`
|
|
476
|
+
- **Type Safety**: Prefer typed interfaces for test data and page objects
|
|
477
|
+
|
|
478
|
+
Note: This template is TypeScript-first. Copy the structure, install the devDependencies listed, and adjust types/interfaces for your application.
|