agents-cli-automation 1.0.7 → 1.0.9

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.
@@ -1,1419 +1,736 @@
1
- name: Create Playwright Framework - UI Testing
2
- description: Creates a production-ready Playwright UI automation framework with all dependencies, fixtures, and test configurations properly set up.
3
- argument-hint: "framework requirements, e.g., 'JavaScript with UI tests'"
4
-
5
1
  ---
6
-
7
- # Playwright UI Testing Framework Setup
8
-
9
- This agent creates a production-ready Playwright framework optimized for UI automation.
10
-
11
- ## Capabilities
12
- - Playwright setup (JS / TS) - Chromium only with latest module syntax
13
- - TypeScript support with strictest config
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
- - Sample tests (headless & headed modes)
20
- - Scalable folder structure with best practices
21
- - WebServer configuration for running tests against local servers
22
- - Complete package.json with all required scripts
23
- - Modern ES modules with latest imports
24
- - Full TypeScript integration with types
25
- - Minimal setup - Developers only write test steps & scenarios
26
-
27
- ## Prerequisites
28
- - Node.js 18+ installed
29
- - npm or yarn
30
-
31
- ## Setup Instructions
32
-
33
- ### 1. Install Dependencies (Chromium Only + TypeScript)
34
- ```bash
35
- npm install
36
- npm install --save-dev typescript @types/node @playwright/test
37
- npx playwright install chromium
38
- ```
39
-
40
- ### 2. Install BDD & Data Handling Dependencies
41
- For Playwright BDD support and test data files:
42
- ```bash
43
- npm install --save-dev @cucumber/cucumber csv-parser js-yaml @types/js-yaml
44
- ```
45
-
46
- ### 3. Required package.json Scripts
47
- Ensure your `package.json` includes these scripts:
48
- ```json
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
49
49
  {
50
+ "name": "poc_bbc",
51
+ "version": "1.0.0",
52
+ "type": "commonjs",
50
53
  "scripts": {
51
- "start": "node server.js",
52
- "test": "playwright test",
53
- "test:headed": "playwright test --headed",
54
- "test:debug": "playwright test --debug",
55
- "test:ui": "playwright test --ui",
56
- "test:bdd": "npx @cucumber/cucumber-js",
57
- "test:bdd:report": "npx @cucumber/cucumber-js --format json:cucumber-report.json"
58
- }
59
- }
60
- ```
61
-
62
- ### 4. TypeScript Configuration (`tsconfig.json`)
63
- ```json
64
- {
65
- "compilerOptions": {
66
- "target": "ES2020",
67
- "lib": ["ES2020"],
68
- "module": "ESNext",
69
- "moduleResolution": "node",
70
- "esModuleInterop": true,
71
- "allowSyntheticDefaultImports": true,
72
- "strict": true,
73
- "skipLibCheck": true,
74
- "forceConsistentCasingInFileNames": true,
75
- "resolveJsonModule": true,
76
- "noEmit": true,
77
- "declaration": true,
78
- "declarationMap": true,
79
- "sourceMap": true,
80
- "baseUrl": ".",
81
- "paths": {
82
- "@pages/*": ["tests/pages/*"],
83
- "@utils/*": ["tests/utils/*"],
84
- "@fixtures/*": ["tests/fixtures/*"],
85
- "@data/*": ["tests/data/*"]
86
- }
87
- },
88
- "include": ["tests/**/*"],
89
- "exclude": ["node_modules"]
90
- }
91
- ```
92
-
93
- ### 5. Project Structure (Scalable with BDD & TypeScript)
94
- ```
95
- project/
96
- ├── tests/
97
- │ ├── fixtures/
98
- │ │ └── base.fixture.ts # Shared fixtures (TypeScript)
99
- │ ├── pages/
100
- │ │ ├── BasePage.ts # Base page object (TypeScript)
101
- │ │ ├── LoginPage.ts # Login page object (TypeScript)
102
- │ │ ├── InventoryPage.ts # Inventory page object (TypeScript)
103
- │ │ └── FormPage.ts # Form page object (TypeScript)
104
- │ ├── utils/ # Utility functions (TypeScript)
105
- │ │ ├── dataReader.ts
106
- │ │ ├── formHelper.ts
107
- │ │ └── testData.ts
108
- │ ├── data/ # Test data files
109
- │ │ ├── users.json
110
- │ │ ├── formData.csv
111
- │ │ └── config.yaml
112
- │ ├── features/ # BDD Gherkin scenarios
113
- │ │ ├── login.feature
114
- │ │ └── shopping.feature
115
- │ ├── steps/ # BDD Step definitions (TypeScript)
116
- │ │ ├── loginSteps.ts
117
- │ │ └── shoppingSteps.ts
118
- │ └── specs/
119
- │ ├── saucedemo.spec.ts # Traditional tests (TypeScript)
120
- │ └── formTest.spec.ts
121
- ├── cucumber.js # Cucumber config
122
- ├── playwright.config.ts # Playwright config (TypeScript)
123
- ├── tsconfig.json # TypeScript config
124
- └── package.json
125
- ```
126
-
127
- ### 6. Cucumber Configuration (`cucumber.js` - for BDD)
128
- ```javascript
129
- module.exports = {
130
- default: {
131
- paths: ['tests/features/**/*.feature'],
132
- require: ['tests/steps/**/*.ts'],
133
- requireModule: ['ts-node/register'],
134
- format: ['progress', 'html:cucumber-report.html'],
135
- parallel: 4,
136
- worldParameters: {
137
- appUrl: 'https://www.saucedemo.com/',
138
- }
139
- }
140
- };
141
- ```
142
-
143
- ### 7. Playwright Configuration (`playwright.config.ts`)
144
- ```typescript
145
- import { defineConfig, devices, type PlaywrightTestOptions } from '@playwright/test';
146
-
147
- export default defineConfig<PlaywrightTestOptions>({
148
- testDir: './tests/specs',
149
- fullyParallel: true,
150
- workers: process.env.CI ? 1 : 4,
151
- retries: 1,
152
- timeout: 30000,
153
- expect: {
154
- timeout: 5000,
54
+ "dbtest": "npx playwright test --project=db",
55
+ "uitest": "npx bddgen & npx playwright test --project=ui --headed",
56
+ "test": "npx playwright test",
57
+ "test:ui": "npx playwright test --project=ui-plain",
58
+ "install:browsers": "npx playwright install"
155
59
  },
156
- use: {
157
- browserName: 'chromium',
158
- headless: true,
159
- screenshot: 'only-on-failure',
160
- video: 'retain-on-failure',
161
- trace: 'on-first-retry',
162
- },
163
- webServer: {
164
- command: 'npm start',
165
- url: 'http://localhost:3000',
166
- reuseExistingServer: !process.env.CI,
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"
167
65
  },
168
- reporter: [
169
- ['html'],
170
- ['json', { outputFile: 'test-results/results.json' }],
66
+ "dependencies": {
67
+ "dotenv": "^17.2.3",
68
+ "mssql": "^12.2.0"
69
+ }
70
+ }
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';
76
+
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
+ },
171
112
  ],
172
113
  });
173
- ```
174
-
175
- ### 8. Data Reader Utility (`tests/utils/dataReader.ts`) - TypeScript
176
- ```typescript
177
- import fs from 'fs';
178
- import path from 'path';
179
- import csv from 'csv-parser';
180
- import yaml from 'js-yaml';
181
-
182
- const DATA_DIR = path.join(process.cwd(), 'tests', 'data');
183
-
184
- interface DataRecord {
185
- [key: string]: string | number | boolean;
186
- }
187
-
188
- export class DataReader {
189
- // Read JSON file
190
- static readJSON<T = DataRecord>(filename: string): T {
191
- const filePath = path.join(DATA_DIR, filename);
192
- const data = fs.readFileSync(filePath, 'utf-8');
193
- return JSON.parse(data) as T;
194
- }
195
-
196
- // Read YAML file
197
- static readYAML<T = DataRecord>(filename: string): T {
198
- const filePath = path.join(DATA_DIR, filename);
199
- const data = fs.readFileSync(filePath, 'utf-8');
200
- return yaml.load(data) as T;
201
- }
202
-
203
- // Read CSV file (returns array of objects)
204
- static async readCSV(filename: string): Promise<DataRecord[]> {
205
- const filePath = path.join(DATA_DIR, filename);
206
- return new Promise<DataRecord[]>((resolve, reject) => {
207
- const records: DataRecord[] = [];
208
- fs.createReadStream(filePath)
209
- .pipe(csv())
210
- .on('data', (data: DataRecord) => records.push(data))
211
- .on('end', () => resolve(records))
212
- .on('error', reject);
213
- });
214
- }
114
+ 3) ui/page/LoginPage.ts (page object)
115
+ typescript
116
+ import { Page } from '@playwright/test';
215
117
 
216
- // Get single record by ID or key
217
- static getRecordByKey<T extends DataRecord>(
218
- data: T[],
219
- key: keyof T,
220
- value: string | number
221
- ): T | undefined {
222
- if (Array.isArray(data)) {
223
- return data.find((record) => record[key] === value);
224
- }
225
- return undefined;
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');
226
126
  }
227
127
  }
228
- ```
229
-
230
- ### 9. Form Helper Utility (`tests/utils/formHelper.ts`) - TypeScript
231
- ```typescript
128
+ 4) ui/page/InventoryPage.ts (page object)
129
+ typescript
232
130
  import { Page, Locator } from '@playwright/test';
233
131
 
234
- interface FormFieldConfig {
235
- type: 'text' | 'textarea' | 'dropdown' | 'radio' | 'checkbox';
236
- selector?: string;
237
- value: string | string[] | boolean;
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; }
238
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"
239
208
 
240
- export class FormHelper {
241
- constructor(private page: Page) {}
242
-
243
- // Fill text input
244
- async fillInput(selector: string, value: string): Promise<void> {
245
- await this.page.fill(selector, value);
246
- }
247
-
248
- // Fill textarea
249
- async fillTextArea(selector: string, value: string): Promise<void> {
250
- await this.page.locator(selector).clear();
251
- await this.page.locator(selector).type(value, { delay: 50 });
252
- }
253
-
254
- // Select option from dropdown (not select element)
255
- async selectDropdown(dropdownSelector: string, optionText: string): Promise<void> {
256
- await this.page.click(dropdownSelector);
257
- await this.page.click(`text="${optionText}"`);
258
- }
259
-
260
- // Select radio button by label text
261
- async selectRadioButton(labelText: string): Promise<void> {
262
- const label = this.page.locator(`label:has-text("${labelText}")`);
263
- const radioId = await label.getAttribute('for');
264
- if (radioId) {
265
- await this.page.locator(`#${radioId}`).click();
266
- }
267
- }
268
-
269
- // Check checkbox by label text
270
- async checkCheckbox(labelText: string): Promise<void> {
271
- const label = this.page.locator(`label:has-text("${labelText}")`);
272
- const checkboxId = await label.getAttribute('for');
273
- if (checkboxId) {
274
- const checkbox = this.page.locator(`#${checkboxId}`);
275
- const isChecked = await checkbox.isChecked();
276
- if (!isChecked) {
277
- await checkbox.click();
278
- }
279
- }
280
- }
281
-
282
- // Uncheck checkbox by label text
283
- async uncheckCheckbox(labelText: string): Promise<void> {
284
- const label = this.page.locator(`label:has-text("${labelText}")`);
285
- const checkboxId = await label.getAttribute('for');
286
- if (checkboxId) {
287
- const checkbox = this.page.locator(`#${checkboxId}`);
288
- const isChecked = await checkbox.isChecked();
289
- if (isChecked) {
290
- await checkbox.click();
291
- }
292
- }
293
- }
294
-
295
- // Select multiple checkboxes
296
- async checkMultiple(labels: string[]): Promise<void> {
297
- for (const label of labels) {
298
- await this.checkCheckbox(label);
299
- }
300
- }
301
-
302
- // Fill form with object data
303
- async fillForm(formData: Record<string, FormFieldConfig>): Promise<void> {
304
- for (const field of Object.values(formData)) {
305
- if (field.type === 'text' && field.selector && typeof field.value === 'string') {
306
- await this.fillInput(field.selector, field.value);
307
- } else if (field.type === 'textarea' && field.selector && typeof field.value === 'string') {
308
- await this.fillTextArea(field.selector, field.value);
309
- } else if (field.type === 'dropdown' && field.selector && typeof field.value === 'string') {
310
- await this.selectDropdown(field.selector, field.value);
311
- } else if (field.type === 'radio' && typeof field.value === 'string') {
312
- await this.selectRadioButton(field.value);
313
- } else if (field.type === 'checkbox' && field.value) {
314
- if (Array.isArray(field.value)) {
315
- await this.checkMultiple(field.value);
316
- } else if (typeof field.value === 'string') {
317
- await this.checkCheckbox(field.value);
318
- }
319
- }
320
- }
321
- }
322
-
323
- // Verify form value
324
- async getInputValue(selector: string): Promise<string | null> {
325
- return await this.page.inputValue(selector);
326
- }
327
-
328
- // Verify radio button is selected
329
- async isRadioSelected(labelText: string): Promise<boolean> {
330
- const label = this.page.locator(`label:has-text("${labelText}")`);
331
- const radioId = await label.getAttribute('for');
332
- return radioId ? await this.page.locator(`#${radioId}`).isChecked() : false;
333
- }
334
-
335
- // Verify checkbox is checked
336
- async isCheckboxChecked(labelText: string): Promise<boolean> {
337
- const label = this.page.locator(`label:has-text("${labelText}")`);
338
- const checkboxId = await label.getAttribute('for');
339
- return checkboxId ? await this.page.locator(`#${checkboxId}`).isChecked() : false;
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"
340
226
  }
