agents-cli-automation 1.0.5 → 1.0.7
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 +196 -0
- package/package.json +2 -2
- package/src/commands/init.js +74 -9
- package/src/templates/playwright-agent-api.md +190 -0
- package/src/templates/playwright-agent-csharp.md +521 -0
- package/src/templates/playwright-agent-java.md +471 -0
- package/src/templates/playwright-agent-js.md +461 -0
- package/src/templates/playwright-agent-ts.md +1419 -0
- package/src/templates/playwright-agent.md +1359 -52
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
name: Create Playwright Framework - UI Testing (JavaScript)
|
|
2
|
+
description: Creates a production-ready Playwright UI automation framework in JavaScript (ES Modules) with all dependencies, fixtures, and test configurations.
|
|
3
|
+
argument-hint: "framework requirements, e.g., 'JavaScript with UI tests'"
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Playwright UI Testing Framework - JavaScript (ES Modules)
|
|
8
|
+
|
|
9
|
+
This agent creates a production-ready Playwright framework in modern JavaScript (ES Modules) optimized for UI automation.
|
|
10
|
+
|
|
11
|
+
## Capabilities
|
|
12
|
+
- **JavaScript (ES Modules)** - Chromium only with latest syntax
|
|
13
|
+
- No build step required - direct ES modules support
|
|
14
|
+
- UI, component, and end-to-end testing
|
|
15
|
+
- **Playwright BDD** - Gherkin format with step definitions
|
|
16
|
+
- **Page Object Model (POM)** - Reusable page classes
|
|
17
|
+
- **Fixtures** - Pre-built browser & page fixtures
|
|
18
|
+
- **Parallel execution** - Tests run simultaneously 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
|
+
|
|
27
|
+
## Setup Instructions
|
|
28
|
+
|
|
29
|
+
### 1. Install Dependencies (Chromium Only)
|
|
30
|
+
```bash
|
|
31
|
+
npm install
|
|
32
|
+
npm install --save-dev @cucumber/cucumber csv-parser js-yaml
|
|
33
|
+
npx playwright install chromium
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Update package.json
|
|
37
|
+
Add `"type": "module"` for ES modules support:
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"type": "module",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"start": "node server.js",
|
|
43
|
+
"test": "playwright test",
|
|
44
|
+
"test:headed": "playwright test --headed",
|
|
45
|
+
"test:debug": "playwright test --debug",
|
|
46
|
+
"test:ui": "playwright test --ui",
|
|
47
|
+
"test:bdd": "npx @cucumber/cucumber-js",
|
|
48
|
+
"test:bdd:report": "npx @cucumber/cucumber-js --format json:cucumber-report.json"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Playwright Configuration (playwright.config.js - ES Module)
|
|
54
|
+
```javascript
|
|
55
|
+
export default {
|
|
56
|
+
testDir: './tests/specs',
|
|
57
|
+
fullyParallel: true,
|
|
58
|
+
workers: process.env.CI ? 1 : 4,
|
|
59
|
+
retries: 1,
|
|
60
|
+
timeout: 30000,
|
|
61
|
+
expect: {
|
|
62
|
+
timeout: 5000,
|
|
63
|
+
},
|
|
64
|
+
use: {
|
|
65
|
+
browserName: 'chromium',
|
|
66
|
+
headless: true,
|
|
67
|
+
screenshot: 'only-on-failure',
|
|
68
|
+
video: 'retain-on-failure',
|
|
69
|
+
trace: 'on-first-retry',
|
|
70
|
+
},
|
|
71
|
+
webServer: {
|
|
72
|
+
command: 'npm start',
|
|
73
|
+
url: 'http://localhost:3000',
|
|
74
|
+
reuseExistingServer: !process.env.CI,
|
|
75
|
+
},
|
|
76
|
+
reporter: [
|
|
77
|
+
['html'],
|
|
78
|
+
['json', { outputFile: 'test-results/results.json' }],
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 4. Project Structure
|
|
84
|
+
```
|
|
85
|
+
project/
|
|
86
|
+
├── tests/
|
|
87
|
+
│ ├── fixtures/
|
|
88
|
+
│ │ └── base.fixture.js # Shared fixtures
|
|
89
|
+
│ ├── pages/
|
|
90
|
+
│ │ ├── BasePage.js # Base page object
|
|
91
|
+
│ │ ├── LoginPage.js # Login page object
|
|
92
|
+
│ │ ├── InventoryPage.js # Inventory page object
|
|
93
|
+
│ │ └── FormPage.js # Form page object
|
|
94
|
+
│ ├── utils/ # Utility functions
|
|
95
|
+
│ │ ├── dataReader.js
|
|
96
|
+
│ │ ├── formHelper.js
|
|
97
|
+
│ │ └── testData.js
|
|
98
|
+
│ ├── data/ # Test data files
|
|
99
|
+
│ │ ├── users.json
|
|
100
|
+
│ │ ├── formData.csv
|
|
101
|
+
│ │ └── config.yaml
|
|
102
|
+
│ ├── features/ # BDD Gherkin scenarios
|
|
103
|
+
│ │ ├── login.feature
|
|
104
|
+
│ │ └── shopping.feature
|
|
105
|
+
│ ├── steps/ # BDD Step definitions
|
|
106
|
+
│ │ ├── loginSteps.js
|
|
107
|
+
│ │ └── shoppingSteps.js
|
|
108
|
+
│ └── specs/
|
|
109
|
+
│ ├── saucedemo.spec.js # Traditional tests
|
|
110
|
+
│ └── formTest.spec.js
|
|
111
|
+
├── cucumber.js # Cucumber config
|
|
112
|
+
├── playwright.config.js # Playwright config
|
|
113
|
+
└── package.json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 5. Cucumber Configuration (cucumber.js)
|
|
117
|
+
```javascript
|
|
118
|
+
export default {
|
|
119
|
+
default: {
|
|
120
|
+
paths: ['tests/features/**/*.feature'],
|
|
121
|
+
require: ['tests/steps/**/*.js'],
|
|
122
|
+
format: ['progress', 'html:cucumber-report.html'],
|
|
123
|
+
parallel: 4,
|
|
124
|
+
worldParameters: {
|
|
125
|
+
appUrl: 'https://www.saucedemo.com/',
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 6. Data Reader Utility (tests/utils/dataReader.js)
|
|
132
|
+
```javascript
|
|
133
|
+
import fs from 'fs';
|
|
134
|
+
import path from 'path';
|
|
135
|
+
import csv from 'csv-parser';
|
|
136
|
+
import yaml from 'js-yaml';
|
|
137
|
+
import { fileURLToPath } from 'url';
|
|
138
|
+
|
|
139
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
140
|
+
const DATA_DIR = path.join(path.resolve(__dirname, '..'), 'data');
|
|
141
|
+
|
|
142
|
+
export class DataReader {
|
|
143
|
+
// Read JSON file
|
|
144
|
+
static readJSON(filename) {
|
|
145
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
146
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
147
|
+
return JSON.parse(data);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Read YAML file
|
|
151
|
+
static readYAML(filename) {
|
|
152
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
153
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
154
|
+
return yaml.load(data);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Read CSV file (returns array of objects)
|
|
158
|
+
static async readCSV(filename) {
|
|
159
|
+
const filePath = path.join(DATA_DIR, filename);
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const records = [];
|
|
162
|
+
fs.createReadStream(filePath)
|
|
163
|
+
.pipe(csv())
|
|
164
|
+
.on('data', (data) => records.push(data))
|
|
165
|
+
.on('end', () => resolve(records))
|
|
166
|
+
.on('error', reject);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Get single record by ID or key
|
|
171
|
+
static getRecordByKey(data, key, value) {
|
|
172
|
+
if (Array.isArray(data)) {
|
|
173
|
+
return data.find(record => record[key] === value);
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 7. Form Helper Utility (tests/utils/formHelper.js)
|
|
181
|
+
```javascript
|
|
182
|
+
export class FormHelper {
|
|
183
|
+
constructor(page) {
|
|
184
|
+
this.page = page;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Fill text input
|
|
188
|
+
async fillInput(selector, value) {
|
|
189
|
+
await this.page.fill(selector, value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Fill textarea
|
|
193
|
+
async fillTextArea(selector, value) {
|
|
194
|
+
await this.page.locator(selector).clear();
|
|
195
|
+
await this.page.locator(selector).type(value, { delay: 50 });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Select option from dropdown (non-select element)
|
|
199
|
+
async selectDropdown(dropdownSelector, optionText) {
|
|
200
|
+
await this.page.click(dropdownSelector);
|
|
201
|
+
await this.page.click(`text="${optionText}"`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Select radio button by label text
|
|
205
|
+
async selectRadioButton(labelText) {
|
|
206
|
+
const label = this.page.locator(`label:has-text("${labelText}")`);
|
|
207
|
+
const radioId = await label.getAttribute('for');
|
|
208
|
+
if (radioId) {
|
|
209
|
+
await this.page.locator(`#${radioId}`).click();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check checkbox by label text
|
|
214
|
+
async checkCheckbox(labelText) {
|
|
215
|
+
const label = this.page.locator(`label:has-text("${labelText}")`);
|
|
216
|
+
const checkboxId = await label.getAttribute('for');
|
|
217
|
+
if (checkboxId) {
|
|
218
|
+
const checkbox = this.page.locator(`#${checkboxId}`);
|
|
219
|
+
if (!(await checkbox.isChecked())) {
|
|
220
|
+
await checkbox.click();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Select multiple checkboxes
|
|
226
|
+
async checkMultiple(labels) {
|
|
227
|
+
for (const label of labels) {
|
|
228
|
+
await this.checkCheckbox(label);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Verify form value
|
|
233
|
+
async getInputValue(selector) {
|
|
234
|
+
return await this.page.inputValue(selector);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Verify checkbox is checked
|
|
238
|
+
async isCheckboxChecked(labelText) {
|
|
239
|
+
const label = this.page.locator(`label:has-text("${labelText}")`);
|
|
240
|
+
const checkboxId = await label.getAttribute('for');
|
|
241
|
+
return checkboxId ? await this.page.locator(`#${checkboxId}`).isChecked() : false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 8. Test Data Manager (tests/utils/testData.js)
|
|
247
|
+
```javascript
|
|
248
|
+
import { DataReader } from './dataReader.js';
|
|
249
|
+
|
|
250
|
+
export class TestDataManager {
|
|
251
|
+
static loadUsers() {
|
|
252
|
+
return DataReader.readJSON('users.json');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static async loadFormData() {
|
|
256
|
+
return await DataReader.readCSV('formData.csv');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
static loadConfig() {
|
|
260
|
+
return DataReader.readYAML('config.yaml');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
static getUserByUsername(username) {
|
|
264
|
+
const data = this.loadUsers();
|
|
265
|
+
return DataReader.getRecordByKey(data.users, 'username', username);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static async getFormDataByScenario(scenarioName) {
|
|
269
|
+
const data = await this.loadFormData();
|
|
270
|
+
return DataReader.getRecordByKey(data, 'scenario', scenarioName);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 9. Base Page (tests/pages/BasePage.js)
|
|
276
|
+
```javascript
|
|
277
|
+
export class BasePage {
|
|
278
|
+
constructor(page) {
|
|
279
|
+
this.page = page;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async navigateTo(url) {
|
|
283
|
+
await this.page.goto(url);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async click(selector) {
|
|
287
|
+
await this.page.click(selector);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async fill(selector, text) {
|
|
291
|
+
await this.page.fill(selector, text);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async getText(selector) {
|
|
295
|
+
return await this.page.textContent(selector);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async waitForElement(selector, timeout = 5000) {
|
|
299
|
+
await this.page.waitForSelector(selector, { timeout });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async isVisible(selector) {
|
|
303
|
+
return await this.page.locator(selector).isVisible();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### 10. Page Object - Login (tests/pages/LoginPage.js)
|
|
309
|
+
```javascript
|
|
310
|
+
import { BasePage } from './BasePage.js';
|
|
311
|
+
|
|
312
|
+
export class LoginPage extends BasePage {
|
|
313
|
+
constructor(page) {
|
|
314
|
+
super(page);
|
|
315
|
+
this.usernameField = '[data-test="username"]';
|
|
316
|
+
this.passwordField = '[data-test="password"]';
|
|
317
|
+
this.loginButton = '[data-test="login-button"]';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async goto() {
|
|
321
|
+
await this.page.goto('https://www.saucedemo.com/');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async login(username, password) {
|
|
325
|
+
await this.page.fill(this.usernameField, username);
|
|
326
|
+
await this.page.fill(this.passwordField, password);
|
|
327
|
+
await this.page.click(this.loginButton);
|
|
328
|
+
await this.page.waitForURL('**/inventory.html');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async isLoaded() {
|
|
332
|
+
return await this.page.locator(this.usernameField).isVisible();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 11. Fixtures Setup (tests/fixtures/base.fixture.js)
|
|
338
|
+
```javascript
|
|
339
|
+
import { test as base, expect } from '@playwright/test';
|
|
340
|
+
import { LoginPage } from '../pages/LoginPage.js';
|
|
341
|
+
import { InventoryPage } from '../pages/InventoryPage.js';
|
|
342
|
+
import { FormPage } from '../pages/FormPage.js';
|
|
343
|
+
|
|
344
|
+
export const test = base.extend({
|
|
345
|
+
loginPage: async ({ page }, use) => {
|
|
346
|
+
const loginPage = new LoginPage(page);
|
|
347
|
+
await use(loginPage);
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
inventoryPage: async ({ page }, use) => {
|
|
351
|
+
const inventoryPage = new InventoryPage(page);
|
|
352
|
+
await use(inventoryPage);
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
formPage: async ({ page }, use) => {
|
|
356
|
+
const formPage = new FormPage(page);
|
|
357
|
+
await use(formPage);
|
|
358
|
+
},
|
|
359
|
+
|
|
360
|
+
authenticatedUser: async ({ page, loginPage }, use) => {
|
|
361
|
+
await loginPage.goto();
|
|
362
|
+
await loginPage.login('standard_user', 'secret_sauce');
|
|
363
|
+
await use({ page, loginPage });
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
export { expect };
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 12. Simple Test Example (tests/specs/saucedemo.spec.js)
|
|
371
|
+
```javascript
|
|
372
|
+
import { test, expect } from '../fixtures/base.fixture.js';
|
|
373
|
+
|
|
374
|
+
test.describe('SauceDemo E2E Tests', () => {
|
|
375
|
+
test('Login with valid credentials', async ({ loginPage, page }) => {
|
|
376
|
+
await loginPage.goto();
|
|
377
|
+
await loginPage.login('standard_user', 'secret_sauce');
|
|
378
|
+
expect(page.url()).toContain('inventory.html');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('Add product to cart', async ({ authenticatedUser, inventoryPage }) => {
|
|
382
|
+
await inventoryPage.addProductToCart();
|
|
383
|
+
const cartCount = await inventoryPage.getCartCount();
|
|
384
|
+
expect(cartCount).toBe('1');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 13. BDD Feature Example (tests/features/login.feature)
|
|
390
|
+
```gherkin
|
|
391
|
+
Feature: Login to SauceDemo
|
|
392
|
+
As a user
|
|
393
|
+
I want to login to the application
|
|
394
|
+
So that I can access the inventory
|
|
395
|
+
|
|
396
|
+
Scenario: User logs in with valid credentials
|
|
397
|
+
Given I navigate to the SauceDemo application
|
|
398
|
+
When I login with username "standard_user" and password "secret_sauce"
|
|
399
|
+
Then I should see the inventory page
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 14. Test Data Files
|
|
403
|
+
|
|
404
|
+
**tests/data/users.json**
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"users": [
|
|
408
|
+
{
|
|
409
|
+
"id": "1",
|
|
410
|
+
"username": "standard_user",
|
|
411
|
+
"password": "secret_sauce",
|
|
412
|
+
"email": "user@example.com",
|
|
413
|
+
"firstName": "John",
|
|
414
|
+
"lastName": "Doe"
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**tests/data/config.yaml**
|
|
421
|
+
```yaml
|
|
422
|
+
app:
|
|
423
|
+
url: https://www.saucedemo.com/
|
|
424
|
+
timeout: 30000
|
|
425
|
+
users:
|
|
426
|
+
standard:
|
|
427
|
+
username: standard_user
|
|
428
|
+
password: secret_sauce
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Run Tests
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
# Run all tests in parallel
|
|
435
|
+
npm test
|
|
436
|
+
|
|
437
|
+
# Run specific test file
|
|
438
|
+
npm test tests/specs/saucedemo.spec.js
|
|
439
|
+
|
|
440
|
+
# Run with headed browser
|
|
441
|
+
npm run test:headed
|
|
442
|
+
|
|
443
|
+
# Debug mode
|
|
444
|
+
npm run test:debug
|
|
445
|
+
|
|
446
|
+
# Run BDD scenarios
|
|
447
|
+
npm run test:bdd
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Best Practices
|
|
451
|
+
✅ **Page Object Model** - Encapsulate all selectors in page classes
|
|
452
|
+
✅ **Fixtures** - Reusable page objects and setup/teardown
|
|
453
|
+
✅ **Test Data** - Externalize data in JSON, CSV, YAML
|
|
454
|
+
✅ **Form Helpers** - Handle dropdowns, radios, checkboxes
|
|
455
|
+
✅ **BDD Support** - Write scenarios in Gherkin
|
|
456
|
+
✅ **Parallel Execution** - 4 workers by default
|
|
457
|
+
✅ **Chromium Only** - Single browser for consistency
|
|
458
|
+
✅ **Modern ES Modules** - Clean import/export syntax
|
|
459
|
+
✅ **No Build Step** - Node.js 18+ direct ES module support
|
|
460
|
+
|
|
461
|
+
**Note:** This framework uses JavaScript with ES Modules, Chromium only, and parallel execution. Start by copying the project structure and customizing page objects for your application.
|