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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-cli-automation",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Agents CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- name: automation.md
3
- description: Describe what this custom agent does and when to use it.
4
- argument-hint: The inputs this agent expects, e.g., "a task to implement" or "a question to answer".
5
- tools:
6
- - read
7
- - create_file
8
- - apply_patch
9
- - run_in_terminal
10
- - file_search
11
- - grep_search
12
- - manage_todo_list
13
- ---
14
- This agent documents and automates reproducing the repository layout, creating a minimal, scalable Playwright-based UI test framework (targeting https://www.saucedemo.com/ by default), and providing recommended developer commands so a new contributor can recreate the workspace and run tests. When to use - When a new contributor needs to scaffold a local copy of this project structure, generate minimal UI test scaffolding (Playwright + page objects + sample tests), and install tooling to run tests and Playwright flows against Saucedemo. Inputs (argument-hint) - task (required): one of scaffold, verify, describe, create-sample-tests. - targetPath (optional): filesystem path where scaffolding should be created. Defaults to repository root. - installDeps (optional boolean): when true, agent will install Node dependencies and Playwright browsers. - force (optional boolean): when true, allow overwriting placeholder files (use with caution). - uiTarget (optional): URL to target for UI tests; defaults to https://www.saucedemo.com/. Capabilities / Behavior - Read the repo layout and create any missing directories listed in the standard layout. - Create placeholder files for missing source files (short header + file path + TODO notes). - Create Playwright scaffolding when requested, including: - a playwright.config.ts sample if missing, - a tests/ui/saucedemo.spec.ts sample test that logs in and asserts inventory load, - a ui/page page-object for the login and inventory pages, - package.json script recommendations for test, test:ui, and install:browsers. - Optionally install Node dependencies and Playwright browsers when installDeps=true. - Run verification commands (npm test, npx playwright test --list, npx playwright test) and return concise logs. Prerequisites - Node.js (v16+) and npm (or pnpm) installed. - Internet access for package installation and Playwright browser download. Representative scaffolded layout
15
- package.json (sample scripts)
16
- playwright.config.ts (generated if missing)
17
- tests/
18
- ui/
19
- saucedemo.spec.ts # sample UI test
20
- api/
21
- schd-group-create.api.spec.ts
22
- core/
23
- api/
24
- auth/
25
- db/
26
- ui/
27
- page/
28
- LoginPage.ts
29
- InventoryPage.ts
30
- features/
31
- steps/
32
- utils/
33
- readJson.ts
34
- playwright-report/
35
- screenshots/
36
- test-results/
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
- "dbtest": "npx playwright test --project=db",
55
- "uitest": "npx bddgen & npx playwright test --project=ui --headed",
43
+ "start": "node server.js",
44
+ "build": "tsc -p tsconfig.json",
56
45
  "test": "npx playwright test",
57
- "test:ui": "npx playwright test --project=ui-plain",
58
- "install:browsers": "npx playwright install"
59
- },
60
- "devDependencies": {
61
- "@playwright/test": "^1.58.2",
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
- 2) playwright.config.ts (the repo already has a BDD config; this snippet adds a plain UI project)
72
- typescript
73
- import 'dotenv/config';
74
- import { defineConfig, devices } from '@playwright/test';
75
- import { defineBddConfig } from 'playwright-bdd';
53
+ ```
76
54
 
77
- export const bddConfig = defineBddConfig({
78
- features: ['tests/ui/features/**/*.feature'],
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
- 3) core/api/apiClient.ts
304
- typescript
305
- import type { APIRequestContext, APIResponse } from "@playwright/test";
306
-
307
- export const apiGet = (
308
- request: APIRequestContext,
309
- url: string,
310
- ): Promise<APIResponse> => {
311
- return request.get(url);
312
- };
313
-
314
- export const apiPost = <T>(
315
- request: APIRequestContext,
316
- url: string,
317
- payload?: T,
318
- ): Promise<APIResponse> => {
319
- return request.post(url, { data: payload });
320
- };
321
-
322
- export const apiPut = <T>(
323
- request: APIRequestContext,
324
- url: string,
325
- payload?: T,
326
- ): Promise<APIResponse> => {
327
- return request.put(url, { data: payload });
328
- };
329
-
330
- export const apiDelete = (
331
- request: APIRequestContext,
332
- url: string,
333
- ): Promise<APIResponse> => {
334
- return request.delete(url);
335
- };
336
- 4) core/db/connection.ts
337
- typescript
338
- import sql from 'mssql';
339
-
340
- const config: sql.config = {
341
- server: process.env.DB_HOST!,
342
- database: process.env.DB_NAME!,
343
- user: process.env.DB_USER,
344
- password: process.env.DB_PASSWORD,
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
- export async function getDbPool(): Promise<sql.ConnectionPool> {
351
- return sql.connect(config);
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
- [UserRole.AREA_ADMIN]: {
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
- [UserRole.VIEW_ONLY]: {
411
- view: Scope.OWN_AREA,
412
- create: Scope.NONE,
413
- edit: Scope.NONE,
414
- delete: Scope.NONE,
415
- },
416
- };
417
- 9) workflows/schd-group/api/schd-group.api.ts
418
- typescript
419
- import { APIRequestContext } from '@playwright/test';
420
- import {
421
- apiGet,
422
- apiPost,
423
- apiPut,
424
- apiDelete,
425
- } from '../../../core/api/apiClient';
426
-
427
- export function createSchedulingGroup(
428
- api: APIRequestContext,
429
- payload: {
430
- area: string;
431
- groupName: string;
432
- allocationsMenu?: boolean;
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
- export function editSchedulingGroup(
440
- api: APIRequestContext,
441
- id: number,
442
- payload: {
443
- area?: string;
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
- export function deleteSchedulingGroup(
453
- api: APIRequestContext,
454
- id: number
455
- ) {
456
- return apiDelete(api, `/scheduling-groups/${id}`);
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
- export function listSchedulingGroups(
460
- api: APIRequestContext
461
- ) {
462
- return apiGet(api, '/scheduling-groups');
289
+ static async getFormDataByScenario(scenarioName: string) {
290
+ const data = await this.loadFormData();
291
+ return DataReader.getRecordByKey(data, 'scenario', scenarioName);
292
+ }
463
293
  }
464
- 10) workflows/schd-group/db/schd-group.queries.ts
465
- typescript
466
- import sql from 'mssql';
467
-
468
- export class SchedulingGroupQueries {
469
- static async getByName(
470
- db: sql.ConnectionPool,
471
- name: string
472
- ) {
473
- const result = await db
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
- static async getById(
486
- db: sql.ConnectionPool,
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
- static async exists(
502
- db: sql.ConnectionPool,
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
- static async listByArea(
510
- db: sql.ConnectionPool,
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
- static async getHistory(
526
- db: sql.ConnectionPool,
527
- groupId: number
528
- ) {
529
- const result = await db
530
- .request()
531
- .input('id', sql.Int, groupId)
532
- .query(`
533
- SELECT *
534
- FROM scheduling_group_history
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
- 11) tests/fixtures/pages.fixture.ts
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
- export type PageFixtures = {
549
- homePage: HomePage;
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
- export const test = bddTest.extend<PageFixtures>({
554
- homePage: async ({ page }, use) => {
555
- const homePage = new HomePage(page);
556
- await use(homePage);
557
- },
558
- });
341
+ constructor(page: import('@playwright/test').Page) {
342
+ super(page);
343
+ }
559
344
 
560
- export { expect };
561
- 12) tests/fixtures/test.fixture.ts
562
- typescript
563
- import {
564
- test as base,
565
- expect,
566
- request,
567
- APIRequestContext,
568
- } from '@playwright/test';
569
- import sql from 'mssql';
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
- await use(api);
586
- await api.dispose();
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
- apiAsAreaAdmin: async ({}, use) => {
590
- const api = await request.newContext({
591
- baseURL: process.env.API_BASE_URL,
592
- storageState: 'storage/area-admin.json',
593
- });
375
+ inventoryPage: async ({ page }, use) => {
376
+ const inventoryPage = new InventoryPage(page);
377
+ await use(inventoryPage);
378
+ },
594
379
 
595
- await use(api);
596
- await api.dispose();
380
+ formPage: async ({ page }, use) => {
381
+ const formPage = new FormPage(page);
382
+ await use(formPage);
597
383
  },
598
384
 
599
- db: async ({}, use) => {
600
- const pool = await getDbPool();
601
- await use(pool);
602
- await pool.close();
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
- 13) tests/ui/steps/booking.steps.ts
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
- const { Given, When, Then } = createBdd(test);
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
- Given('user opens the Allocate application', async ({ homePage }) => {
616
- await homePage.open();
617
- await homePage.isBbcMenuLoaded();
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
- await homePage.createFacility()
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
- await homePage.verifyFacilityAdded()
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
- await homePage.assertViewFacility()
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
- await homePage.deleteFacility()
629
-
630
- });
631
- 14) tests/ui/steps/scheduling-groups.steps.ts
632
- typescript
442
+ # Run Tests
633
443
 