341
227
  }
342
- ```
343
-
344
- ### 10. Test Data Manager (`tests/utils/testData.ts`) - TypeScript
345
- ```typescript
346
- import { DataReader } from './dataReader';
347
-
348
- interface User {
349
- id: string;
350
- username: string;
351
- password: string;
352
- email: string;
353
- firstName: string;
354
- lastName: string;
355
- }
356
-
357
- interface FormData {
358
- scenario: string;
359
- firstName: string;
360
- lastName: string;
361
- email: string;
362
- country: string;
363
- acceptTerms: string;
364
- newsletter: string;
365
- }
366
-
367
- interface Config {
368
- app: {
369
- url: string;
370
- timeout: number;
371
- };
372
- users: {
373
- [key: string]: {
374
- username: string;
375
- password: string;
376
- };
377
- };
378
- forms: {
379
- [key: string]: {
380
- [field: string]: string;
381
- };
382
- };
383
- }
384
-
385
- export class TestDataManager {
386
- // Load user data from JSON
387
- static loadUsers(): { users: User[] } {
388
- return DataReader.readJSON<{ users: User[] }>('users.json');
389
- }
390
-
391
- // Load form data from CSV
392
- static async loadFormData(): Promise<FormData[]> {
393
- return await DataReader.readCSV('formData.csv') as unknown as FormData[];
394
- }
395
-
396
- // Load config from YAML
397
- static loadConfig(): Config {
398
- return DataReader.readYAML<Config>('config.yaml');
399
- }
228
+ 2) playwright.config.ts
229
+ typescript
230
+ import 'dotenv/config';
231
+ import { defineConfig, devices } from '@playwright/test';
232
+ import { defineBddConfig } from 'playwright-bdd';
233
+
234
+ // BDD config for UI tests
235
+ export const bddConfig = defineBddConfig({
236
+ features: ['tests/ui/features/**/*.feature'],
237
+ steps: [
238
+ 'tests/ui/steps/**/*.steps.ts',
239
+ 'tests/fixtures/pages.fixture.ts',
240
+ ],
241
+ });
400
242
 
