@zohodesk/testinglibrary 0.0.45-n20-experimental → 0.0.47-n20-experimental
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/AUTO_CLEANUP_PLAN.md +332 -0
- package/README.md +8 -0
- package/build/common/data-generator/steps/DataGenerator.spec.js +1 -1
- package/build/common/data-generator/steps/DataGeneratorStepsHelper.js +14 -4
- package/build/core/dataGenerator/DataGenerator.js +63 -15
- package/build/core/dataGenerator/validateGenerators.js +82 -0
- package/build/core/playwright/constants/reporterConstants.js +0 -1
- package/build/core/playwright/readConfigFile.js +2 -1
- package/build/core/playwright/report-generator.js +42 -0
- package/build/core/playwright/validateFeature.js +11 -0
- package/build/lib/cli.js +7 -30
- package/build/utils/commonUtils.js +0 -9
- package/npm-shrinkwrap.json +166 -104
- package/package.json +7 -8
- package/.vscode/mcp.json +0 -9
- package/build/core/playwright/reporter/PlaywrightReporter.js +0 -44
- package/build/core/playwright/reporter/UnitReporter.js +0 -27
- package/zohodesk-testinglibrary-0.0.34-n20-experimental.tgz +0 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Data Generator System — Design & Constraints
|
|
2
|
+
|
|
3
|
+
## Data Generator Overview
|
|
4
|
+
|
|
5
|
+
### How It Works
|
|
6
|
+
- BDD step: `Given generate a "{Type}" entity "{name}" with generator "{GeneratorName}"`
|
|
7
|
+
- Framework resolves generator template from `data-generators/*.json` files
|
|
8
|
+
- Walks up directory tree from feature file to find `data-generators/` folder
|
|
9
|
+
- Scans ALL `.json` files within (not hardcoded to `generators.json`)
|
|
10
|
+
- Generator names must be **unique across the entire UAT suite**
|
|
11
|
+
- `ZDTestingFramework validate` checks for duplicate generator names before test run
|
|
12
|
+
|
|
13
|
+
### Generator JSON Structure
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"generators": {
|
|
17
|
+
"GeneratorName": [
|
|
18
|
+
{
|
|
19
|
+
"type": "dynamic",
|
|
20
|
+
"name": "stepName",
|
|
21
|
+
"generatorOperationId": "support.Module.operationName",
|
|
22
|
+
"dataPath": "$.response.body:$",
|
|
23
|
+
"params": { "key": "$previousStep.value" },
|
|
24
|
+
"cleanup": { ... }
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Constraints
|
|
32
|
+
| Constraint | Detail |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Unique generator names** | Generator names must be unique across all `data-generators/*.json` files in the UAT suite. Walk-up discovery returns the first match. |
|
|
35
|
+
| **Walk-up discovery** | Framework walks from feature file directory toward filesystem root, scanning each level for `data-generators/` folder. |
|
|
36
|
+
| **Multiple JSON files** | Any `.json` file in a `data-generators/` folder is scanned (e.g., `ticket.json`, `account.json`, `contact.json`). |
|
|
37
|
+
| **Parent serves children** | A `data-generators/` folder at a parent module level serves all sub-modules via walk-up (reduces duplication). |
|
|
38
|
+
| **DG_API_NAME matching** | In Gherkin data tables, `DG_API_NAME` column matches generator step's `name` field to inject params. |
|
|
39
|
+
| **Chained steps** | Multi-step generators use `$previousStep.value` syntax to pass output from one step to the next. |
|
|
40
|
+
| **dataPath extraction** | `dataPath` uses JSONPath to extract specific fields from DG service response (server-side). |
|
|
41
|
+
| **Cached response** | Generated data is cached via `cacheLayer.set(entityName, response.data)` for use in subsequent steps. |
|
|
42
|
+
| **Validation** | `ZDTestingFramework validate` scans all `data-generators/` directories and fails if duplicate generator names are found. |
|
|
43
|
+
|
|
44
|
+
### File Discovery Pattern
|
|
45
|
+
```
|
|
46
|
+
feature-files/MyTest.feature ← test starts here
|
|
47
|
+
↑ walks up
|
|
48
|
+
data-generators/ticket.json ← found at same level or parent
|
|
49
|
+
data-generators/account.json ← multiple files supported
|
|
50
|
+
↑ keeps walking up
|
|
51
|
+
../data-generators/shared.json ← parent level generators serve all children
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
# Auto-Cleanup Generated Test Data After Scenario
|
|
57
|
+
|
|
58
|
+
## Context
|
|
59
|
+
|
|
60
|
+
The framework generates test data (contacts, tickets, accounts, etc.) via the DG service but never cleans it up. Generated data accumulates in test portals. We need auto-cleanup that deletes all generated entities after each scenario ends.
|
|
61
|
+
|
|
62
|
+
**Approach:** Auto-cleanup all generated entities after each scenario via fixture teardown. No explicit step needed in feature files. Cleanup config is defined in the generator JSON via a `cleanup` property on each generator step.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Three Cleanup Types
|
|
67
|
+
|
|
68
|
+
### Type 1: `oas` — DG service delete (OAS supported)
|
|
69
|
+
```json
|
|
70
|
+
"cleanup": {
|
|
71
|
+
"type": "oas",
|
|
72
|
+
"operationId": "support.Contact.deleteContact",
|
|
73
|
+
"idPath": "$.id"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Type 2: `api` — Direct REST API (OAS not supported, DELETE)
|
|
78
|
+
```json
|
|
79
|
+
"cleanup": {
|
|
80
|
+
"type": "api",
|
|
81
|
+
"method": "DELETE",
|
|
82
|
+
"apiPath": "/api/v1/contracts/{id}",
|
|
83
|
+
"idPath": "$.id"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Type 3: `api` — Disable instead of delete (PATCH)
|
|
88
|
+
```json
|
|
89
|
+
"cleanup": {
|
|
90
|
+
"type": "api",
|
|
91
|
+
"method": "PATCH",
|
|
92
|
+
"apiPath": "/api/v1/webhooks/{id}",
|
|
93
|
+
"idPath": "$.id",
|
|
94
|
+
"body": { "isActive": false }
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### No cleanup — read-only operations
|
|
99
|
+
Steps without `cleanup` property are skipped (e.g., `getDepartments`).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Full Generator JSON Examples
|
|
104
|
+
|
|
105
|
+
### Contact (OAS supported)
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"generators": {
|
|
109
|
+
"CreateContactRecord": [
|
|
110
|
+
{
|
|
111
|
+
"type": "dynamic",
|
|
112
|
+
"name": "CreateContact",
|
|
113
|
+
"generatorOperationId": "support.Contact.createContact",
|
|
114
|
+
"dataPath": "$.response.body:$",
|
|
115
|
+
"cleanup": {
|
|
116
|
+
"type": "oas",
|
|
117
|
+
"operationId": "support.Contact.deleteContact",
|
|
118
|
+
"idPath": "$.id"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Ticket with dependencies (OAS supported, multi-step)
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"generators": {
|
|
130
|
+
"TicketWithDepartment": [
|
|
131
|
+
{
|
|
132
|
+
"type": "dynamic",
|
|
133
|
+
"name": "departments",
|
|
134
|
+
"generatorOperationId": "support.Department.getDepartments",
|
|
135
|
+
"dataPath": "$.response.body:$.data[0].id"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"type": "dynamic",
|
|
139
|
+
"name": "products",
|
|
140
|
+
"generatorOperationId": "support.Product.createProduct",
|
|
141
|
+
"dataPath": "$.response.body:$.id",
|
|
142
|
+
"cleanup": {
|
|
143
|
+
"type": "oas",
|
|
144
|
+
"operationId": "support.Product.deleteProduct",
|
|
145
|
+
"idPath": "$.id"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"type": "dynamic",
|
|
150
|
+
"name": "CreateTicket",
|
|
151
|
+
"generatorOperationId": "support.Ticket.createTicket",
|
|
152
|
+
"dataPath": "$.response.body:$",
|
|
153
|
+
"cleanup": {
|
|
154
|
+
"type": "oas",
|
|
155
|
+
"operationId": "support.Ticket.deleteTicket",
|
|
156
|
+
"idPath": "$.id"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Webhook (OAS not supported — disable via REST API)
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"generators": {
|
|
168
|
+
"CreateWebhookRecord": [
|
|
169
|
+
{
|
|
170
|
+
"type": "dynamic",
|
|
171
|
+
"name": "CreateWebhook",
|
|
172
|
+
"generatorOperationId": "support.Webhook.createWebhook",
|
|
173
|
+
"dataPath": "$.response.body:$",
|
|
174
|
+
"cleanup": {
|
|
175
|
+
"type": "api",
|
|
176
|
+
"method": "PATCH",
|
|
177
|
+
"apiPath": "/api/v1/webhooks/{id}",
|
|
178
|
+
"idPath": "$.id",
|
|
179
|
+
"body": { "isActive": false }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Contract (OAS not supported — delete via REST API)
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"generators": {
|
|
191
|
+
"CreateContractRecord": [
|
|
192
|
+
{
|
|
193
|
+
"type": "dynamic",
|
|
194
|
+
"name": "CreateContract",
|
|
195
|
+
"generatorOperationId": "support.Contract.createContract",
|
|
196
|
+
"dataPath": "$.response.body:$",
|
|
197
|
+
"cleanup": {
|
|
198
|
+
"type": "api",
|
|
199
|
+
"method": "DELETE",
|
|
200
|
+
"apiPath": "/api/v1/contracts/{id}",
|
|
201
|
+
"idPath": "$.id"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## How It Works
|
|
212
|
+
|
|
213
|
+
### Cleanup flow
|
|
214
|
+
```
|
|
215
|
+
Scenario ends (pass OR fail)
|
|
216
|
+
↓
|
|
217
|
+
cleanupTracker fixture teardown fires (guaranteed by Playwright)
|
|
218
|
+
↓
|
|
219
|
+
For each tracked entity (reverse order):
|
|
220
|
+
- Read cleanup config from the generator step
|
|
221
|
+
- Skip if no cleanup property
|
|
222
|
+
- Extract entity ID using cleanup.idPath from cached response
|
|
223
|
+
- Based on cleanup.type:
|
|
224
|
+
- "oas" → POST to DG service with cleanup.operationId + entity ID
|
|
225
|
+
- "api" → Direct HTTP request (cleanup.method + cleanup.apiPath with {id} replaced)
|
|
226
|
+
- Log result (success or failure — never fail the test)
|
|
227
|
+
↓
|
|
228
|
+
Clear tracker for next scenario
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Cleanup on scenario failure
|
|
232
|
+
|
|
233
|
+
Playwright fixtures guarantee that teardown code (everything after `await use()`) runs **regardless of whether the test passes or fails**. This means:
|
|
234
|
+
|
|
235
|
+
- **Scenario passes** → cleanup runs
|
|
236
|
+
- **Scenario fails** → cleanup still runs
|
|
237
|
+
- **Individual cleanup step fails** → remaining cleanup steps still run (each step is wrapped in try/catch)
|
|
238
|
+
|
|
239
|
+
This is a core Playwright design principle — fixtures always clean up after themselves.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Files to Modify/Create
|
|
244
|
+
|
|
245
|
+
### 1. New: `src/core/dataGenerator/CleanupTracker.js`
|
|
246
|
+
|
|
247
|
+
Tracks generated entities for cleanup:
|
|
248
|
+
|
|
249
|
+
```js
|
|
250
|
+
class CleanupTracker {
|
|
251
|
+
#entries = [];
|
|
252
|
+
|
|
253
|
+
track({ entityName, generators, cachedData, actorInfo }) {
|
|
254
|
+
this.#entries.push({ entityName, generators, cachedData, actorInfo });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getEntries() { return [...this.#entries]; }
|
|
258
|
+
clear() { this.#entries = []; }
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 2. New: `src/core/dataGenerator/DataCleanup.js`
|
|
263
|
+
|
|
264
|
+
Performs the actual deletion with two strategies:
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
class DataCleanup {
|
|
268
|
+
async cleanup(entries) {
|
|
269
|
+
for (const entry of entries.reverse()) {
|
|
270
|
+
for (const step of [...entry.generators].reverse()) {
|
|
271
|
+
if (!step.cleanup) continue;
|
|
272
|
+
try {
|
|
273
|
+
const entityId = this.#extractId(entry.cachedData, step.cleanup.idPath);
|
|
274
|
+
|
|
275
|
+
if (step.cleanup.type === 'oas') {
|
|
276
|
+
await this.#cleanupViaOAS(step.cleanup.operationId, entityId, entry.actorInfo);
|
|
277
|
+
} else if (step.cleanup.type === 'api') {
|
|
278
|
+
await this.#cleanupViaAPI(step.cleanup, entityId, entry.actorInfo);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
Logger.log(Logger.SUCCESS_TYPE, `Cleanup: ${step.cleanup.method || 'delete'} ${step.name} (${entityId})`);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
Logger.log(Logger.INFO_TYPE, `Cleanup warning for ${step.name}: ${err.message}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async #cleanupViaOAS(operationId, entityId, actorInfo) {
|
|
290
|
+
// POST to DG service with delete operationId + entity ID as param
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async #cleanupViaAPI(cleanupConfig, entityId, actorInfo) {
|
|
294
|
+
// Direct HTTP call: cleanupConfig.method + cleanupConfig.apiPath.replace('{id}', entityId)
|
|
295
|
+
// If cleanupConfig.body exists, send as request body (for PATCH/disable)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Key behaviors:
|
|
301
|
+
- Delete in **reverse order** (dependent entities first)
|
|
302
|
+
- `type: "oas"` → POST to DG service (same auth/env as generation)
|
|
303
|
+
- `type: "api"` → Direct REST call (supports DELETE, PATCH, any HTTP method)
|
|
304
|
+
- `body` field for PATCH operations (e.g., `{ "isActive": false }`)
|
|
305
|
+
- `{id}` placeholder in `apiPath` replaced with extracted entity ID
|
|
306
|
+
- **Never fail the test** — log warnings only
|
|
307
|
+
|
|
308
|
+
### 3. Modify: `src/core/playwright/builtInFixtures/cacheLayer.js`
|
|
309
|
+
|
|
310
|
+
Add `cleanupTracker` fixture. Code after `await use()` runs after scenario ends:
|
|
311
|
+
|
|
312
|
+
### 4. Modify: `src/core/dataGenerator/DataGenerator.js`
|
|
313
|
+
|
|
314
|
+
Return generator steps alongside response: `return { response, generators }`
|
|
315
|
+
|
|
316
|
+
### 5. Modify: `src/common/data-generator/steps/DataGeneratorStepsHelper.js`
|
|
317
|
+
|
|
318
|
+
Track each generated entity with its generators and actorInfo for cleanup.
|
|
319
|
+
|
|
320
|
+
### 6. Modify: `src/common/data-generator/steps/DataGenerator.spec.js`
|
|
321
|
+
|
|
322
|
+
Pass `cleanupTracker` fixture to all 4 step definitions.
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Verification
|
|
327
|
+
|
|
328
|
+
1. `npm run build` to confirm compilation
|
|
329
|
+
2. Add `cleanup` config to generator JSONs (contact, ticket, webhook)
|
|
330
|
+
3. Run a scenario → verify cleanup logs show correct operation after scenario ends
|
|
331
|
+
4. Verify PATCH operations send the body (webhook disable)
|
|
332
|
+
5. Verify reverse order (ticket deleted before product)
|
package/README.md
CHANGED
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
- npm run report
|
|
19
19
|
|
|
20
|
+
### v0.0.44-n20-experimental - 23-03-2026
|
|
21
|
+
|
|
22
|
+
#### Enhancement
|
|
23
|
+
- DataGenerator now walks up the directory tree to discover data-generators folders
|
|
24
|
+
- Supports multiple JSON files in data-generators directory (no longer hardcoded to generators.json)
|
|
25
|
+
- Removed hardcoded folder name dependencies for flexible generator file placement
|
|
26
|
+
- Added duplicate generator name validation to `ZDTestingFramework validate` command
|
|
27
|
+
|
|
20
28
|
### v4.1.1/v3.3.0 - 28-01-2026
|
|
21
29
|
|
|
22
30
|
#### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test } from '@zohodesk/testinglibrary';
|
|
2
2
|
import DataGenerator from '@zohodesk/testinglibrary/DataGenerator';
|
|
3
|
-
|
|
3
|
+
import {getUserForSelectedEditionAndProfile, getListOfActors} from '@zohodesk/testinglibrary/helpers'
|
|
4
4
|
|
|
5
5
|
const dataGenerator = new DataGenerator();
|
|
6
6
|
|
|
@@ -10,12 +10,22 @@ export async function generateAndCacheTestData(executionContext, type, identifie
|
|
|
10
10
|
const scenarioName = testInfo.title.split('/').pop() || 'Unknown Scenario';
|
|
11
11
|
|
|
12
12
|
if (profile) {
|
|
13
|
+
// Explicit profile requested — resolve that profile's credentials
|
|
13
14
|
const { edition, orgName: portal, beta } = executionContext.actorInfo;
|
|
14
15
|
actorInfo = await getUserForSelectedEditionAndProfile(edition, profile, beta, portal);
|
|
15
16
|
} else {
|
|
17
|
+
// Default — use current actor, fall back to org-level data-generator if profile has none
|
|
16
18
|
actorInfo = executionContext.actorInfo;
|
|
19
|
+
if (!actorInfo['data-generator']) {
|
|
20
|
+
const { edition, orgName: portal, beta } = actorInfo;
|
|
21
|
+
const actorsData = getListOfActors(beta);
|
|
22
|
+
const portalData = actorsData.editions[edition]?.find(e => e.orgName === portal);
|
|
23
|
+
if (portalData?.['data-generator']) {
|
|
24
|
+
actorInfo = { ...actorInfo, 'data-generator': portalData['data-generator'] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
17
27
|
}
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
await cacheLayer.set(entityName,
|
|
28
|
+
|
|
29
|
+
const { response } = await dataGenerator.generate(testInfo, actorInfo, type, identifier, scenarioName, dataTable ? dataTable.hashes() : []);
|
|
30
|
+
await cacheLayer.set(entityName, response.data);
|
|
21
31
|
}
|
|
@@ -9,15 +9,22 @@ var _path = _interopRequireDefault(require("path"));
|
|
|
9
9
|
var _fs = _interopRequireDefault(require("fs"));
|
|
10
10
|
var _logger = require("../../utils/logger");
|
|
11
11
|
var _DataGeneratorHelper = require("./DataGeneratorHelper");
|
|
12
|
-
var _helpers = require("@zohodesk/testinglibrary/helpers");
|
|
13
12
|
var _DataGeneratorError = require("./DataGeneratorError");
|
|
13
|
+
var _readConfigFile = require("../playwright/readConfigFile");
|
|
14
|
+
var _configConstants = _interopRequireDefault(require("../playwright/constants/configConstants"));
|
|
15
|
+
var _ConfigurationHelper = require("../playwright/configuration/ConfigurationHelper");
|
|
14
16
|
function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
|
|
17
|
+
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
|
|
15
18
|
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
|
|
19
|
+
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
|
|
20
|
+
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
|
|
16
21
|
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
|
|
22
|
+
var _generatorIndex = /*#__PURE__*/new WeakMap();
|
|
17
23
|
var _DataGenerator_brand = /*#__PURE__*/new WeakSet();
|
|
18
24
|
class DataGenerator {
|
|
19
25
|
constructor() {
|
|
20
26
|
_classPrivateMethodInitSpec(this, _DataGenerator_brand);
|
|
27
|
+
_classPrivateFieldInitSpec(this, _generatorIndex, null);
|
|
21
28
|
}
|
|
22
29
|
async generate(testInfo, actorInfo, generatorType, generatorName, scenarioName, dataTable) {
|
|
23
30
|
try {
|
|
@@ -25,13 +32,16 @@ class DataGenerator {
|
|
|
25
32
|
if (generatorType === 'API') {
|
|
26
33
|
generators = await _assertClassBrand(_DataGenerator_brand, this, _generateAPIGenerator).call(this, generatorName);
|
|
27
34
|
} else {
|
|
28
|
-
generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this,
|
|
35
|
+
generators = await _assertClassBrand(_DataGenerator_brand, this, _getGenerator).call(this, generatorName);
|
|
29
36
|
}
|
|
30
37
|
const processedGenerators = await (0, _DataGeneratorHelper.processGenerator)(generators, dataTable);
|
|
31
38
|
const apiPayload = await _assertClassBrand(_DataGenerator_brand, this, _constructApiPayload).call(this, scenarioName, processedGenerators, actorInfo);
|
|
32
39
|
const response = await (0, _DataGeneratorHelper.makeRequest)(process.env.DG_SERVICE_DOMAIN + process.env.DG_SERVICE_API_PATH, apiPayload);
|
|
33
40
|
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Generated response for the generator: ${generatorName} for scenario: ${scenarioName}, Response: ${JSON.stringify(response)}`);
|
|
34
|
-
return
|
|
41
|
+
return {
|
|
42
|
+
response,
|
|
43
|
+
generators
|
|
44
|
+
};
|
|
35
45
|
} catch (error) {
|
|
36
46
|
if (error instanceof _DataGeneratorError.DataGeneratorError) {
|
|
37
47
|
console.error(error.getMessage());
|
|
@@ -48,21 +58,59 @@ class DataGenerator {
|
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
function _matchesPattern(filename, pattern) {
|
|
62
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
63
|
+
return regex.test(filename);
|
|
64
|
+
}
|
|
65
|
+
function _scanDir(dir, index, pattern) {
|
|
66
|
+
const entries = _fs.default.readdirSync(dir, {
|
|
67
|
+
withFileTypes: true
|
|
68
|
+
});
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = _path.default.join(dir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
_assertClassBrand(_DataGenerator_brand, this, _scanDir).call(this, fullPath, index, pattern);
|
|
73
|
+
} else if (_assertClassBrand(_DataGenerator_brand, this, _matchesPattern).call(this, entry.name, pattern)) {
|
|
74
|
+
try {
|
|
75
|
+
const data = _fs.default.readFileSync(fullPath, 'utf8');
|
|
76
|
+
const obj = JSON.parse(data);
|
|
77
|
+
if (obj.generators) {
|
|
78
|
+
for (const [name, config] of Object.entries(obj.generators)) {
|
|
79
|
+
if (!index.has(name)) {
|
|
80
|
+
index.set(name, config);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, `Failed to parse generator file: ${fullPath} - ${err.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function _buildIndex(modulesRoot, pattern) {
|
|
91
|
+
const index = new Map();
|
|
92
|
+
_assertClassBrand(_DataGenerator_brand, this, _scanDir).call(this, modulesRoot, index, pattern);
|
|
93
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Generator index built: ${index.size} generators found`);
|
|
94
|
+
return index;
|
|
95
|
+
}
|
|
96
|
+
function _getModulesRoot() {
|
|
97
|
+
const stage = (0, _ConfigurationHelper.getRunStage)();
|
|
98
|
+
return _path.default.join(process.cwd(), _configConstants.default.TEST_SLICE_FOLDER, stage, 'modules');
|
|
99
|
+
}
|
|
100
|
+
async function _getGenerator(generatorName) {
|
|
101
|
+
if (!_classPrivateFieldGet(_generatorIndex, this)) {
|
|
102
|
+
const modulesRoot = _assertClassBrand(_DataGenerator_brand, this, _getModulesRoot).call(this);
|
|
103
|
+
const {
|
|
104
|
+
generatorFilePattern: pattern = '*.generators.json'
|
|
105
|
+
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
106
|
+
if (modulesRoot) {
|
|
107
|
+
_classPrivateFieldSet(_generatorIndex, this, _assertClassBrand(_DataGenerator_brand, this, _buildIndex).call(this, modulesRoot, pattern));
|
|
60
108
|
}
|
|
61
109
|
}
|
|
62
|
-
if (
|
|
63
|
-
|
|
110
|
+
if (_classPrivateFieldGet(_generatorIndex, this) && _classPrivateFieldGet(_generatorIndex, this).has(generatorName)) {
|
|
111
|
+
return _classPrivateFieldGet(_generatorIndex, this).get(generatorName);
|
|
64
112
|
}
|
|
65
|
-
|
|
113
|
+
throw new _DataGeneratorError.GeneratorError(`Generator "${generatorName}" could not be found in any generator file`);
|
|
66
114
|
}
|
|
67
115
|
async function _generateAPIGenerator(operationId) {
|
|
68
116
|
return [{
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.validateGenerators = validateGenerators;
|
|
8
|
+
var _path = _interopRequireDefault(require("path"));
|
|
9
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
10
|
+
var _logger = require("../../utils/logger");
|
|
11
|
+
var _readConfigFile = require("../playwright/readConfigFile");
|
|
12
|
+
function matchesPattern(filename, pattern) {
|
|
13
|
+
const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
14
|
+
return regex.test(filename);
|
|
15
|
+
}
|
|
16
|
+
function findGeneratorFiles(dir, pattern, results = []) {
|
|
17
|
+
const entries = _fs.default.readdirSync(dir, {
|
|
18
|
+
withFileTypes: true
|
|
19
|
+
});
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
const fullPath = _path.default.join(dir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
findGeneratorFiles(fullPath, pattern, results);
|
|
24
|
+
} else if (matchesPattern(entry.name, pattern)) {
|
|
25
|
+
results.push(fullPath);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
function validateGenerators(modulesRoot) {
|
|
31
|
+
if (!_fs.default.existsSync(modulesRoot)) {
|
|
32
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, `Modules directory not found: ${modulesRoot}. Skipping generator validation.`);
|
|
33
|
+
return {
|
|
34
|
+
valid: true,
|
|
35
|
+
duplicates: []
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const {
|
|
39
|
+
generatorFilePattern: pattern = '*.generators.json'
|
|
40
|
+
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
41
|
+
const generatorMap = {};
|
|
42
|
+
const generatorFiles = findGeneratorFiles(modulesRoot, pattern);
|
|
43
|
+
for (const filePath of generatorFiles) {
|
|
44
|
+
try {
|
|
45
|
+
const data = _fs.default.readFileSync(filePath, 'utf8');
|
|
46
|
+
const generatorObj = JSON.parse(data);
|
|
47
|
+
if (generatorObj.generators) {
|
|
48
|
+
for (const name of Object.keys(generatorObj.generators)) {
|
|
49
|
+
if (!generatorMap[name]) {
|
|
50
|
+
generatorMap[name] = [];
|
|
51
|
+
}
|
|
52
|
+
generatorMap[name].push(filePath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, `Failed to parse generator file: ${filePath} - ${err.message}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const duplicates = Object.entries(generatorMap).filter(([, files]) => files.length > 1).map(([name, files]) => ({
|
|
60
|
+
name,
|
|
61
|
+
files
|
|
62
|
+
}));
|
|
63
|
+
if (duplicates.length > 0) {
|
|
64
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, 'Duplicate generator names found:');
|
|
65
|
+
for (const dup of duplicates) {
|
|
66
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, ` Generator "${dup.name}" defined in:`);
|
|
67
|
+
for (const file of dup.files) {
|
|
68
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, ` - ${file}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
valid: false,
|
|
73
|
+
duplicates
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const generatorCount = Object.keys(generatorMap).length;
|
|
77
|
+
_logger.Logger.log(_logger.Logger.SUCCESS_TYPE, `Generator validation passed. ${generatorCount} unique generators found across ${generatorFiles.length} generator files.`);
|
|
78
|
+
return {
|
|
79
|
+
valid: true,
|
|
80
|
+
duplicates: []
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -11,6 +11,5 @@ const stage = (0, _ConfigurationHelper.getRunStage)();
|
|
|
11
11
|
class ReporterConstants {
|
|
12
12
|
static DEFAULT_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/playwright-test-results.json`;
|
|
13
13
|
static LAST_RUN_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/${stage}/test-results/.last-run.json`;
|
|
14
|
-
static DEFAULT_UNIT_TEST_REPORTER_PATH = `${_configConstants.default.TEST_SLICE_FOLDER}/unit-test/unit_reports/report.html`;
|
|
15
14
|
}
|
|
16
15
|
exports.default = ReporterConstants;
|
|
@@ -55,7 +55,8 @@ function getDefaultConfig() {
|
|
|
55
55
|
featureFilesFolder: 'feature-files',
|
|
56
56
|
stepDefinitionsFolder: 'steps',
|
|
57
57
|
testSetup: {},
|
|
58
|
-
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise']
|
|
58
|
+
editionOrder: ['Free', 'Express', 'Standard', 'Professional', 'Enterprise'],
|
|
59
|
+
generatorFilePattern: '*.generators.json'
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
function combineDefaultConfigWithUserConfig(userConfiguration) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = generateReport;
|
|
8
|
+
var _child_process = require("child_process");
|
|
9
|
+
var _path = _interopRequireDefault(require("path"));
|
|
10
|
+
var _logger = require("../../utils/logger");
|
|
11
|
+
var _rootPath = require("../../utils/rootPath");
|
|
12
|
+
var _readConfigFile = require("./readConfigFile");
|
|
13
|
+
async function generateReport() {
|
|
14
|
+
// await preProcessReport()
|
|
15
|
+
const userArgs = process.argv.slice(3);
|
|
16
|
+
const playwrightPath = _path.default.resolve((0, _rootPath.getExecutableBinaryPath)('playwright'));
|
|
17
|
+
const command = playwrightPath;
|
|
18
|
+
const {
|
|
19
|
+
reportPath: htmlPath
|
|
20
|
+
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
21
|
+
const args = ['show-report', htmlPath].concat(userArgs);
|
|
22
|
+
const childProcess = (0, _child_process.spawn)(command, args, {
|
|
23
|
+
stdio: 'inherit'
|
|
24
|
+
});
|
|
25
|
+
childProcess.on('error', error => {
|
|
26
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, error);
|
|
27
|
+
});
|
|
28
|
+
childProcess.on('exit', (code, signal) => {
|
|
29
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, `Child Process Exited with Code ${code} and Signal ${signal}`);
|
|
30
|
+
process.exit();
|
|
31
|
+
});
|
|
32
|
+
process.on('exit', () => {
|
|
33
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, 'Terminating Playwright Process...');
|
|
34
|
+
childProcess.kill();
|
|
35
|
+
return;
|
|
36
|
+
});
|
|
37
|
+
process.on('SIGINT', () => {
|
|
38
|
+
_logger.Logger.log(_logger.Logger.INFO_TYPE, 'Cleaning up...');
|
|
39
|
+
childProcess.kill();
|
|
40
|
+
process.exit();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -5,11 +5,15 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
|
+
var _path = _interopRequireDefault(require("path"));
|
|
8
9
|
var _parseUserArgs = _interopRequireDefault(require("./helpers/parseUserArgs"));
|
|
9
10
|
var _readConfigFile = require("./readConfigFile");
|
|
10
11
|
var _tagProcessor = _interopRequireDefault(require("./tagProcessor"));
|
|
11
12
|
var _testRunner = require("./test-runner");
|
|
12
13
|
var _logger = require("../../utils/logger");
|
|
14
|
+
var _validateGenerators = require("../dataGenerator/validateGenerators");
|
|
15
|
+
var _configConstants = _interopRequireDefault(require("./constants/configConstants"));
|
|
16
|
+
var _ConfigurationHelper = require("./configuration/ConfigurationHelper");
|
|
13
17
|
const validateFeatureFiles = () => {
|
|
14
18
|
const userArgsObject = (0, _parseUserArgs.default)();
|
|
15
19
|
const uatConfig = (0, _readConfigFile.generateConfigFromFile)();
|
|
@@ -17,6 +21,13 @@ const validateFeatureFiles = () => {
|
|
|
17
21
|
editionOrder
|
|
18
22
|
} = uatConfig;
|
|
19
23
|
const configPath = (0, _readConfigFile.isUserConfigFileAvailable)() ? require.resolve('./setup/config-creator.js') : require.resolve('../../../playwright.config.js');
|
|
24
|
+
const stage = (0, _ConfigurationHelper.getRunStage)();
|
|
25
|
+
const modulesRoot = _path.default.join(process.cwd(), _configConstants.default.TEST_SLICE_FOLDER, stage, 'modules');
|
|
26
|
+
const generatorResult = (0, _validateGenerators.validateGenerators)(modulesRoot);
|
|
27
|
+
if (!generatorResult.valid) {
|
|
28
|
+
_logger.Logger.log(_logger.Logger.FAILURE_TYPE, 'Generator validation failed. Fix duplicate generator names before running tests.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
20
31
|
const tagProcessor = new _tagProcessor.default(editionOrder);
|
|
21
32
|
const tagArgs = tagProcessor.processTags(userArgsObject);
|
|
22
33
|
(0, _testRunner.runPreprocessing)(tagArgs, configPath).then(() => {
|