agents-cli-automation 1.0.9 → 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 +348 -606
package/package.json
CHANGED
|
@@ -1,232 +1,59 @@
|
|
|
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
|
+
|
|
1
5
|
---
|
|
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
|
-
Scaffold steps (task: scaffold) 1. Validate targetPath exists or create it. 2. Create the repository directory tree (only new directories created). 3. For each file present in the source repo, preserve content. For missing, create placeholders containing a header, responsibilities, and TODO notes. 4. When createSample=true or task=create-sample-tests, add Playwright scaffolding: - playwright.config.ts (simple config using chromium and testDir tests/ui). - tests/ui/saucedemo.spec.ts (sample test: navigate to uiTarget, perform login using standard Saucedemo credentials from sample data, assert inventory list visible). - ui/page/LoginPage.ts and ui/page/InventoryPage.ts page objects. - Update or create package.json scripts: test, test:ui, install:browsers. 5. If installDeps=true, run npm ci (or npm install) then npx playwright install (use --with-deps where appropriate). 6. Run verification: npm run test:ui -- --list or npx playwright test --list and optionally execute the sample test. Suggested commands (PowerShell / cross-platform)
|
|
38
|
-
powershell
|
|
39
|
-
# install dependencies
|
|
40
|
-
npm ci
|
|
41
|
-
# install Playwright browsers
|
|
42
|
-
npx playwright install
|
|
43
|
-
# run unit/api tests
|
|
44
|
-
npm test
|
|
45
|
-
# run Playwright E2E tests
|
|
46
|
-
npm run test:ui
|
|
47
|
-
Sample tests/ui/saucedemo.spec.ts behavior (generated when requested) - Open uiTarget (defaults to https://www.saucedemo.com/). - Use LoginPage to enter standard_user / secret_sauce and submit. - Wait for InventoryPage to display and assert at least one product is visible. Outputs - Created directories and placeholder files in targetPath. - Generated Playwright UI scaffolding and a runnable sample test when requested. - Verification logs for dependency install and sample test runs. - A summary list of placeholders and recommended edits (e.g., secrets, environment overrides). Safety & constraints - The agent will not overwrite existing non-placeholder files unless force=true is set. - The agent will not embed secrets into scaffolded files; secrets must be provided separately or via env vars. Example usages - Scaffold without installing deps: { "task": "scaffold", "installDeps": false } - Scaffold + install deps + create samples: { "task": "scaffold", "installDeps": true, "createSample": true } - Create only sample UI tests: { "task": "create-sample-tests", "targetPath": ".", "uiTarget": "https://www.saucedemo.com/" } - Describe repo: { "task": "describe" } Maintainer notes - Keep the generated playwright.config.ts and sample tests minimal — encourage contributors to migrate tests into the repository's existing structure and page objects. - When adding real credentials, instruct users to use environment variables or a secure secrets store. --- Automation agent specification end. Included file templates (drop-in ready) Below are copy-paste-ready templates for the key files you can generate from this agent to produce a runnable Playwright UI framework targeting https://www.saucedemo.com/. 1) package.json (merge with your existing package.json or use as a template)
|
|
48
|
-
json
|
|
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
|
|
49
41
|
{
|
|
50
|
-
"name": "poc_bbc",
|
|
51
|
-
"version": "1.0.0",
|
|
52
|
-
"type": "commonjs",
|
|
53
42
|
"scripts": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
43
|
+
"start": "node server.js",
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
56
45
|
"test": "npx playwright test",
|
|
57
|
-
"test:
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
"playwright-bdd": "^8.4.2",
|
|
63
|
-
"ts-node": "^10.9.2",
|
|
64
|
-
"typescript": "^5.9.3"
|
|
65
|
-
},
|
|
66
|
-
"dependencies": {
|
|
67
|
-
"dotenv": "^17.2.3",
|
|
68
|
-
"mssql": "^12.2.0"
|
|
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"
|
|
69
51
|
}
|
|
70
52
|
}
|
|
71
|
-
|
|
72
|
-
typescript
|
|
73
|
-
import 'dotenv/config';
|
|
74
|
-
import { defineConfig, devices } from '@playwright/test';
|
|
75
|
-
import { defineBddConfig } from 'playwright-bdd';
|
|
53
|
+
```
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
steps: ['tests/ui/steps/**/*.steps.ts','tests/fixtures/pages.fixture.ts'],
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
export default defineConfig({
|
|
83
|
-
testDir: './tests',
|
|
84
|
-
timeout: 20 * 1000,
|
|
85
|
-
expect: { timeout: 10 * 1000 },
|
|
86
|
-
retries: process.env.CI ? 1 : 0,
|
|
87
|
-
reporter: [['html', { open: 'never' }], ['list']],
|
|
88
|
-
use: { trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure' },
|
|
89
|
-
projects: [
|
|
90
|
-
{
|
|
91
|
-
name: 'ui',
|
|
92
|
-
testDir: './.features-gen',
|
|
93
|
-
testMatch: '**/*.feature.spec.*',
|
|
94
|
-
use: { ...devices['Desktop Chrome'], headless: true, baseURL: process.env.UI_BASE_URL },
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'api',
|
|
98
|
-
testMatch: /.*\.api\.spec\.ts/,
|
|
99
|
-
use: { browserName: undefined, baseURL: process.env.API_BASE_URL, extraHTTPHeaders: { 'Content-Type': 'application/json' } },
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
name: 'db',
|
|
103
|
-
testMatch: /.*\.db\.spec\.ts/,
|
|
104
|
-
use: { browserName: undefined },
|
|
105
|
-
},
|
|
106
|
-
// Plain UI tests (non-BDD)
|
|
107
|
-
{
|
|
108
|
-
name: 'ui-plain',
|
|
109
|
-
testDir: './tests/ui',
|
|
110
|
-
use: { ...devices['Desktop Chrome'], headless: true, baseURL: process.env.UI_BASE_URL || 'https://www.saucedemo.com/' },
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
});
|
|
114
|
-
3) ui/page/LoginPage.ts (page object)
|
|
115
|
-
typescript
|
|
116
|
-
import { Page } from '@playwright/test';
|
|
117
|
-
|
|
118
|
-
export class LoginPage {
|
|
119
|
-
readonly page: Page;
|
|
120
|
-
constructor(page: Page) { this.page = page; }
|
|
121
|
-
async goto() { await this.page.goto('/'); }
|
|
122
|
-
async login(username: string, password: string) {
|
|
123
|
-
await this.page.fill('#user-name', username);
|
|
124
|
-
await this.page.fill('#password', password);
|
|
125
|
-
await this.page.click('#login-button');
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
4) ui/page/InventoryPage.ts (page object)
|
|
129
|
-
typescript
|
|
130
|
-
import { Page, Locator } from '@playwright/test';
|
|
131
|
-
|
|
132
|
-
export class InventoryPage {
|
|
133
|
-
readonly page: Page;
|
|
134
|
-
readonly items: Locator;
|
|
135
|
-
constructor(page: Page) { this.page = page; this.items = page.locator('.inventory_item'); }
|
|
136
|
-
async isLoaded(): Promise<boolean> { return await this.items.first().isVisible(); }
|
|
137
|
-
getItems(): Locator { return this.items; }
|
|
138
|
-
}
|
|
139
|
-
5) tests/ui/saucedemo.spec.ts (plain Playwright test)
|
|
140
|
-
typescript
|
|
141
|
-
import { test, expect } from '@playwright/test';
|
|
142
|
-
import { LoginPage } from '../../ui/page/LoginPage';
|
|
143
|
-
import { InventoryPage } from '../../ui/page/InventoryPage';
|
|
144
|
-
|
|
145
|
-
test.describe('Saucedemo smoke', () => {
|
|
146
|
-
test('login and inventory visible', async ({ page, baseURL }) => {
|
|
147
|
-
const login = new LoginPage(page);
|
|
148
|
-
const inventory = new InventoryPage(page);
|
|
149
|
-
await page.goto(baseURL || 'https://www.saucedemo.com/');
|
|
150
|
-
await login.login('standard_user', 'secret_sauce');
|
|
151
|
-
await expect(page.locator('.inventory_list')).toBeVisible();
|
|
152
|
-
await expect(inventory.getItems().first()).toBeVisible();
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
6) .env.example (environment variables sample)
|
|
156
|
-
text
|
|
157
|
-
UI_BASE_URL=https://www.saucedemo.com/
|
|
158
|
-
API_BASE_URL=http://localhost:3000
|
|
159
|
-
Usage notes - The templates above are ready to paste into files. The agent's scaffold task may create them automatically when createSample=true. - To run locally after creating files:
|
|
160
|
-
powershell
|
|
161
|
-
npm ci
|
|
162
|
-
npm run install:browsers
|
|
163
|
-
npm run test:ui
|
|
164
|
-
- The test uses Saucedemo public credentials: standard_user / secret_sauce. If you want, I can now: (choose one) - Generate these files automatically in the repo (I will create the files listed above). - Or leave this agent file as the canonical template and not modify the filesystem. Repository file list and embedded key file contents Below is a repository file list (captured from the workspace) followed by the full contents of selected key files (UI, tests, core helpers). Use these as drop-in references when running the agent's create/scaffold actions. File list (workspace snapshot):
|
|
165
|
-
c:\poc_bbc\workflows\schd-group\api\schd-group.api.ts
|
|
166
|
-
c:\poc_bbc\workflows\schd-group\invariants\db.invariants.ts
|
|
167
|
-
c:\poc_bbc\workflows\schd-group\invariants\api.invariants.ts
|
|
168
|
-
c:\poc_bbc\workflows\schd-group\invariants\permission.invariants.ts
|
|
169
|
-
c:\poc_bbc\core\permission\permission.types.ts
|
|
170
|
-
c:\poc_bbc\core\permission\permission.schd-group.ts
|
|
171
|
-
c:\poc_bbc\workflows\schd-group\db\schd-group.queries.ts
|
|
172
|
-
c:\poc_bbc\core\auth\roles.ts
|
|
173
|
-
c:\poc_bbc\core\db\queries.ts
|
|
174
|
-
c:\poc_bbc\core\db\connection.ts
|
|
175
|
-
c:\poc_bbc\tsconfig.json
|
|
176
|
-
c:\poc_bbc\core\api\apiClient.ts
|
|
177
|
-
c:\poc_bbc\playwright.config.ts
|
|
178
|
-
c:\poc_bbc\package.json
|
|
179
|
-
c:\poc_bbc\package-lock.json
|
|
180
|
-
c:\poc_bbc\bitbucket-pipelines.yml
|
|
181
|
-
c:\poc_bbc\.gitignore
|
|
182
|
-
c:\poc_bbc\.github\agents\automation.md.agent.md
|
|
183
|
-
c:\poc_bbc\tests\utils\readJson.ts
|
|
184
|
-
c:\poc_bbc\tests\utils\formFiller.ts
|
|
185
|
-
c:\poc_bbc\tests\fixtures\test.fixture.ts
|
|
186
|
-
c:\poc_bbc\tests\fixtures\pages.fixture.ts
|
|
187
|
-
c:\poc_bbc\tests\types\formField.ts
|
|
188
|
-
c:\poc_bbc\tests\ui\steps\scheduling-groups.steps.ts
|
|
189
|
-
c:\poc_bbc\tests\ui\steps\booking.steps.ts
|
|
190
|
-
c:\poc_bbc\tests\ui\features\scheduling-groups.feature
|
|
191
|
-
c:\poc_bbc\tests\ui\features\booking.feature
|
|
192
|
-
c:\poc_bbc\tests\ui\page\HomePage.ts
|
|
193
|
-
c:\poc_bbc\tests\db\01-connection.db.spec.ts
|
|
194
|
-
c:\poc_bbc\tests\api\schd-group-create.api.spec.ts
|
|
195
|
-
c:\poc_bbc\.bitbucket\README.md
|
|
196
|
-
c:\poc_bbc\.bitbucket\pull_request_template.md
|
|
197
|
-
c:\poc_bbc\tests\data\facilityFormData.json
|
|
198
|
-
Embedded key file contents 1) package.json
|
|
199
|
-
json
|
|
200
|
-
{
|
|
201
|
-
"name": "poc_bbc",
|
|
202
|
-
"version": "1.0.0",
|
|
203
|
-
"description": "",
|
|
204
|
-
"main": "index.js",
|
|
205
|
-
"scripts": {
|
|
206
|
-
"dbtest": "npx playwright test --project=db",
|
|
207
|
-
"uitest": "npx bddgen & npx playwright test --project=ui --headed"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
},
|
|
211
|
-
"keywords": [],
|
|
212
|
-
"author": "",
|
|
213
|
-
"license": "ISC",
|
|
214
|
-
"type": "commonjs",
|
|
215
|
-
"devDependencies": {
|
|
216
|
-
"@playwright/test": "^1.58.2",
|
|
217
|
-
"@types/mssql": "^9.1.9",
|
|
218
|
-
"@types/node": "^25.2.3",
|
|
219
|
-
"playwright-bdd": "^8.4.2",
|
|
220
|
-
"ts-node": "^10.9.2",
|
|
221
|
-
"typescript": "^5.9.3"
|
|
222
|
-
},
|
|
223
|
-
"dependencies": {
|
|
224
|
-
"dotenv": "^17.2.3",
|
|
225
|
-
"mssql": "^12.2.0"
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
2) playwright.config.ts
|
|
229
|
-
typescript
|
|
55
|
+
### 3. Playwright Configuration (playwright.config.ts)
|
|
56
|
+
```typescript
|
|
230
57
|
import 'dotenv/config';
|
|
231
58
|
import { defineConfig, devices } from '@playwright/test';
|
|
232
59
|
import { defineBddConfig } from 'playwright-bdd';
|
|
@@ -300,437 +127,352 @@ export default defineConfig({
|
|
|
300
127
|
},
|
|
301
128
|
],
|
|
302
129
|
});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
options: {
|
|
346
|
-
trustServerCertificate: true,
|
|
347
|
-
},
|
|
348
|
-
};
|
|
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';
|
|
349
172
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
5) core/db/queries.ts
|
|
354
|
-
typescript
|
|
355
|
-
import type { ConnectionPool } from 'mssql';
|
|
356
|
-
|
|
357
|
-
export async function listTables(db: ConnectionPool) {
|
|
358
|
-
const result = await db.request().query(`
|
|
359
|
-
SELECT TABLE_SCHEMA, TABLE_NAME
|
|
360
|
-
FROM INFORMATION_SCHEMA.TABLES
|
|
361
|
-
WHERE TABLE_TYPE = 'BASE TABLE'
|
|
362
|
-
`);
|
|
363
|
-
|
|
364
|
-
return result.recordset;
|
|
365
|
-
}
|
|
366
|
-
6) core/auth/roles.ts
|
|
367
|
-
typescript
|
|
368
|
-
export enum UserRole {
|
|
369
|
-
SYSTEM_ADMIN = 'SYSTEM_ADMIN',
|
|
370
|
-
AREA_ADMIN = 'AREA_ADMIN',
|
|
371
|
-
VIEW_ONLY = 'VIEW_ONLY',
|
|
372
|
-
}
|
|
373
|
-
7) core/permission/permission.types.ts
|
|
374
|
-
typescript
|
|
375
|
-
export enum Scope {
|
|
376
|
-
ANY = 'ANY',
|
|
377
|
-
OWN_AREA = 'OWN_AREA',
|
|
378
|
-
NONE = 'NONE',
|
|
379
|
-
}
|
|
380
|
-
8) core/permission/permission.schd-group.ts
|
|
381
|
-
typescript
|
|
382
|
-
import { UserRole } from '../auth/roles';
|
|
383
|
-
import { Scope } from './permission.types';
|
|
384
|
-
|
|
385
|
-
export type SchedulingGroupPermissions = {
|
|
386
|
-
view: Scope;
|
|
387
|
-
create: Scope;
|
|
388
|
-
edit: Scope;
|
|
389
|
-
delete: Scope;
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
export const schedulingGroupPermissions: Record<
|
|
393
|
-
UserRole,
|
|
394
|
-
SchedulingGroupPermissions
|
|
395
|
-
> = {
|
|
396
|
-
[UserRole.SYSTEM_ADMIN]: {
|
|
397
|
-
view: Scope.ANY,
|
|
398
|
-
create: Scope.ANY,
|
|
399
|
-
edit: Scope.ANY,
|
|
400
|
-
delete: Scope.ANY,
|
|
401
|
-
},
|
|
173
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
174
|
+
const DATA_DIR = path.join(path.resolve(__dirname, '..'), 'data');
|
|
402
175
|
|
|
403
|
-
|
|
404
|
-
view: Scope.OWN_AREA,
|
|
405
|
-
create: Scope.OWN_AREA,
|
|
406
|
-
edit: Scope.OWN_AREA,
|
|
407
|
-
delete: Scope.NONE,
|
|
408
|
-
},
|
|
176
|
+
export type JsonObject = Record<string, unknown>;
|
|
409
177
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
notes?: string;
|
|
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
|
+
});
|
|
434
201
|
}
|
|
435
|
-
) {
|
|
436
|
-
return apiPost(api, '/scheduling-groups', payload);
|
|
437
|
-
}
|
|
438
202
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
groupName?: string;
|
|
445
|
-
allocationsMenu?: boolean;
|
|
446
|
-
notes?: string;
|
|
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;
|
|
447
208
|
}
|
|
448
|
-
) {
|
|
449
|
-
return apiPut(api, `/scheduling-groups/${id}`, payload);
|
|
450
209
|
}
|
|
210
|
+
```
|
|
451
211
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
+
}
|
|
457
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
|
+
}
|
|
458
288
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
)
|
|
462
|
-
|
|
289
|
+
static async getFormDataByScenario(scenarioName: string) {
|
|
290
|
+
const data = await this.loadFormData();
|
|
291
|
+
return DataReader.getRecordByKey(data, 'scenario', scenarioName);
|
|
292
|
+
}
|
|
463
293
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
) {
|
|
473
|
-
|
|
474
|
-
.request()
|
|
475
|
-
.input('name', sql.VarChar, name)
|
|
476
|
-
.query(`
|
|
477
|
-
SELECT *
|
|
478
|
-
FROM scheduling_groups
|
|
479
|
-
WHERE scheduling_group_name = @name
|
|
480
|
-
`);
|
|
481
|
-
|
|
482
|
-
return result.recordset[0];
|
|
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;
|
|
483
304
|
}
|
|
484
305
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
id: number
|
|
488
|
-
) {
|
|
489
|
-
const result = await db
|
|
490
|
-
.request()
|
|
491
|
-
.input('id', sql.Int, id)
|
|
492
|
-
.query(`
|
|
493
|
-
SELECT *
|
|
494
|
-
FROM scheduling_groups
|
|
495
|
-
WHERE id = @id
|
|
496
|
-
`);
|
|
497
|
-
|
|
498
|
-
return result.recordset[0];
|
|
306
|
+
async navigateTo(url: string) {
|
|
307
|
+
await this.page.goto(url);
|
|
499
308
|
}
|
|
500
309
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
name: string
|
|
504
|
-
): Promise<boolean> {
|
|
505
|
-
const record = await this.getByName(db, name);
|
|
506
|
-
return !!record;
|
|
310
|
+
async click(selector: string) {
|
|
311
|
+
await this.page.click(selector);
|
|
507
312
|
}
|
|
508
313
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
area: string
|
|
512
|
-
) {
|
|
513
|
-
const result = await db
|
|
514
|
-
.request()
|
|
515
|
-
.input('area', sql.VarChar, area)
|
|
516
|
-
.query(`
|
|
517
|
-
SELECT *
|
|
518
|
-
FROM scheduling_groups
|
|
519
|
-
WHERE area = @area
|
|
520
|
-
`);
|
|
521
|
-
|
|
522
|
-
return result.recordset;
|
|
314
|
+
async fill(selector: string, text: string) {
|
|
315
|
+
await this.page.fill(selector, text);
|
|
523
316
|
}
|
|
524
317
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
WHERE scheduling_group_id = @id
|
|
536
|
-
ORDER BY amended_date DESC
|
|
537
|
-
`);
|
|
538
|
-
|
|
539
|
-
return result.recordset;
|
|
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();
|
|
540
328
|
}
|
|
541
329
|
}
|
|
542
|
-
|
|
543
|
-
typescript
|
|
544
|
-
import { test as bddTest } from 'playwright-bdd';
|
|
545
|
-
import { expect } from '@playwright/test';
|
|
546
|
-
import { HomePage } from '../ui/page/HomePage';
|
|
330
|
+
```
|
|
547
331
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
};
|
|
332
|
+
### 10. Page Object - Login (tests/pages/LoginPage.ts)
|
|
333
|
+
```typescript
|
|
334
|
+
import { BasePage } from './BasePage';
|
|
551
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"]';
|
|
552
340
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
await use(homePage);
|
|
557
|
-
},
|
|
558
|
-
});
|
|
341
|
+
constructor(page: import('@playwright/test').Page) {
|
|
342
|
+
super(page);
|
|
343
|
+
}
|
|
559
344
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
import { getDbPool } from '../../core/db/connection';
|
|
571
|
-
|
|
572
|
-
type TestFixtures = {
|
|
573
|
-
apiAsSystemAdmin: APIRequestContext;
|
|
574
|
-
apiAsAreaAdmin: APIRequestContext;
|
|
575
|
-
db: sql.ConnectionPool;
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
export const test = base.extend<TestFixtures>({
|
|
579
|
-
apiAsSystemAdmin: async ({}, use) => {
|
|
580
|
-
const api = await request.newContext({
|
|
581
|
-
baseURL: process.env.API_BASE_URL,
|
|
582
|
-
storageState: 'storage/system-admin.json',
|
|
583
|
-
});
|
|
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
|
+
}
|
|
584
355
|
|
|
585
|
-
|
|
586
|
-
await
|
|
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);
|
|
587
373
|
},
|
|
588
374
|
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
});
|
|
375
|
+
inventoryPage: async ({ page }, use) => {
|
|
376
|
+
const inventoryPage = new InventoryPage(page);
|
|
377
|
+
await use(inventoryPage);
|
|
378
|
+
},
|
|
594
379
|
|
|
595
|
-
|
|
596
|
-
|
|
380
|
+
formPage: async ({ page }, use) => {
|
|
381
|
+
const formPage = new FormPage(page);
|
|
382
|
+
await use(formPage);
|
|
597
383
|
},
|
|
598
384
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
await
|
|
602
|
-
await
|
|
385
|
+
authenticatedUser: async ({ page, loginPage }, use) => {
|
|
386
|
+
await loginPage.goto();
|
|
387
|
+
await loginPage.login('standard_user', 'secret_sauce');
|
|
388
|
+
await use({ page, loginPage });
|
|
603
389
|
},
|
|
604
390
|
});
|
|
605
391
|
|
|
606
392
|
export { expect };
|
|
607
|
-
|
|
608
|
-
typescript
|
|
609
|
-
import { createBdd } from 'playwright-bdd';
|
|
610
|
-
import {test} from '@fixtures/pages.fixture'
|
|
393
|
+
```
|
|
611
394
|
|
|
395
|
+
### 12. Simple Test Example
|
|
396
|
+
This template focuses on Playwright-BDD generated tests (`tests/.features-gen`).
|
|
612
397
|
|
|
613
|
-
|
|
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
|
|
614
404
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
+
```
|
|
618
410
|
|
|
619
|
-
|
|
620
|
-
When('user create new facility', async ({homePage}) => {
|
|
411
|
+
### 14. Test Data Files
|
|
621
412
|
|
|
622
|
-
|
|
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
|
+
```
|
|
623
428
|
|
|
624
|
-
|
|
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.
|
|
625
430
|
|
|
626
|
-
|
|
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
|
+
```
|
|
627
441
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
});
|
|
631
|
-
14) tests/ui/steps/scheduling-groups.steps.ts
|
|
632
|
-
typescript
|
|
442
|
+
# Run Tests
|
|
633
443
|
|
|
634
|
-
|
|
635
|
-
|
|
444
|
+
```bash
|
|
445
|
+
# Run all tests in parallel
|
|
446
|
+
npm test
|
|
636
447
|
|
|
637
|
-
|
|
448
|
+
# Run with headed browser
|
|
449
|
+
npm run test:headed
|
|
638
450
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const isBbcMenuLoaded = await homePage.isBbcMenuLoaded();
|
|
642
|
-
expect(isBbcMenuLoaded).toBe(true);
|
|
643
|
-
});
|
|
451
|
+
# Debug mode
|
|
452
|
+
npm run test:debug
|
|
644
453
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
// When('user creates a scheduling group {string} in {string} with allocations menu {string}', async ({}, arg: string, arg1: string, arg2: string) => {
|
|
650
|
-
// // Step: And user creates a scheduling group "Auto_SystemAdmin_" in "Manchester" with allocations menu "Yes"
|
|
651
|
-
// // From: tests\ui\features\scheduling-groups.feature:17:5
|
|
652
|
-
// });
|
|
653
|
-
|
|
654
|
-
// Then('user can see the new scheduling group in the list', async ({}) => {
|
|
655
|
-
// // Step: Then user can see the new scheduling group in the list
|
|
656
|
-
// // From: tests\ui\features\scheduling-groups.feature:18:5
|
|
657
|
-
// });
|
|
658
|
-
15) tests/ui/features/booking.feature
|
|
659
|
-
feature
|
|
660
|
-
@smoke
|
|
661
|
-
Feature: Booking navigation
|
|
662
|
-
|
|
663
|
-
Scenario: Navigate to Facility Catalogue
|
|
664
|
-
Given user opens the Allocate application
|
|
665
|
-
When user create new facility
|
|
666
|
-
16) tests/ui/features/scheduling-groups.feature
|
|
667
|
-
feature
|
|
668
|
-
@smoke @scheduling-groups
|
|
669
|
-
Feature: Scheduling Groups Management
|
|
670
|
-
|
|
671
|
-
@system-admin
|
|
672
|
-
Scenario: System Admin - Create a new Scheduling Group in any area
|
|
673
|
-
Given user opens the Allocate application
|
|
674
|
-
And user switches to System Admin role
|
|
675
|
-
When user navigates to Scheduling Groups page
|
|
676
|
-
And user creates a scheduling group "Auto_SystemAdmin_" in "Area"
|
|
677
|
-
Then user can see the new scheduling group in the list
|
|
678
|
-
17) tests/ui/page/HomePage.ts (already present in repo)
|
|
679
|
-
typescript
|
|
680
|
-
<content from existing file - see repo for full source>
|
|
681
|
-
18) tests/api/schd-group-create.api.spec.ts
|
|
682
|
-
typescript
|
|
683
|
-
import { test, expect } from "../../tests/fixtures/test.fixture";
|
|
684
|
-
import { createSchedulingGroup } from "../../workflows/schd-group/api/schd-group.api";
|
|
685
|
-
import { SchedulingGroupQueries } from "../../workflows/schd-group/db/schd-group.queries";
|
|
686
|
-
import { assertGroupExists } from "../../workflows/schd-group/invariants/db.invariants";
|
|
687
|
-
|
|
688
|
-
test("System Admin can create Scheduling Group in any Area", async ({apiAsSystemAdmin,db}) => {
|
|
689
|
-
const payload = {
|
|
690
|
-
area: "Manchester",
|
|
691
|
-
groupName: `Auto_${Date.now()}`,
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
//api
|
|
696
|
-
const res = await createSchedulingGroup(apiAsSystemAdmin, payload);
|
|
697
|
-
expect(res.status()).toBe(201);
|
|
698
|
-
|
|
699
|
-
const record = await SchedulingGroupQueries.getByName(db, payload.groupName);
|
|
700
|
-
|
|
701
|
-
//db
|
|
702
|
-
assertGroupExists(record);
|
|
703
|
-
});
|
|
454
|
+
# Run BDD scenarios (uses ts-node)
|
|
455
|
+
npm run test:bdd
|
|
456
|
+
```
|
|
704
457
|
|
|
705
|
-
|
|
706
|
-
const res = await apiAsSystemAdmin.post("/scheduling-groups", {
|
|
707
|
-
data: { area: "London" },
|
|
708
|
-
});
|
|
458
|
+
# BDD generation + run
|
|
709
459
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
typescript
|
|
714
|
-
import { test, expect } from '../../tests/fixtures/test.fixture';
|
|
715
|
-
import { listTables } from '../../core/db/queries';
|
|
716
|
-
|
|
717
|
-
test('Tables exist', async ({ db }) => {
|
|
718
|
-
const tables = await listTables(db);
|
|
719
|
-
expect(tables.length).toBeGreaterThan(0);
|
|
720
|
-
});
|
|
721
|
-
20) tests/utils/readJson.ts
|
|
722
|
-
typescript
|
|
723
|
-
import fs from 'fs';
|
|
724
|
-
import path from 'path';
|
|
725
|
-
import { FormField } from '../types/formField';
|
|
460
|
+
```bash
|
|
461
|
+
# Generate Playwright-BDD test files (outputs to tests/.features-gen)
|
|
462
|
+
npx bddgen
|
|
726
463
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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.
|