401
- // Get specific user by username
402
- static getUserByUsername(username: string): User | undefined {
403
- const data = this.loadUsers();
404
- return DataReader.getRecordByKey(data.users, 'username' as keyof User, username);
405
- }
243
+ export default defineConfig({
244
+ testDir: './tests',
245
+ timeout: 20 * 1000,
246
+ expect: {
247
+ timeout: 10 * 1000,
248
+ },
249
+ retries: process.env.CI ? 1 : 0,
250
+ reporter: [
251
+ ['html', { open: 'never' }],
252
+ ['list'],
253
+ ],
254
+ use: {
255
+ trace: 'retain-on-failure',
256
+ screenshot: 'only-on-failure',
257
+ video: 'retain-on-failure'
258
+ },
406
259
 
407
- // Get form data by scenario name
408
- static async getFormDataByScenario(scenarioName: string): Promise<FormData | undefined> {
409
- const data = await this.loadFormData();
410
- return DataReader.getRecordByKey(data, 'scenario' as keyof FormData, scenarioName);
411
- }
412
- }
413
- ```
260
+ projects: [
261
+ // =======================
262
+ // UI BDD TESTS
263
+ // =======================
264
+ {
265
+ name: 'ui',
266
+ testDir: './.features-gen', // folder where Playwright-BDD generates .feature.spec.ts
267
+ testMatch: '**/*.feature.spec.*', // match .ts or .js files
268
+ use: {
269
+ ...devices['Desktop Chrome'],
270
+ headless: true,
271
+ baseURL: process.env.UI_BASE_URL,
272
+ },
414
273
 
415
- ### 9. Test Data Files
274
+ },
416
275
 
417
- **`tests/data/users.json`**
418
- ```json
419
- {
420
- "users": [
276
+ // =======================
277
+ // API TESTS
278
+ // =======================
421
279
  {
422
- "id": "1",
423
- "username": "standard_user",
424
- "password": "secret_sauce",
425
- "email": "user@example.com",
426
- "firstName": "John",
427
- "lastName": "Doe"
280
+ name: 'api',
281
+ testMatch: /.*\.api\.spec\.ts/,
282
+ use: {
283
+ browserName: undefined,
284
+ baseURL: process.env.API_BASE_URL,
285
+ extraHTTPHeaders: {
286
+ 'Content-Type': 'application/json',
287
+ },
288
+ },
428
289
  },
429
- {
430
- "id": "2",
431
- "username": "problem_user",
432
- "password": "secret_sauce",
433
- "email": "problem@example.com",
434
- "firstName": "Jane",
435
- "lastName": "Smith"
436
- }
437
- ]
438
- }
439
- ```
440
-
441
- **`tests/data/formData.csv`**
442
- ```csv
443
- scenario,firstName,lastName,email,country,acceptTerms,newsletter
444
- registration_valid,John,Doe,john@example.com,USA,true,true
445
- registration_minimal,Jane,Smith,jane@example.com,Canada,true,false
446
- registration_eu,Pierre,Martin,pierre@example.com,France,true,true
447
- ```
448
-
449
- **`tests/data/config.yaml`**
450
- ```yaml
451
- app:
452
- url: https://www.saucedemo.com/
453
- timeout: 30000
454
-
455
- users:
456
- standard:
457
- username: standard_user
458
- password: secret_sauce
459
- admin:
460
- username: admin
461
- password: admin123
462
-
463
- forms:
464
- registration:
465
- firstName: '#firstName'
466
- lastName: '#lastName'
467
- email: '#email'
468
- country: '.country-dropdown'
469
- acceptTerms: '#terms'
470
- newsletter: '#newsletter'
471
-
472
- ```
473
-
474
- ### 10. Enhanced Page Object with Form Helper (`tests/pages/FormPage.js`)
475
- ```javascript
476
- import { BasePage } from './BasePage';
477
- import { FormHelper } from '../utils/formHelper.js';
478
- import { TestDataManager } from '../utils/testData.js';
479
-
480
- export class FormPage extends BasePage {
481
- constructor(page) {
482
- super(page);
483
- this.formHelper = new FormHelper(page);
484
- this.config = TestDataManager.loadConfig();
485
- }
486
290
 
487
- async fillRegistrationForm(testDataKey) {
488
- const formData = await TestDataManager.getFormDataByScenario(testDataKey);
489
-
490
- const formConfig = {
491
- firstName: {
492
- type: 'text',
493
- selector: this.config.forms.registration.firstName,
494
- value: formData.firstName
495
- },
496
- lastName: {
497
- type: 'text',
498
- selector: this.config.forms.registration.lastName,
499
- value: formData.lastName
500
- },
501
- email: {
502
- type: 'text',
503
- selector: this.config.forms.registration.email,
504
- value: formData.email
505
- },
506
- country: {
507
- type: 'dropdown',
508
- selector: this.config.forms.registration.country,
509
- value: formData.country
510
- },
511
- acceptTerms: {
512
- type: 'checkbox',
513
- value: 'I accept the terms'
291
+ // =======================
292
+ // DB TESTS (NO BROWSER)
293
+ // =======================
294
+ {
295
+ name: 'db',
296
+ testMatch: /.*\.db\.spec\.ts/,
297
+ use: {
298
+ browserName: undefined,
514
299
  },
515
- newsletter: {
516
- type: 'checkbox',
517
- value: formData.newsletter === 'true' ? 'Subscribe to newsletter' : null
518
- }
519
- };
520
-
521
- for (const field of Object.values(formConfig)) {
522
- if (field.value === null) continue;
523
-
524
- if (field.type === 'text') {
525
- await this.formHelper.fillInput(field.selector, field.value);
526
- } else if (field.type === 'dropdown') {
527
- await this.formHelper.selectDropdown(field.selector, field.value);
528
- } else if (field.type === 'checkbox') {
529
- await this.formHelper.checkCheckbox(field.value);
530
- }
531
- }
532
- }
533
- }
534
- ```
300
+ },
301
+ ],
302
+ });
303
+ 3) core/api/apiClient.ts
304
+ typescript
305
+ import type { APIRequestContext, APIResponse } from "@playwright/test";
535
306
 
536
- ### 11. Enhanced Page Object with Form Helper (`tests/pages/FormPage.ts`) - TypeScript
537
- ```typescript
538
- import { Page } from '@playwright/test';
539
- import { BasePage } from './BasePage';
540
- import { FormHelper } from '@utils/formHelper';
541
- import { TestDataManager } from '@utils/testData';
542
-
543
- export class FormPage extends BasePage {
544
- readonly formHelper: FormHelper;
545
- private config: any;
546
-
547
- constructor(page: Page) {
548
- super(page);
549
- this.formHelper = new FormHelper(page);
550
- this.config = TestDataManager.loadConfig();
551
- }
307
+ export const apiGet = (
308
+ request: APIRequestContext,
309
+ url: string,
310
+ ): Promise<APIResponse> => {
311
+ return request.get(url);
312
+ };
552
313
 
553
- async fillRegistrationForm(testDataKey: string): Promise<void> {
554
- const formData = await TestDataManager.getFormDataByScenario(testDataKey);
555
- if (!formData) throw new Error(`Form data not found for: ${testDataKey}`);
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
+ };
556
321
 
557
- const formConfig = {
558
- firstName: {
559
- type: 'text' as const,
560
- selector: this.config.forms.registration.firstName,
561
- value: formData.firstName
562
- },
563
- lastName: {
564
- type: 'text' as const,
565
- selector: this.config.forms.registration.lastName,
566
- value: formData.lastName
567
- },
568
- email: {
569
- type: 'text' as const,
570
- selector: this.config.forms.registration.email,
571
- value: formData.email
572
- },
573
- country: {
574
- type: 'dropdown' as const,
575
- selector: this.config.forms.registration.country,
576
- value: formData.country
577
- },
578
- acceptTerms: {
579
- type: 'checkbox' as const,
580
- value: 'I accept the terms'
581
- },
582
- newsletter: {
583
- type: 'checkbox' as const,
584
- value: formData.newsletter === 'true' ? 'Subscribe to newsletter' : null
585
- }
586
- };
587
-
588
- for (const field of Object.values(formConfig)) {
589
- if (field.value === null) continue;
590
-
591
- if (field.type === 'text') {
592
- await this.formHelper.fillInput(field.selector!, field.value as string);
593
- } else if (field.type === 'dropdown') {
594
- await this.formHelper.selectDropdown(field.selector!, field.value as string);
595
- } else if (field.type === 'checkbox') {
596
- await this.formHelper.checkCheckbox(field.value as string);
597
- }
598
- }
599
- }
600
- }
601
- ```
602
-
603
- ### 12. Fixtures Setup (`tests/fixtures/base.fixture.ts`) - TypeScript
604
- ```typescript
605
- import { test as base, expect } from '@playwright/test';
606
- import { LoginPage } from '@pages/LoginPage';
607
- import { InventoryPage } from '@pages/InventoryPage';
608
- import { FormPage } from '@pages/FormPage';
609
-
610
- type AuthFixtures = {
611
- loginPage: LoginPage;
612
- inventoryPage: InventoryPage;
613
- formPage: FormPage;
614
- authenticatedUser: { page: any; loginPage: LoginPage };
322
+ export const apiPut = <T>(
323
+ request: APIRequestContext,
324
+ url: string,
325
+ payload?: T,
326
+ ): Promise<APIResponse> => {
327
+ return request.put(url, { data: payload });
615
328
  };
616
329
 
617
- export const test = base.extend<AuthFixtures>({
618
- loginPage: async ({ page }, use) => {
619
- const loginPage = new LoginPage(page);
620
- await use(loginPage);
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,
621
347
  },
348
+ };
622
349
 
623
- inventoryPage: async ({ page }, use) => {
624
- const inventoryPage = new InventoryPage(page);
625
- await use(inventoryPage);
626
- },
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
+ };
627
391
 
628
- formPage: async ({ page }, use) => {
629
- const formPage = new FormPage(page);
630
- await use(formPage);
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,
631
401
  },
632
402
 
633
- authenticatedUser: async ({ page, loginPage }, use) => {
634
- await loginPage.goto();
635
- await loginPage.login('standard_user', 'secret_sauce');
636
- await use({ page, loginPage });
403
+ [UserRole.AREA_ADMIN]: {
404
+ view: Scope.OWN_AREA,
405
+ create: Scope.OWN_AREA,
406
+ edit: Scope.OWN_AREA,
407
+ delete: Scope.NONE,
637
408
  },
638
- });
639
-
640
- export { expect };
641
- ```
642
409
 
643
- ### 13. Base Page (`tests/pages/BasePage.ts`) - TypeScript
644
- ```typescript
645
- import { Page, Locator } from '@playwright/test';
646
-
647
- export class BasePage {
648
- protected readonly page: Page;
649
-
650
- constructor(page: Page) {
651
- this.page = page;
652
- }
653
-
654
- async navigateTo(url: string): Promise<void> {
655
- await this.page.goto(url);
656
- }
657
-
658
- async click(selector: string): Promise<void> {
659
- await this.page.click(selector);
660
- }
661
-
662
- async fill(selector: string, text: string): Promise<void> {
663
- await this.page.fill(selector, text);
664
- }
665
-
666
- async getText(selector: string): Promise<string | null> {
667
- return await this.page.textContent(selector);
668
- }
669
-
670
- async getLocator(selector: string): Locator {
671
- return this.page.locator(selector);
672
- }
673
-
674
- async waitForElement(selector: string, timeout: number = 5000): Promise<void> {
675
- await this.page.waitForSelector(selector, { timeout });
676
- }
677
-
678
- async isVisible(selector: string): Promise<boolean> {
679
- return await this.page.locator(selector).isVisible();
680
- }
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;
434
+ }
435
+ ) {
436
+ return apiPost(api, '/scheduling-groups', payload);
681
437
  }
682
- ```
683
-
684
- ### 14. Page Object Model (`tests/pages/LoginPage.ts`) - TypeScript
685
- ```typescript
686
- import { BasePage } from './BasePage';
687
-
688
- export class LoginPage extends BasePage {
689
- private readonly usernameField = '[data-test="username"]';
690
- private readonly passwordField = '[data-test="password"]';
691
- private readonly loginButton = '[data-test="login-button"]';
692
- private readonly appUrl = 'https://www.saucedemo.com/';
693
438
 
694
- async goto(): Promise<void> {
695
- await this.page.goto(this.appUrl);
696
- }
697
-
698
- async login(username: string, password: string): Promise<void> {
699
- await this.page.fill(this.usernameField, username);
700
- await this.page.fill(this.passwordField, password);
701
- await this.page.click(this.loginButton);
702
- await this.page.waitForURL('**/inventory.html');
703
- }
704
-
705
- async isLoaded(): Promise<boolean> {
706
- return await this.page.locator(this.usernameField).isVisible();
707
- }
439
+ export function editSchedulingGroup(
440
+ api: APIRequestContext,
441
+ id: number,
442
+ payload: {
443
+ area?: string;
444
+ groupName?: string;
445
+ allocationsMenu?: boolean;
446
+ notes?: string;
447
+ }
448
+ ) {
449
+ return apiPut(api, `/scheduling-groups/${id}`, payload);
708
450
  }
709
- ```
710
-
711
- ### 15. Inventory Page (`tests/pages/InventoryPage.ts`) - TypeScript
712
- ```typescript
713
- import { BasePage } from './BasePage';
714
-
715
- export class InventoryPage extends BasePage {
716
- private readonly cartBadge = '[data-test="shopping-cart-badge"]';
717
- private readonly addToCartBtn = '[data-test="add-to-cart-sauce-labs-backpack"]';
718
- private readonly menuButton = '[data-test="bm-menu-button"]';
719
- private readonly logoutLink = '[data-test="logout-sidebar-link"]';
720
-
721
- async addProductToCart(): Promise<void> {
722
- await this.click(this.addToCartBtn);
723
- }
724
451
 
725
- async getCartCount(): Promise<string | null> {
726
- return await this.getText(this.cartBadge);
727
- }
728
-
729
- async isProductAdded(): Promise<string | null> {
730
- await this.waitForElement(this.cartBadge);
731
- return await this.getText(this.cartBadge);
732
- }
452
+ export function deleteSchedulingGroup(
453
+ api: APIRequestContext,
454
+ id: number
455
+ ) {
456
+ return apiDelete(api, `/scheduling-groups/${id}`);
457
+ }
733
458
 
734
- async logout(): Promise<void> {
735
- await this.click(this.menuButton);
736
- await this.click(this.logoutLink);
737
- await this.page.waitForURL('https://www.saucedemo.com/');
459
+ export function listSchedulingGroups(
460
+ api: APIRequestContext
461
+ ) {
462
+ return apiGet(api, '/scheduling-groups');
463
+ }
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];
483
+ }
484
+
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];
499
+ }
500
+
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;
507
+ }
508
+
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;
523
+ }
524
+
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;
738
540
  }