634
- import { createBdd } from 'playwright-bdd';
635
- import { test,expect } from '@fixtures/pages.fixture';
444
+ ```bash
445
+ # Run all tests in parallel
446
+ npm test
636
447
 
637
- const { Given, When, Then } = createBdd(test);
448
+ # Run with headed browser
449
+ npm run test:headed
638
450
 
639
- Given('user switches to System Admin role', async ({homePage}) => {
640
- await homePage.open();
641
- const isBbcMenuLoaded = await homePage.isBbcMenuLoaded();
642
- expect(isBbcMenuLoaded).toBe(true);
643
- });
451
+ # Debug mode
452
+ npm run test:debug
644
453
 
645
- // When('user navigates to Scheduling Groups page', async ({homePage}) => {
646
- // // From: tests\ui\features\scheduling-groups.feature:16:5
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
- test("Scheduling Group Name is mandatory", async ({ apiAsSystemAdmin }) => {
706
- const res = await apiAsSystemAdmin.post("/scheduling-groups", {
707
- data: { area: "London" },
708
- });
458
+ # BDD generation + run
709
459
 
710
- expect(res.status()).toBe(400);
711
- });
712
- 19) tests/db/01-connection.db.spec.ts
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
- export async function readJSON(fileName: string): Promise<Record<string, FormField>> {
728
- const filePath = path.resolve(__dirname, `../data/${fileName}`);
729
- const rawData = fs.readFileSync(filePath, 'utf-8');
730
- const data: Record<string, FormField> = JSON.parse(rawData);
731
- return data;
732
- }
733
- 21) tests/utils/formFiller.ts (full implementation included in repo)
734
- typescript
735
- <content from existing file - see repo for full source>
736
- 22) tests/types/formField.ts (included above) 23) tests/data/facilityFormData.json (included above) --- Notes - I embedded the full contents of many small files above; for very large source files (like formFiller.ts and HomePage.ts) I included a placeholder pointer to the existing file since the full content is already present in the repository and shown earlier in this document. If you'd like, I can paste their complete contents here as well. - Next I can: (choose) - Auto-generate the Playwright UI scaffold files from the templates (create the files in the repo). - Leave this agent file as the single source of templates and not modify the filesystem.
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.