@testsmith/testblocks 0.8.0 → 0.8.2
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/dist/cli/executor.d.ts +2 -2
- package/dist/cli/executor.js +29 -5
- package/dist/cli/index.js +107 -6
- package/package.json +1 -1
package/dist/cli/executor.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export declare class TestExecutor {
|
|
|
20
20
|
private requiresBrowser;
|
|
21
21
|
runTestFile(testFile: TestFile): Promise<TestResult[]>;
|
|
22
22
|
private createBaseContext;
|
|
23
|
-
runTestWithData(test: TestCase, testFile: TestFile, dataSet: TestDataSet, dataIndex: number): Promise<TestResult>;
|
|
24
|
-
runTest(test: TestCase, testFile: TestFile): Promise<TestResult>;
|
|
23
|
+
runTestWithData(test: TestCase, testFile: TestFile, dataSet: TestDataSet, dataIndex: number, sharedVariables?: Map<string, unknown>): Promise<TestResult>;
|
|
24
|
+
runTest(test: TestCase, testFile: TestFile, sharedVariables?: Map<string, unknown>): Promise<TestResult>;
|
|
25
25
|
private runSteps;
|
|
26
26
|
private resolveVariableDefaults;
|
|
27
27
|
private registerCustomBlocksFromProcedures;
|
package/dist/cli/executor.js
CHANGED
|
@@ -99,20 +99,20 @@ class TestExecutor {
|
|
|
99
99
|
const steps = this.extractStepsFromBlocklyState(testFile.beforeAll);
|
|
100
100
|
await this.runSteps(steps, baseContext, 'beforeAll');
|
|
101
101
|
}
|
|
102
|
-
// Run each test
|
|
102
|
+
// Run each test - pass baseContext variables so beforeAll state persists
|
|
103
103
|
for (const test of testFile.tests) {
|
|
104
104
|
// Check if test has data-driven sets
|
|
105
105
|
if (test.data && test.data.length > 0) {
|
|
106
106
|
// Run test for each data set
|
|
107
107
|
for (let i = 0; i < test.data.length; i++) {
|
|
108
108
|
const dataSet = test.data[i];
|
|
109
|
-
const result = await this.runTestWithData(test, testFile, dataSet, i);
|
|
109
|
+
const result = await this.runTestWithData(test, testFile, dataSet, i, baseContext.variables);
|
|
110
110
|
results.push(result);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
114
114
|
// Run test once without data
|
|
115
|
-
const result = await this.runTest(test, testFile);
|
|
115
|
+
const result = await this.runTest(test, testFile, baseContext.variables);
|
|
116
116
|
results.push(result);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
@@ -141,7 +141,7 @@ class TestExecutor {
|
|
|
141
141
|
procedures: this.procedures,
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
-
async runTestWithData(test, testFile, dataSet, dataIndex) {
|
|
144
|
+
async runTestWithData(test, testFile, dataSet, dataIndex, sharedVariables) {
|
|
145
145
|
const testName = dataSet.name
|
|
146
146
|
? `${test.name} [${dataSet.name}]`
|
|
147
147
|
: `${test.name} [${dataIndex + 1}]`;
|
|
@@ -155,6 +155,17 @@ class TestExecutor {
|
|
|
155
155
|
currentData: dataSet,
|
|
156
156
|
dataIndex,
|
|
157
157
|
};
|
|
158
|
+
// Merge shared variables from beforeAll (if any)
|
|
159
|
+
if (sharedVariables) {
|
|
160
|
+
for (const [key, value] of sharedVariables) {
|
|
161
|
+
if (!context.variables.has(key) || context.variables.get(key) === '' || context.variables.get(key) === undefined) {
|
|
162
|
+
context.variables.set(key, value);
|
|
163
|
+
}
|
|
164
|
+
else if (key.startsWith('__')) {
|
|
165
|
+
context.variables.set(key, value);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
158
169
|
// Inject data values into variables
|
|
159
170
|
for (const [key, value] of Object.entries(dataSet.values)) {
|
|
160
171
|
context.variables.set(key, value);
|
|
@@ -239,12 +250,25 @@ class TestExecutor {
|
|
|
239
250
|
},
|
|
240
251
|
};
|
|
241
252
|
}
|
|
242
|
-
async runTest(test, testFile) {
|
|
253
|
+
async runTest(test, testFile, sharedVariables) {
|
|
243
254
|
console.log(` Running: ${test.name}`);
|
|
244
255
|
const startedAt = new Date().toISOString();
|
|
245
256
|
const startTime = Date.now();
|
|
246
257
|
const stepResults = [];
|
|
247
258
|
const context = this.createBaseContext(testFile.variables);
|
|
259
|
+
// Merge shared variables from beforeAll (if any)
|
|
260
|
+
if (sharedVariables) {
|
|
261
|
+
for (const [key, value] of sharedVariables) {
|
|
262
|
+
// Only copy if not already set (don't override file-level defaults)
|
|
263
|
+
if (!context.variables.has(key) || context.variables.get(key) === '' || context.variables.get(key) === undefined) {
|
|
264
|
+
context.variables.set(key, value);
|
|
265
|
+
}
|
|
266
|
+
else if (key.startsWith('__')) {
|
|
267
|
+
// Always copy internal state variables like __requestHeaders
|
|
268
|
+
context.variables.set(key, value);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
248
272
|
for (const plugin of this.plugins.values()) {
|
|
249
273
|
if (plugin.hooks?.beforeTest) {
|
|
250
274
|
await plugin.hooks.beforeTest(context, test);
|
package/dist/cli/index.js
CHANGED
|
@@ -97,6 +97,75 @@ function findPluginsDir(startDir) {
|
|
|
97
97
|
}
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Load folder hooks from a test file's directory up to the root globals directory
|
|
102
|
+
* Returns hooks in order from outermost folder to innermost
|
|
103
|
+
*/
|
|
104
|
+
function loadFolderHooks(testFilePath, globalsDir) {
|
|
105
|
+
let currentDir = path.dirname(path.resolve(testFilePath));
|
|
106
|
+
const stopDir = globalsDir ? path.resolve(globalsDir) : null;
|
|
107
|
+
// Collect hooks from innermost to outermost
|
|
108
|
+
const collectedHooks = [];
|
|
109
|
+
while (currentDir) {
|
|
110
|
+
const hooksPath = path.join(currentDir, '_hooks.testblocks.json');
|
|
111
|
+
if (fs.existsSync(hooksPath)) {
|
|
112
|
+
try {
|
|
113
|
+
const content = fs.readFileSync(hooksPath, 'utf-8');
|
|
114
|
+
const hooksFile = JSON.parse(content);
|
|
115
|
+
collectedHooks.push(hooksFile);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
console.warn(`Warning: Could not load hooks from ${hooksPath}: ${e.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Stop at globals directory or root
|
|
122
|
+
if (stopDir && currentDir === stopDir)
|
|
123
|
+
break;
|
|
124
|
+
const parentDir = path.dirname(currentDir);
|
|
125
|
+
if (parentDir === currentDir)
|
|
126
|
+
break; // Reached root
|
|
127
|
+
currentDir = parentDir;
|
|
128
|
+
}
|
|
129
|
+
// Reverse to get outermost-to-innermost order
|
|
130
|
+
return collectedHooks.reverse();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Merge folder hooks into a test file
|
|
134
|
+
* - beforeAll/beforeEach: parent hooks run first
|
|
135
|
+
* - afterAll/afterEach: child hooks run first (reverse order)
|
|
136
|
+
*/
|
|
137
|
+
function mergeFolderHooksIntoTestFile(testFile, folderHooks) {
|
|
138
|
+
if (!folderHooks || folderHooks.length === 0) {
|
|
139
|
+
return testFile;
|
|
140
|
+
}
|
|
141
|
+
const beforeAllSteps = [];
|
|
142
|
+
const afterAllSteps = [];
|
|
143
|
+
const beforeEachSteps = [];
|
|
144
|
+
const afterEachSteps = [];
|
|
145
|
+
// Parent to child order for beforeAll/beforeEach
|
|
146
|
+
for (const hooks of folderHooks) {
|
|
147
|
+
if (hooks.beforeAll)
|
|
148
|
+
beforeAllSteps.push(...hooks.beforeAll);
|
|
149
|
+
if (hooks.beforeEach)
|
|
150
|
+
beforeEachSteps.push(...hooks.beforeEach);
|
|
151
|
+
}
|
|
152
|
+
// Child to parent order for afterAll/afterEach
|
|
153
|
+
for (let i = folderHooks.length - 1; i >= 0; i--) {
|
|
154
|
+
const hooks = folderHooks[i];
|
|
155
|
+
if (hooks.afterAll)
|
|
156
|
+
afterAllSteps.unshift(...hooks.afterAll);
|
|
157
|
+
if (hooks.afterEach)
|
|
158
|
+
afterEachSteps.unshift(...hooks.afterEach);
|
|
159
|
+
}
|
|
160
|
+
// Merge with test file hooks
|
|
161
|
+
return {
|
|
162
|
+
...testFile,
|
|
163
|
+
beforeAll: [...beforeAllSteps, ...(testFile.beforeAll || [])],
|
|
164
|
+
afterAll: [...(testFile.afterAll || []), ...afterAllSteps],
|
|
165
|
+
beforeEach: [...beforeEachSteps, ...(testFile.beforeEach || [])],
|
|
166
|
+
afterEach: [...(testFile.afterEach || []), ...afterEachSteps],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
100
169
|
const program = new commander_1.Command();
|
|
101
170
|
program
|
|
102
171
|
.name('testblocks')
|
|
@@ -212,9 +281,25 @@ program
|
|
|
212
281
|
const allResults = [];
|
|
213
282
|
let hasFailures = false;
|
|
214
283
|
for (const file of files) {
|
|
215
|
-
|
|
284
|
+
// Skip _hooks.testblocks.json files - these are folder hooks, not test files
|
|
285
|
+
const basename = path.basename(file);
|
|
286
|
+
if (basename === '_hooks.testblocks.json') {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
console.log(`Running: ${basename}`);
|
|
216
290
|
const content = fs.readFileSync(file, 'utf-8');
|
|
217
|
-
|
|
291
|
+
let testFile = JSON.parse(content);
|
|
292
|
+
// Skip files that have no tests array (e.g., hooks-only files)
|
|
293
|
+
if (!testFile.tests || !Array.isArray(testFile.tests)) {
|
|
294
|
+
console.log(' (no tests in file)\n');
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
// Load and merge folder hooks
|
|
298
|
+
const globalsDir = fs.existsSync(globalsPath) ? path.dirname(globalsPath) : null;
|
|
299
|
+
const folderHooks = loadFolderHooks(file, globalsDir);
|
|
300
|
+
if (folderHooks.length > 0) {
|
|
301
|
+
testFile = mergeFolderHooksIntoTestFile(testFile, folderHooks);
|
|
302
|
+
}
|
|
218
303
|
// Apply filter if specified
|
|
219
304
|
if (options.filter) {
|
|
220
305
|
const filterRegex = new RegExp(options.filter, 'i');
|
|
@@ -266,7 +351,13 @@ program
|
|
|
266
351
|
}
|
|
267
352
|
let hasErrors = false;
|
|
268
353
|
for (const file of files) {
|
|
269
|
-
|
|
354
|
+
const basename = path.basename(file);
|
|
355
|
+
// Skip _hooks.testblocks.json files from validation (they're hooks, not test files)
|
|
356
|
+
if (basename === '_hooks.testblocks.json') {
|
|
357
|
+
console.log(`Skipping: ${basename} (folder hooks file)`);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
console.log(`Validating: ${basename}`);
|
|
270
361
|
try {
|
|
271
362
|
const content = fs.readFileSync(file, 'utf-8');
|
|
272
363
|
const testFile = JSON.parse(content);
|
|
@@ -277,7 +368,8 @@ program
|
|
|
277
368
|
errors.forEach(err => console.log(` - ${err}`));
|
|
278
369
|
}
|
|
279
370
|
else {
|
|
280
|
-
|
|
371
|
+
const testCount = testFile.tests?.length || 0;
|
|
372
|
+
console.log(` ✓ Valid (${testCount} tests)`);
|
|
281
373
|
}
|
|
282
374
|
}
|
|
283
375
|
catch (error) {
|
|
@@ -342,7 +434,7 @@ program
|
|
|
342
434
|
'test:junit': 'testblocks run tests/**/*.testblocks.json -r junit -o reports',
|
|
343
435
|
},
|
|
344
436
|
devDependencies: {
|
|
345
|
-
'@testsmith/testblocks': '^0.8.
|
|
437
|
+
'@testsmith/testblocks': '^0.8.2',
|
|
346
438
|
},
|
|
347
439
|
};
|
|
348
440
|
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
|
|
@@ -522,9 +614,18 @@ program
|
|
|
522
614
|
process.exit(1);
|
|
523
615
|
}
|
|
524
616
|
for (const file of files) {
|
|
525
|
-
|
|
617
|
+
// Skip _hooks.testblocks.json files
|
|
618
|
+
const basename = path.basename(file);
|
|
619
|
+
if (basename === '_hooks.testblocks.json') {
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
console.log(`\n${basename}:`);
|
|
526
623
|
const content = fs.readFileSync(file, 'utf-8');
|
|
527
624
|
const testFile = JSON.parse(content);
|
|
625
|
+
if (!testFile.tests || !Array.isArray(testFile.tests)) {
|
|
626
|
+
console.log(' (no tests in file)');
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
528
629
|
testFile.tests.forEach((test, index) => {
|
|
529
630
|
const tags = test.tags?.length ? ` [${test.tags.join(', ')}]` : '';
|
|
530
631
|
console.log(` ${index + 1}. ${test.name}${tags}`);
|