739
541
  }
740
- ```
741
-
742
- ### 16. Clean Test File with TypeScript (`tests/specs/saucedemo.spec.ts`)
743
- ```typescript
744
- import { test, expect } from '@fixtures/base.fixture';
745
-
746
- test.describe('SauceDemo E2E Tests', () => {
747
- test('Login with valid credentials', async ({ loginPage, page }) => {
748
- await loginPage.goto();
749
- await loginPage.login('standard_user', 'secret_sauce');
750
- expect(page.url()).toContain('inventory.html');
751
- });
752
-
753
- test('Add product to cart', async ({ authenticatedUser, inventoryPage }) => {
754
- await inventoryPage.addProductToCart();
755
- const cartCount = await inventoryPage.getCartCount();
756
- expect(cartCount).toBe('1');
757
- });
758
-
759
- test('Logout from inventory', async ({ authenticatedUser, inventoryPage, page }) => {
760
- await inventoryPage.logout();
761
- expect(page.url()).toBe('https://www.saucedemo.com/');
762
- });
763
- });
764
- ```
765
-
766
- ### 17. Form Test with Data (`tests/specs/formTest.spec.ts`)
767
- ```typescript
768
- import { test, expect } from '@fixtures/base.fixture';
769
- import { TestDataManager } from '@utils/testData';
770
-
771
- test.describe('Form Registration Tests with TypeScript', () => {
772
- test('Register with JSON user data', async ({ formPage, page }) => {
773
- const user = TestDataManager.getUserByUsername('standard_user');
774
- expect(user).toBeDefined();
775
-
776
- await page.goto('https://example.com/register');
777
- await formPage.formHelper.fillInput('#firstName', user!.firstName);
778
- await formPage.formHelper.fillInput('#lastName', user!.lastName);
779
- await formPage.formHelper.fillInput('#email', user!.email);
780
-
781
- await page.click('button[type="submit"]');
782
- await expect(page).toHaveURL('**/success');
783
- });
784
-
785
- test.describe.configure({ mode: 'parallel' });
786
-
787
- test('Register with CSV test data - Valid', async ({ formPage, page }) => {
788
- await page.goto('https://example.com/register');
789
- await formPage.fillRegistrationForm('registration_valid');
790
- await page.click('button[type="submit"]');
791
- await expect(page).toHaveURL('**/success');
792
- });
793
-
794
- test('Register with CSV test data - Minimal', async ({ formPage, page }) => {
795
- await page.goto('https://example.com/register');
796
- await formPage.fillRegistrationForm('registration_minimal');
797
- await page.click('button[type="submit"]');
798
- await expect(page).toHaveURL('**/success');
799
- });
800
- });
801
- ```
802
-
803
- ### 18. Run Tests
804
- ```bash
805
- # Run all tests in parallel (4 workers)
806
- npm test
807
-
808
- # Run specific test file
809
- npm test tests/specs/saucedemo.spec.ts
810
-
811
- # Run tests with UI (single worker)
812
- npm run test:headed
813
-
814
- # Debug mode
815
- npm run test:debug
816
-
817
- # Run with custom worker count
818
- npm test -- --workers=2
819
-
820
- # Run BDD scenarios
821
- npm run test:bdd
822
-
823
- # Run BDD scenarios with JSON report
824
- npm run test:bdd:report
825
-
826
- # Run specific BDD feature
827
- npx @cucumber/cucumber-js tests/features/login.feature
828
- ```
829
-
830
- ### 19. BDD Feature File (`tests/features/login.feature`)
831
- ```gherkin
832
- Feature: Login to SauceDemo
833
- As a user
834
- I want to login to the application
835
- So that I can access the inventory
836
-
837
- Background:
838
- Given I navigate to the SauceDemo application
839
-
840
- Scenario: User logs in with valid credentials
841
- When I login with username "standard_user" and password "secret_sauce"
842
- Then I should see the inventory page
843
- And the page title should contain "Inventory"
844
-
845
- Scenario: User logs in and adds product to cart
846
- When I login with username "standard_user" and password "secret_sauce"
847
- And I add "Sauce Labs Backpack" to cart
848
- Then the cart count should show "1"
849
- And the product should be in the cart
850
-
851
- Scenario: User can logout
852
- When I login with username "standard_user" and password "secret_sauce"
853
- And I click the menu button
854
- And I click logout
855
- Then I should be back on the login page
856
- ```
857
-
858
- ### 20. BDD Step Definitions with TypeScript (`tests/steps/loginSteps.ts`)
859
- ```typescript
860
- import { Given, When, Then, Before, After } from '@cucumber/cucumber';
861
- import { chromium, Browser, Page } from '@playwright/test';
862
- import { LoginPage } from '@pages/LoginPage';
863
- import { InventoryPage } from '@pages/InventoryPage';
864
-
865
- let browser: Browser;
866
- let page: Page;
867
- let loginPage: LoginPage;
868
- let inventoryPage: InventoryPage;
869
-
870
- Before(async function() {
871
- browser = await chromium.launch();
872
- page = await browser.newPage();
873
- loginPage = new LoginPage(page);
874
- inventoryPage = new InventoryPage(page);
875
- });
876
-
877
- After(async function() {
878
- await page.close();
879
- await browser.close();
880
- });
881
-
882
- Given('I navigate to the SauceDemo application', async function() {
883
- await loginPage.goto();
884
- });
885
-
886
- When('I login with username {string} and password {string}', async function(username: string, password: string) {
887
- await loginPage.login(username, password);
888
- });
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';
547
+
548
+ export type PageFixtures = {
549
+ homePage: HomePage;
550
+ };
889
551
 
890
- Then('I should see the inventory page', async function() {
891
- await page.waitForURL('**/inventory.html');
892
- });
893
552
 
894
- Then('the page title should contain {string}', async function(title: string) {
895
- const pageTitle = await page.title();
896
- if (!pageTitle.includes(title)) {
897
- throw new Error(`Expected title to contain "${title}" but got "${pageTitle}"`);
898
- }
553
+ export const test = bddTest.extend<PageFixtures>({
554
+ homePage: async ({ page }, use) => {
555
+ const homePage = new HomePage(page);
556
+ await use(homePage);
557
+ },
899
558
  });
900
559
 
901
- When('I add {string} to cart', async function(productName: string) {
902
- const addBtn = '[data-test="add-to-cart-sauce-labs-backpack"]';
903
- await page.click(addBtn);
904
- });
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
+ };
905
577
 
906
- Then('the cart count should show {string}', async function(count: string) {
907
- const cartCount = await inventoryPage.getCartCount();
908
- if (cartCount !== count) {
909
- throw new Error(`Expected cart count "${count}" but got "${cartCount}"`);
910
- }
911
- });
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
+ });
912
584
 
913
- When('I click the menu button', async function() {
914
- await page.click('[data-test="bm-menu-button"]');
915
- });
585
+ await use(api);
586
+ await api.dispose();
587
+ },
916
588
 
917
- When('I click logout', async function() {
918
- await page.click('[data-test="logout-sidebar-link"]');
919
- });
589
+ apiAsAreaAdmin: async ({}, use) => {
590
+ const api = await request.newContext({
591
+ baseURL: process.env.API_BASE_URL,
592
+ storageState: 'storage/area-admin.json',
593
+ });
920
594
 
921
- Then('I should be back on the login page', async function() {
922
- await page.waitForURL('https://www.saucedemo.com/');
923
- });
924
- ```
925
- });
595
+ await use(api);
596
+ await api.dispose();
597
+ },
926
598
 
927
- test('Logout from inventory', async ({ authenticatedUser, page }) => {
928
- await page.click('[data-test="bm-menu-button"]');
929
- await page.click('[data-test="logout-sidebar-link"]');
930
- await expect(page).toHaveURL('https://www.saucedemo.com/');
931
- });
932
- ```
933
-
934
- ### 17. BDD Feature File (`tests/features/login.feature`)
935
- ```gherkin
936
- Feature: Login to SauceDemo
937
- As a user
938
- I want to login to the application
939
- So that I can access the inventory
940
-
941
- Background:
942
- Given I navigate to the SauceDemo application
943
-
944
- Scenario: User logs in with valid credentials
945
- When I login with username "standard_user" and password "secret_sauce"
946
- Then I should see the inventory page
947
- And the page title should contain "Inventory"
948
-
949
- Scenario: User logs in and adds product to cart
950
- When I login with username "standard_user" and password "secret_sauce"
951
- And I add "Sauce Labs Backpack" to cart
952
- Then the cart count should show "1"
953
- And the product should be in the cart
954
-
955
- Scenario: User can logout
956
- When I login with username "standard_user" and password "secret_sauce"
957
- And I click the menu button
958
- And I click logout
959
- Then I should be back on the login page
960
- ```
961
-
962
- ### 18. BDD Shopping Feature (`tests/features/shopping.feature`)
963
- ```gherkin
964
- Feature: Shopping Cart
965
- As a user
966
- I want to manage my shopping cart
967
- So that I can purchase items
968
-
969
- Background:
970
- Given I am logged into the application
971
-
972
- Scenario: Add single item to cart
973
- When I add "Sauce Labs Backpack" to cart
974
- Then the cart count should show "1"
975
-
976
- Scenario: Add multiple items to cart
977
- When I add "Sauce Labs Backpack" to cart
978
- And I add "Sauce Labs Bike Light" to cart
979
- Then the cart count should show "2"
980
-
981
- Scenario: Remove item from cart
982
- When I add "Sauce Labs Backpack" to cart
983
- And I remove "Sauce Labs Backpack" from cart
984
- Then the cart should be empty
985
- ```
986
-
987
- ### 19. BDD Step Definitions (`tests/steps/loginSteps.js`)
988
- ```javascript
989
- import { Given, When, Then, Before, After } from '@cucumber/cucumber';
990
- import { chromium } from 'playwright';
991
- import { LoginPage } from '../pages/LoginPage';
992
- import { InventoryPage } from '../pages/InventoryPage';
993
-
994
- let browser;
995
- let page;
996
- let loginPage;
997
- let inventoryPage;
998
-
999
- Before(async function() {
1000
- browser = await chromium.launch();
1001
- page = await browser.newPage();
1002
- loginPage = new LoginPage(page);
1003
- inventoryPage = new InventoryPage(page);
599
+ db: async ({}, use) => {
600
+ const pool = await getDbPool();
601
+ await use(pool);
602
+ await pool.close();
603
+ },
1004
604
  });
1005
605
 
1006
- After(async function() {
1007
- await page.close();
1008
- await browser.close();
1009
- });
606
+ 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'
1010
611
 
1011
- Given('I navigate to the SauceDemo application', async function() {
1012
- await loginPage.goto();
1013
- });
1014
612
 
1015
- When('I login with username {string} and password {string}', async function(username, password) {
1016
- await loginPage.login(username, password);
1017
- });
613
+ const { Given, When, Then } = createBdd(test);
1018
614
 
1019
- Then('I should see the inventory page', async function() {
1020
- await page.waitForURL('**/inventory.html');
1021
- });
615
+ Given('user opens the Allocate application', async ({ homePage }) => {
616
+ await homePage.open();
617
+ await homePage.isBbcMenuLoaded();
1022
618
 
1023
- Then('the page title should contain {string}', async function(title) {
1024
- const pageTitle = await page.title();
1025
- if (!pageTitle.includes(title)) {
1026
- throw new Error(`Expected title to contain "${title}" but got "${pageTitle}"`);
1027
- }
1028
619
  });
620
+ When('user create new facility', async ({homePage}) => {
1029
621
 
1030
- When('I add {string} to cart', async function(productName) {
1031
- const addBtn = '[data-test="add-to-cart-sauce-labs-backpack"]';
1032
- await page.click(addBtn);
1033
- });
622
+ await homePage.createFacility()
1034
623
 
1035
- Then('the cart count should show {string}', async function(count) {
1036
- const cartCount = await inventoryPage.getCartCount();
1037
- if (cartCount !== count) {
1038
- throw new Error(`Expected cart count "${count}" but got "${cartCount}"`);
1039
- }
1040
- });
624
+ await homePage.verifyFacilityAdded()
1041
625
 
1042
- When('I click the menu button', async function() {
1043
- await page.click('[data-test="bm-menu-button"]');
1044
- });
626
+ await homePage.assertViewFacility()
1045
627
 
1046
- When('I click logout', async function() {
1047
- await page.click('[data-test="logout-sidebar-link"]');
628
+ await homePage.deleteFacility()
629
+
1048
630
  });
631
+ 14) tests/ui/steps/scheduling-groups.steps.ts
632
+ typescript
633
+
634
+ import { createBdd } from 'playwright-bdd';
635
+ import { test,expect } from '@fixtures/pages.fixture';
636
+
637
+ const { Given, When, Then } = createBdd(test);
638
+
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
+ });
644
+
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
+ };
1049
693
 
1050
- Then('I should be back on the login page', async function() {
1051
- await page.waitForURL('https://www.saucedemo.com/');
1052
- });
1053
- ```
1054
-
1055
- ### 20. BDD Shopping Steps (`tests/steps/shoppingSteps.js`)
1056
- ```javascript
1057
- import { Given, When, Then } from '@cucumber/cucumber';
1058
- import { LoginPage } from '../pages/LoginPage';
1059
-
1060
- let page;
1061
- let loginPage;
1062
-
1063
- Given('I am logged into the application', async function() {
1064
- const { page: gherkinPage } = this;
1065
- page = gherkinPage;
1066
- loginPage = new LoginPage(page);
1067
- await loginPage.goto();
1068
- await loginPage.login('standard_user', 'secret_sauce');
1069
- });
1070
694
 
1071
- When('I remove {string} from cart', async function(productName) {
1072
- const removeBtn = '[data-test="remove-sauce-labs-backpack"]';
1073
- await page.click(removeBtn);
1074
- });
695
+ //api
696
+ const res = await createSchedulingGroup(apiAsSystemAdmin, payload);
697
+ expect(res.status()).toBe(201);
1075
698
 
1076
- Then('the cart should be empty', async function() {
1077
- const cartBadge = page.locator('[data-test="shopping-cart-badge"]');
1078
- const isHidden = await cartBadge.isHidden().catch(() => true);
1079
- if (!isHidden) {
1080
- throw new Error('Cart is not empty');
1081
- }
1082
- });
699
+ const record = await SchedulingGroupQueries.getByName(db, payload.groupName);
1083
700
 
1084
- Then('the product should be in the cart', async function() {
1085
- const badge = await page.locator('[data-test="shopping-cart-badge"]').isVisible();
1086
- if (!badge) {
1087
- throw new Error('Product not found in cart');
1088
- }
701
+ //db
702
+ assertGroupExists(record);
1089
703
  });
1090
- ```
1091
-
1092
- ### 21. Run Tests
1093
- ```bash
1094
- # Run all tests in parallel (4 workers)
1095
- npm test
1096
-
1097
- # Run specific test file
1098
- npm test tests/specs/saucedemo.spec.js
1099
-
1100
- # Run tests with UI (single worker)
1101
- npm run test:headed
1102
-
1103
- # Debug mode
1104
- npm run test:debug
1105
-
1106
- # Run with custom worker count
1107
- npm test -- --workers=2
1108
-
1109
- # Run BDD scenarios
1110
- npm run test:bdd
1111
-
1112
- # Run BDD scenarios with JSON report
1113
- npm run test:bdd:report
1114
704
 
1115
- # Run specific BDD feature
1116
- npx @cucumber/cucumber-js tests/features/login.feature
1117
- ```
1118
-
1119
- ### 22. Example Test Using Utilities (`tests/specs/formTest.spec.js`)
1120
- ```javascript
1121
- import { test, expect } from '../fixtures/base.fixture';
1122
- import { FormPage } from '../pages/FormPage';
1123
- import { TestDataManager } from '../utils/testData';
1124
-
1125
- test.describe('Form Registration with Test Data', () => {
1126
- let formPage;
1127
-
1128
- test.beforeEach(async ({ page }) => {
1129
- formPage = new FormPage(page);
1130
- await page.goto('https://example.com/register');
1131
- });
1132
-
1133
- test('Register with JSON user data', async ({ page }) => {
1134
- const user = TestDataManager.getUserByUsername('standard_user');
1135
-
1136
- await formPage.formHelper.fillInput('#firstName', user.firstName);
1137
- await formPage.formHelper.fillInput('#lastName', user.lastName);
1138
- await formPage.formHelper.fillInput('#email', user.email);
1139
-
1140
- await page.click('button[type="submit"]');
1141
- await expect(page).toHaveURL('**/success');
1142
- });
1143
-
1144
- test('Register with CSV test scenarios', async ({ page }) => {
1145
- await formPage.fillRegistrationForm('registration_valid');
1146
-
1147
- await page.click('button[type="submit"]');
1148
- await expect(page).toHaveURL('**/success');
705
+ test("Scheduling Group Name is mandatory", async ({ apiAsSystemAdmin }) => {
706
+ const res = await apiAsSystemAdmin.post("/scheduling-groups", {
707
+ data: { area: "London" },
1149
708
  });
1150
709
 
1151
- test('Fill complex form with multiple field types', async ({ page }) => {
1152
- const formConfig = {
1153
- firstName: {
1154
- type: 'text',
1155
- selector: '#firstName',
1156
- value: 'John'
1157
- },
1158
- country: {
1159
- type: 'dropdown',
1160
- selector: '.country-select',
1161
- value: 'United States'
1162
- },
1163
- gender: {
1164
- type: 'radio',
1165
- value: 'Male'
1166
- },
1167
- interests: {
1168
- type: 'checkbox',
1169
- value: ['Sports', 'Gaming', 'Reading']
1170
- },
1171
- terms: {
1172
- type: 'checkbox',
1173
- value: 'I accept terms'
1174
- }
1175
- };
1176
-
1177
- // Use helper to fill entire form
1178
- for (const field of Object.values(formConfig)) {
1179
- if (field.type === 'text') {
1180
- await formPage.formHelper.fillInput(field.selector, field.value);
1181
- } else if (field.type === 'dropdown') {
1182
- await formPage.formHelper.selectDropdown(field.selector, field.value);
1183
- } else if (field.type === 'radio') {
1184
- await formPage.formHelper.selectRadioButton(field.value);
1185
- } else if (field.type === 'checkbox') {
1186
- if (Array.isArray(field.value)) {
1187
- await formPage.formHelper.checkMultiple(field.value);
1188
- } else {
1189
- await formPage.formHelper.checkCheckbox(field.value);
1190
- }
1191
- }
1192
- }
1193
-
1194
- await page.click('button[type="submit"]');
1195
- await expect(page).toHaveURL('**/success');
1196
- });
1197
-
1198
- test('Verify form values after filling', async ({ page }) => {
1199
- await formPage.formHelper.fillInput('#firstName', 'John');
1200
- await formPage.formHelper.checkCheckbox('I accept terms');
1201
-
1202
- const firstName = await formPage.formHelper.getInputValue('#firstName');
1203
- const isTermsChecked = await formPage.formHelper.isCheckboxChecked('I accept terms');
1204
-
1205
- expect(firstName).toBe('John');
1206
- expect(isTermsChecked).toBe(true);
1207
- });
710
+ expect(res.status()).toBe(400);
1208
711
  });
1209
- ```
1210
-
1211
- ### 23. BDD Step with Test Data Integration
1212
- ```gherkin
1213
- Feature: Registration with Test Data
1214
- As a new user
1215
- I want to register using predefined test data
1216
- So that I can access the application
1217
-
1218
- Scenario: Register with JSON test data
1219
- Given I navigate to the registration page
1220
- When I fill the form with user data "standard_user"
1221
- And I accept the terms and conditions
1222
- And I submit the form
1223
- Then I should see the success message
1224
-
1225
- Scenario Outline: Register with CSV test scenarios
1226
- Given I navigate to the registration page
1227
- When I fill first name with "<firstName>"
1228
- And I fill last name with "<lastName>"
1229
- And I fill email with "<email>"
1230
- And I select country "<country>"
1231
- And I check "<acceptTerms>"
1232
- And I submit the form
1233
- Then I should see the success message
1234
-
1235
- Examples:
1236
- | firstName | lastName | email | country | acceptTerms |
1237
- | John | Doe | john@test.com | USA | true |
1238
- | Jane | Smith | jane@test.com | Canada | true |
1239
- | Pierre | Martin | pierre@test.fr | France | true |
1240
- ```
1241
-
1242
- ## Developer Quick Start
1243
-
1244
- ### Using Test Data & Form Helpers
1245
- **Step 1:** Create test data files in `tests/data/` (JSON, CSV, YAML)
1246
- **Step 2:** Use `DataReader` to load test data:
1247
- ```javascript
1248
- const users = DataReader.readJSON('users.json');
1249
- const formData = await DataReader.readCSV('formData.csv');
1250
- const config = DataReader.readYAML('config.yaml');
1251
- ```
1252
-
1253
- **Step 3:** Use `FormHelper` for complex form interactions:
1254
- ```javascript
1255
- const formHelper = new FormHelper(page);
1256
- await formHelper.fillInput('#name', 'John');
1257
- await formHelper.selectDropdown('.country', 'USA');
1258
- await formHelper.checkMultiple(['Option1', 'Option2']);
1259
- ```
1260
-
1261
- **Step 4:** Create page objects that combine both:
1262
- ```javascript
1263
- const formPage = new FormPage(page);
1264
- await formPage.fillRegistrationForm('registration_valid');
1265
- ```
1266
-
1267
- ### Traditional Playwright Tests
1268
- **Step 1:** Copy project structure from Section 4
1269
- **Step 2:** Create page objects in `tests/pages/` (copy LoginPage & InventoryPage)
1270
- **Step 3:** Import fixtures in your tests: `import { test, expect } from '../fixtures/base.fixture'`
1271
- **Step 4:** Use fixtures in tests: `test('my test', async ({ loginPage, authenticatedUser }) => { ... })`
1272
- **Step 5:** Run: `npm test`
1273
-
1274
- ### BDD Scenarios
1275
- **Step 1:** Create `.feature` files in `tests/features/` with Gherkin scenarios
1276
- **Step 2:** Create step definitions in `tests/steps/` (copy loginSteps & shoppingSteps)
1277
- **Step 3:** Map steps to page objects (reuse LoginPage, InventoryPage)
1278
- **Step 4:** Configure `cucumber.js` for parallel execution
1279
- **Step 5:** Run: `npm run test:bdd`
1280
-
1281
- ## Verification Checklist
1282
- ✅ Chromium browser installed (`npx playwright install chromium`)
1283
- ✅ TypeScript configured (`typescript @types/node` installed + `tsconfig.json` created)
1284
- ✅ All dependencies installed (`npm install` + `@cucumber/cucumber` + `csv-parser`, `js-yaml`, `@types/js-yaml`)
1285
- ✅ TypeScript paths configured in `tsconfig.json` (@pages/*, @utils/*, @fixtures/*, @data/*)
1286
- ✅ Playwright config in TypeScript (`playwright.config.ts` with latest imports)
1287
- ✅ Folder structure created (all `.ts` files: `tests/pages/`, `tests/fixtures/`, `tests/specs/`, `tests/features/`, `tests/steps/`, `tests/utils/`, `tests/data/`)
1288
- ✅ Base fixtures created with TypeScript typing (`base.fixture.ts`)
1289
- ✅ Cucumber configuration created (`cucumber.js` with ts-node support)
1290
- ✅ Page Object Model classes in TypeScript with type safety
1291
- ✅ Step definitions in TypeScript with proper typing
1292
- ✅ Feature files created (login.feature, shopping.feature)
1293
- ✅ Utilities created in TypeScript (dataReader.ts, formHelper.ts, testData.ts with interfaces)
1294
- ✅ Test data files created (users.json, formData.csv, config.yaml)
1295
- ✅ Enhanced page objects in TypeScript (FormPage.ts with utilities integrated)
1296
- ✅ Modern ES module imports throughout (import/export)
1297
- ✅ Parallel execution enabled (`fullyParallel: true` & Cucumber parallel: 4)
1298
- ✅ Tests execute in parallel against single Chromium browser
1299
- ✅ Both Playwright & BDD tests configured with full TypeScript support
1300
-
1301
- ## Best Practices for Scalability & TypeScript
1302
- ✅ **Use Fixtures** - Pre-set up authenticated users, page objects (base.fixture.ts with types)
1303
- ✅ **Page Object Model** - Encapsulate selectors & actions in TypeScript classes
1304
- ✅ **Type Safety** - Full TypeScript typing for all utilities, pages, and tests
1305
- ✅ **Test Data Management** - Use JSON, CSV, YAML with DataReader (fully typed)
1306
- ✅ **Form Helpers** - Reusable FormHelper for all form interactions (typed interfaces)
1307
- ✅ **BDD for Non-Technical Stakeholders** - Write scenarios in Gherkin (Feature files)
1308
- ✅ **Parallel Execution** - Tests & scenarios run simultaneously (4 workers by default)
1309
- ✅ **Minimal Developer Effort** - Write only test logic, reuse fixtures, pages, steps & utilities
1310
- ✅ **Chromium Only** - Single browser reduces flakiness & speeds up execution
1311
- ✅ **Retry Failed Tests** - Automatic retries improve reliability
1312
- ✅ **Capture Failures** - Screenshots & videos on failures for debugging
1313
- ✅ **Dual Approach** - Mix traditional tests & BDD scenarios based on needs
1314
- ✅ **Module Path Aliases** - Use @pages/, @utils/, @fixtures/, @data/ for clean imports
1315
- ✅ **Latest Playwright** - Uses latest Playwright APIs with modern module syntax
1316
- ✅ **Parameterized Tests** - Use CSV data for scenario outlines (data-driven testing)
1317
- ✅ **Form Helpers** - Reusable FormHelper for all form interactions (text, dropdown, radio, checkbox)
1318
- ✅ **BDD for Non-Technical Stakeholders** - Write scenarios in Gherkin (Feature files)
1319
- ✅ **Parallel Execution** - Tests & scenarios run simultaneously (4 workers by default)
1320
- ✅ **Minimal Developer Effort** - Write only test logic, reuse fixtures, pages, steps & utilities
1321
- ✅ **Chromium Only** - Single browser reduces flakiness & speeds up execution
1322
- ✅ **Retry Failed Tests** - Automatic retries improve reliability
1323
- ✅ **Capture Failures** - Screenshots & videos on failures for debugging
1324
- ✅ **Dual Approach** - Mix traditional tests & BDD scenarios based on needs
1325
- ✅ **Parameterized Tests** - Use CSV data for scenario outlines (data-driven testing)
1326
- ✅ **Full TypeScript Support** - All code written in TypeScript with strict mode enabled
1327
- ✅ **Path Aliases** - Clean imports using @pages/, @utils/, @fixtures/, @data/ prefixes
1328
-
1329
- ## How to Use Test Data & Utilities (TypeScript)
1330
-
1331
- ### 1. Loading Test Data
1332
- ```typescript
1333
- import { TestDataManager } from '@utils/testData';
1334
-
1335
- // Get user by username (fully typed)
1336
- const user = TestDataManager.getUserByUsername('standard_user');
1337
-
1338
- // Get form data by scenario
1339
- const formData = await TestDataManager.getFormDataByScenario('registration_valid');
1340
-
1341
- // Load all config with type safety
1342
- const config = TestDataManager.loadConfig();
1343
- ```
1344
-
1345
- ### 2. Form Interactions with Type Safety
1346
- ```typescript
1347
- import { FormHelper } from '@utils/formHelper';
1348
- import { Page } from '@playwright/test';
1349
-
1350
- const formHelper = new FormHelper(page);
1351
-
1352
- // Text input
1353
- await formHelper.fillInput('#firstName', 'John');
1354
-
1355
- // Dropdown (non-select)
1356
- await formHelper.selectDropdown('.country-dropdown', 'USA');
1357
-
1358
- // Radio button
1359
- await formHelper.selectRadioButton('Male');
1360
-
1361
- // Single checkbox
1362
- await formHelper.checkCheckbox('I accept terms');
1363
-
1364
- // Multiple checkboxes
1365
- await formHelper.checkMultiple(['Option1', 'Option2', 'Option3']);
1366
-
1367
- // Get and verify values (fully typed)
1368
- const firstName = await formHelper.getInputValue('#firstName');
1369
- const isChecked = await formHelper.isCheckboxChecked('I accept terms');
1370
- ```
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';
1371
716
 
1372
- ### 3. Data-Driven Tests in TypeScript
1373
- ```typescript
1374
- import { test, expect } from '@fixtures/base.fixture';
1375
- import { FormPage } from '@pages/FormPage';
1376
-
1377
- test.describe('Data-Driven Form Tests', () => {
1378
- const testCases = [
1379
- { scenario: 'registration_valid' as const },
1380
- { scenario: 'registration_minimal' as const },
1381
- { scenario: 'registration_eu' as const }
1382
- ];
1383
-
1384
- testCases.forEach((testCase) => {
1385
- test(`Register with ${testCase.scenario}`, async ({ page, formPage }: any) => {
1386
- await formPage.fillRegistrationForm(testCase.scenario);
1387
- // Assert...
1388
- });
1389
- });
717
+ test('Tables exist', async ({ db }) => {
718
+ const tables = await listTables(db);
719
+ expect(tables.length).toBeGreaterThan(0);
1390
720
  });
1391
- ```
1392
-
1393
- ### 4. Using Module Path Aliases
1394
- ```typescript
1395
- // Clean imports with path aliases
1396
- import { LoginPage } from '@pages/LoginPage';
1397
- import { FormHelper } from '@utils/formHelper';
1398
- import { DataReader } from '@utils/dataReader';
1399
- import { test, expect } from '@fixtures/base.fixture';
1400
-
1401
- // Old way (avoid)
1402
- // import { LoginPage } from '../pages/LoginPage';
1403
- ```
1404
-
1405
- ## When to Use Traditional vs BDD
1406
- **Use Traditional Tests (`npm test`)** when:
1407
- - Writing complex technical validations
1408
- - Need direct access to page objects with full typing
1409
- - Testing internal APIs or utilities
1410
- - Data-driven test variations
1411
- - Using TypeScript for type safety
1412
-
1413
- **Use BDD (`npm run test:bdd`)** when:
1414
- - Collaborating with non-technical stakeholders
1415
- - Business logic needs clear documentation
1416
- - Multiple teams working on same scenarios
1417
- - Acceptance criteria need to be testable
1418
-
1419
- **Note:** This setup uses Chromium only with parallel execution, full TypeScript support with strict mode, and modern ES module imports. Both Playwright & BDD tests can run simultaneously with complete type safety. All utilities and page objects are fully typed. Module path aliases (@pages/, @utils/, etc.) provide clean imports. Latest Playwright features and APIs are used throughout. If you see "Missing script: start" error, add the `start` script to your package.json before running tests.
721
+ 20) tests/utils/readJson.ts
722
+ typescript
723
+ import fs from 'fs';
724
+ import path from 'path';
725
+ import { FormField } from '../types/formField';
726
+
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.