testblocks 0.1.0
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/LICENSE +21 -0
- package/README.md +333 -0
- package/dist/cli/executor.d.ts +32 -0
- package/dist/cli/executor.js +517 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +411 -0
- package/dist/cli/reporters.d.ts +62 -0
- package/dist/cli/reporters.js +451 -0
- package/dist/client/assets/index-4hbFPUhP.js +2087 -0
- package/dist/client/assets/index-4hbFPUhP.js.map +1 -0
- package/dist/client/assets/index-Dnk1ti7l.css +1 -0
- package/dist/client/index.html +25 -0
- package/dist/core/blocks/api.d.ts +2 -0
- package/dist/core/blocks/api.js +610 -0
- package/dist/core/blocks/data-driven.d.ts +2 -0
- package/dist/core/blocks/data-driven.js +245 -0
- package/dist/core/blocks/index.d.ts +15 -0
- package/dist/core/blocks/index.js +71 -0
- package/dist/core/blocks/lifecycle.d.ts +2 -0
- package/dist/core/blocks/lifecycle.js +199 -0
- package/dist/core/blocks/logic.d.ts +2 -0
- package/dist/core/blocks/logic.js +357 -0
- package/dist/core/blocks/playwright.d.ts +2 -0
- package/dist/core/blocks/playwright.js +764 -0
- package/dist/core/blocks/procedures.d.ts +5 -0
- package/dist/core/blocks/procedures.js +321 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +44 -0
- package/dist/core/plugins.d.ts +66 -0
- package/dist/core/plugins.js +118 -0
- package/dist/core/types.d.ts +153 -0
- package/dist/core/types.js +2 -0
- package/dist/server/codegenManager.d.ts +54 -0
- package/dist/server/codegenManager.js +259 -0
- package/dist/server/codegenParser.d.ts +17 -0
- package/dist/server/codegenParser.js +598 -0
- package/dist/server/executor.d.ts +37 -0
- package/dist/server/executor.js +672 -0
- package/dist/server/globals.d.ts +85 -0
- package/dist/server/globals.js +273 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +361 -0
- package/dist/server/plugins.d.ts +55 -0
- package/dist/server/plugins.js +206 -0
- package/package.json +103 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TestExecutor = void 0;
|
|
4
|
+
const playwright_1 = require("playwright");
|
|
5
|
+
const core_1 = require("../core");
|
|
6
|
+
class TestExecutor {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.browser = null;
|
|
9
|
+
this.browserContext = null;
|
|
10
|
+
this.page = null;
|
|
11
|
+
this.plugins = new Map();
|
|
12
|
+
this.procedures = new Map();
|
|
13
|
+
this.options = {
|
|
14
|
+
headless: true,
|
|
15
|
+
timeout: 30000,
|
|
16
|
+
...options,
|
|
17
|
+
};
|
|
18
|
+
if (options.plugins) {
|
|
19
|
+
options.plugins.forEach(plugin => {
|
|
20
|
+
this.plugins.set(plugin.name, plugin);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async initialize() {
|
|
25
|
+
this.browser = await playwright_1.chromium.launch({
|
|
26
|
+
headless: this.options.headless,
|
|
27
|
+
});
|
|
28
|
+
this.browserContext = await this.browser.newContext();
|
|
29
|
+
this.page = await this.browserContext.newPage();
|
|
30
|
+
if (this.options.timeout) {
|
|
31
|
+
this.page.setDefaultTimeout(this.options.timeout);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async cleanup() {
|
|
35
|
+
if (this.page)
|
|
36
|
+
await this.page.close();
|
|
37
|
+
if (this.browserContext)
|
|
38
|
+
await this.browserContext.close();
|
|
39
|
+
if (this.browser)
|
|
40
|
+
await this.browser.close();
|
|
41
|
+
this.page = null;
|
|
42
|
+
this.browserContext = null;
|
|
43
|
+
this.browser = null;
|
|
44
|
+
}
|
|
45
|
+
async runTestFile(testFile) {
|
|
46
|
+
const results = [];
|
|
47
|
+
await this.initialize();
|
|
48
|
+
// Register procedures from the test file
|
|
49
|
+
if (testFile.procedures) {
|
|
50
|
+
for (const [name, procedure] of Object.entries(testFile.procedures)) {
|
|
51
|
+
this.procedures.set(name, procedure);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Create base context for hooks
|
|
55
|
+
const baseContext = this.createBaseContext(testFile.variables);
|
|
56
|
+
try {
|
|
57
|
+
// Run beforeAll hooks
|
|
58
|
+
if (testFile.beforeAll) {
|
|
59
|
+
const steps = this.extractStepsFromBlocklyState(testFile.beforeAll);
|
|
60
|
+
await this.runSteps(steps, baseContext, 'beforeAll');
|
|
61
|
+
}
|
|
62
|
+
// Run each test
|
|
63
|
+
for (const test of testFile.tests) {
|
|
64
|
+
// Check if test has data-driven sets
|
|
65
|
+
if (test.data && test.data.length > 0) {
|
|
66
|
+
// Run test for each data set
|
|
67
|
+
for (let i = 0; i < test.data.length; i++) {
|
|
68
|
+
const dataSet = test.data[i];
|
|
69
|
+
const result = await this.runTestWithData(test, testFile, dataSet, i);
|
|
70
|
+
results.push(result);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Run test once without data
|
|
75
|
+
const result = await this.runTest(test, testFile);
|
|
76
|
+
results.push(result);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Run afterAll hooks
|
|
80
|
+
if (testFile.afterAll) {
|
|
81
|
+
const steps = this.extractStepsFromBlocklyState(testFile.afterAll);
|
|
82
|
+
await this.runSteps(steps, baseContext, 'afterAll');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await this.cleanup();
|
|
87
|
+
}
|
|
88
|
+
return results;
|
|
89
|
+
}
|
|
90
|
+
createBaseContext(fileVariables) {
|
|
91
|
+
return {
|
|
92
|
+
variables: new Map(Object.entries({
|
|
93
|
+
...this.resolveVariableDefaults(fileVariables),
|
|
94
|
+
...this.options.variables,
|
|
95
|
+
})),
|
|
96
|
+
results: [],
|
|
97
|
+
browser: this.browser,
|
|
98
|
+
page: this.page,
|
|
99
|
+
logger: this.createLogger(),
|
|
100
|
+
plugins: this.plugins,
|
|
101
|
+
procedures: this.procedures,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async runTestWithData(test, testFile, dataSet, dataIndex) {
|
|
105
|
+
const testName = dataSet.name
|
|
106
|
+
? `${test.name} [${dataSet.name}]`
|
|
107
|
+
: `${test.name} [${dataIndex + 1}]`;
|
|
108
|
+
console.log(` Running: ${testName}`);
|
|
109
|
+
const startedAt = new Date().toISOString();
|
|
110
|
+
const startTime = Date.now();
|
|
111
|
+
const stepResults = [];
|
|
112
|
+
// Create context with data
|
|
113
|
+
const context = {
|
|
114
|
+
...this.createBaseContext(testFile.variables),
|
|
115
|
+
currentData: dataSet,
|
|
116
|
+
dataIndex,
|
|
117
|
+
};
|
|
118
|
+
// Inject data values into variables
|
|
119
|
+
for (const [key, value] of Object.entries(dataSet.values)) {
|
|
120
|
+
context.variables.set(key, value);
|
|
121
|
+
}
|
|
122
|
+
let testStatus = 'passed';
|
|
123
|
+
let testError;
|
|
124
|
+
try {
|
|
125
|
+
// Run suite-level beforeEach
|
|
126
|
+
if (testFile.beforeEach) {
|
|
127
|
+
const steps = this.extractStepsFromBlocklyState(testFile.beforeEach);
|
|
128
|
+
const hookResults = await this.runSteps(steps, context, 'beforeEach');
|
|
129
|
+
stepResults.push(...hookResults);
|
|
130
|
+
}
|
|
131
|
+
// Run test-level beforeEach
|
|
132
|
+
if (test.beforeEach) {
|
|
133
|
+
const steps = this.extractStepsFromBlocklyState(test.beforeEach);
|
|
134
|
+
const hookResults = await this.runSteps(steps, context, 'test.beforeEach');
|
|
135
|
+
stepResults.push(...hookResults);
|
|
136
|
+
}
|
|
137
|
+
// Run test steps
|
|
138
|
+
const steps = this.extractStepsFromBlocklyState(test.steps);
|
|
139
|
+
for (const step of steps) {
|
|
140
|
+
const stepResult = await this.runStep(step, context);
|
|
141
|
+
stepResults.push(stepResult);
|
|
142
|
+
if (stepResult.status === 'failed' || stepResult.status === 'error') {
|
|
143
|
+
testStatus = stepResult.status;
|
|
144
|
+
testError = stepResult.error;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (typeof error === 'object' && error !== null && 'skip' in error) {
|
|
151
|
+
testStatus = 'skipped';
|
|
152
|
+
testError = { message: error.reason || 'Skipped' };
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
testStatus = 'error';
|
|
156
|
+
testError = {
|
|
157
|
+
message: error.message,
|
|
158
|
+
stack: error.stack,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
// Run test-level afterEach
|
|
164
|
+
if (test.afterEach) {
|
|
165
|
+
try {
|
|
166
|
+
const steps = this.extractStepsFromBlocklyState(test.afterEach);
|
|
167
|
+
const hookResults = await this.runSteps(steps, context, 'test.afterEach');
|
|
168
|
+
stepResults.push(...hookResults);
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
console.error('Error in test.afterEach:', e);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Run suite-level afterEach
|
|
175
|
+
if (testFile.afterEach) {
|
|
176
|
+
try {
|
|
177
|
+
const steps = this.extractStepsFromBlocklyState(testFile.afterEach);
|
|
178
|
+
const hookResults = await this.runSteps(steps, context, 'afterEach');
|
|
179
|
+
stepResults.push(...hookResults);
|
|
180
|
+
}
|
|
181
|
+
catch (e) {
|
|
182
|
+
console.error('Error in afterEach:', e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
testId: test.id,
|
|
188
|
+
testName,
|
|
189
|
+
status: testStatus,
|
|
190
|
+
duration: Date.now() - startTime,
|
|
191
|
+
steps: stepResults,
|
|
192
|
+
error: testError,
|
|
193
|
+
startedAt,
|
|
194
|
+
finishedAt: new Date().toISOString(),
|
|
195
|
+
dataIteration: {
|
|
196
|
+
index: dataIndex,
|
|
197
|
+
name: dataSet.name,
|
|
198
|
+
data: dataSet.values,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async runTest(test, testFile) {
|
|
203
|
+
console.log(` Running: ${test.name}`);
|
|
204
|
+
const startedAt = new Date().toISOString();
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const stepResults = [];
|
|
207
|
+
const context = this.createBaseContext(testFile.variables);
|
|
208
|
+
for (const plugin of this.plugins.values()) {
|
|
209
|
+
if (plugin.hooks?.beforeTest) {
|
|
210
|
+
await plugin.hooks.beforeTest(context, test);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
let testStatus = 'passed';
|
|
214
|
+
let testError;
|
|
215
|
+
try {
|
|
216
|
+
// Run suite-level beforeEach
|
|
217
|
+
if (testFile.beforeEach) {
|
|
218
|
+
const steps = this.extractStepsFromBlocklyState(testFile.beforeEach);
|
|
219
|
+
const hookResults = await this.runSteps(steps, context, 'beforeEach');
|
|
220
|
+
stepResults.push(...hookResults);
|
|
221
|
+
}
|
|
222
|
+
// Run test-level beforeEach
|
|
223
|
+
if (test.beforeEach) {
|
|
224
|
+
const steps = this.extractStepsFromBlocklyState(test.beforeEach);
|
|
225
|
+
const hookResults = await this.runSteps(steps, context, 'test.beforeEach');
|
|
226
|
+
stepResults.push(...hookResults);
|
|
227
|
+
}
|
|
228
|
+
// Run test steps
|
|
229
|
+
const steps = this.extractStepsFromBlocklyState(test.steps);
|
|
230
|
+
for (const step of steps) {
|
|
231
|
+
const stepResult = await this.runStep(step, context);
|
|
232
|
+
stepResults.push(stepResult);
|
|
233
|
+
if (stepResult.status === 'failed' || stepResult.status === 'error') {
|
|
234
|
+
testStatus = stepResult.status;
|
|
235
|
+
testError = stepResult.error;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (typeof error === 'object' && error !== null && 'skip' in error) {
|
|
242
|
+
testStatus = 'skipped';
|
|
243
|
+
testError = { message: error.reason || 'Skipped' };
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
testStatus = 'error';
|
|
247
|
+
testError = {
|
|
248
|
+
message: error.message,
|
|
249
|
+
stack: error.stack,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
// Run test-level afterEach
|
|
255
|
+
if (test.afterEach) {
|
|
256
|
+
try {
|
|
257
|
+
const steps = this.extractStepsFromBlocklyState(test.afterEach);
|
|
258
|
+
const hookResults = await this.runSteps(steps, context, 'test.afterEach');
|
|
259
|
+
stepResults.push(...hookResults);
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.error('Error in test.afterEach:', e);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Run suite-level afterEach
|
|
266
|
+
if (testFile.afterEach) {
|
|
267
|
+
try {
|
|
268
|
+
const steps = this.extractStepsFromBlocklyState(testFile.afterEach);
|
|
269
|
+
const hookResults = await this.runSteps(steps, context, 'afterEach');
|
|
270
|
+
stepResults.push(...hookResults);
|
|
271
|
+
}
|
|
272
|
+
catch (e) {
|
|
273
|
+
console.error('Error in afterEach:', e);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const result = {
|
|
278
|
+
testId: test.id,
|
|
279
|
+
testName: test.name,
|
|
280
|
+
status: testStatus,
|
|
281
|
+
duration: Date.now() - startTime,
|
|
282
|
+
steps: stepResults,
|
|
283
|
+
error: testError,
|
|
284
|
+
startedAt,
|
|
285
|
+
finishedAt: new Date().toISOString(),
|
|
286
|
+
};
|
|
287
|
+
for (const plugin of this.plugins.values()) {
|
|
288
|
+
if (plugin.hooks?.afterTest) {
|
|
289
|
+
await plugin.hooks.afterTest(context, test, result);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
async runSteps(steps, context, phase) {
|
|
295
|
+
const results = [];
|
|
296
|
+
for (const step of steps) {
|
|
297
|
+
const result = await this.runStep(step, context);
|
|
298
|
+
results.push(result);
|
|
299
|
+
if (result.status === 'failed' || result.status === 'error') {
|
|
300
|
+
throw new Error(`${phase} failed: ${result.error?.message}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return results;
|
|
304
|
+
}
|
|
305
|
+
resolveVariableDefaults(vars) {
|
|
306
|
+
if (!vars)
|
|
307
|
+
return {};
|
|
308
|
+
const resolved = {};
|
|
309
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
310
|
+
if (value && typeof value === 'object' && 'default' in value) {
|
|
311
|
+
resolved[key] = value.default;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
resolved[key] = value;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return resolved;
|
|
318
|
+
}
|
|
319
|
+
extractStepsFromBlocklyState(state) {
|
|
320
|
+
if (!state || typeof state !== 'object')
|
|
321
|
+
return [];
|
|
322
|
+
const stateObj = state;
|
|
323
|
+
if ('blocks' in stateObj && typeof stateObj.blocks === 'object') {
|
|
324
|
+
const blocks = stateObj.blocks;
|
|
325
|
+
if ('blocks' in blocks && Array.isArray(blocks.blocks)) {
|
|
326
|
+
return this.blocksToSteps(blocks.blocks);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (Array.isArray(state)) {
|
|
330
|
+
return state;
|
|
331
|
+
}
|
|
332
|
+
return [];
|
|
333
|
+
}
|
|
334
|
+
blocksToSteps(blocks) {
|
|
335
|
+
const steps = [];
|
|
336
|
+
for (const block of blocks) {
|
|
337
|
+
const step = this.blockToStep(block);
|
|
338
|
+
if (step) {
|
|
339
|
+
steps.push(step);
|
|
340
|
+
let currentBlock = block;
|
|
341
|
+
while (currentBlock.next) {
|
|
342
|
+
const nextBlock = currentBlock.next.block;
|
|
343
|
+
const nextStep = this.blockToStep(nextBlock);
|
|
344
|
+
if (nextStep) {
|
|
345
|
+
steps.push(nextStep);
|
|
346
|
+
}
|
|
347
|
+
currentBlock = nextBlock;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return steps;
|
|
352
|
+
}
|
|
353
|
+
blockToStep(block) {
|
|
354
|
+
if (!block || !block.type)
|
|
355
|
+
return null;
|
|
356
|
+
const step = {
|
|
357
|
+
id: block.id || `step-${Date.now()}`,
|
|
358
|
+
type: block.type,
|
|
359
|
+
params: {},
|
|
360
|
+
};
|
|
361
|
+
if (block.fields && typeof block.fields === 'object') {
|
|
362
|
+
for (const [name, value] of Object.entries(block.fields)) {
|
|
363
|
+
step.params[name] = value;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (block.inputs && typeof block.inputs === 'object') {
|
|
367
|
+
for (const [name, input] of Object.entries(block.inputs)) {
|
|
368
|
+
const inputObj = input;
|
|
369
|
+
if (inputObj.block) {
|
|
370
|
+
const connectedStep = this.blockToStep(inputObj.block);
|
|
371
|
+
if (connectedStep) {
|
|
372
|
+
step.params[name] = connectedStep;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return step;
|
|
378
|
+
}
|
|
379
|
+
async runStep(step, context) {
|
|
380
|
+
const startTime = Date.now();
|
|
381
|
+
for (const plugin of this.plugins.values()) {
|
|
382
|
+
if (plugin.hooks?.beforeStep) {
|
|
383
|
+
await plugin.hooks.beforeStep(context, step);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
let status = 'passed';
|
|
387
|
+
let output;
|
|
388
|
+
let error;
|
|
389
|
+
try {
|
|
390
|
+
const blockDef = (0, core_1.getBlock)(step.type);
|
|
391
|
+
if (!blockDef) {
|
|
392
|
+
throw new Error(`Unknown block type: ${step.type}`);
|
|
393
|
+
}
|
|
394
|
+
const resolvedParams = await this.resolveParams(step.params, context);
|
|
395
|
+
output = await blockDef.execute(resolvedParams, context);
|
|
396
|
+
// Handle special return values for control flow
|
|
397
|
+
if (output && typeof output === 'object') {
|
|
398
|
+
const outputObj = output;
|
|
399
|
+
// Handle procedure calls
|
|
400
|
+
if (outputObj.procedureCall) {
|
|
401
|
+
output = await this.executeProcedure(outputObj.name, outputObj.args, context);
|
|
402
|
+
}
|
|
403
|
+
// Handle compound actions
|
|
404
|
+
if (outputObj.compoundAction && outputObj.steps) {
|
|
405
|
+
for (const subStep of outputObj.steps) {
|
|
406
|
+
const subResult = await this.runStep(subStep, context);
|
|
407
|
+
if (subResult.status !== 'passed') {
|
|
408
|
+
throw new Error(subResult.error?.message || 'Compound action step failed');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
// Handle retry logic
|
|
413
|
+
// TODO: Implement proper retry with nested statement execution
|
|
414
|
+
if (outputObj.retry && outputObj.statement) {
|
|
415
|
+
const _times = outputObj.times;
|
|
416
|
+
const _delay = outputObj.delay;
|
|
417
|
+
// Retry logic placeholder - full implementation would execute nested statements
|
|
418
|
+
console.log(`Retry block: ${_times} attempts with ${_delay}ms delay`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
// Handle skip
|
|
424
|
+
if (typeof err === 'object' && err !== null && 'skip' in err) {
|
|
425
|
+
throw err; // Re-throw skip to be handled by runTest
|
|
426
|
+
}
|
|
427
|
+
status = 'failed';
|
|
428
|
+
error = {
|
|
429
|
+
message: err.message,
|
|
430
|
+
stack: err.stack,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const result = {
|
|
434
|
+
stepId: step.id,
|
|
435
|
+
stepType: step.type,
|
|
436
|
+
status,
|
|
437
|
+
duration: Date.now() - startTime,
|
|
438
|
+
output,
|
|
439
|
+
error,
|
|
440
|
+
};
|
|
441
|
+
for (const plugin of this.plugins.values()) {
|
|
442
|
+
if (plugin.hooks?.afterStep) {
|
|
443
|
+
await plugin.hooks.afterStep(context, step, result);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
async executeProcedure(name, args, context) {
|
|
449
|
+
const procedure = this.procedures.get(name);
|
|
450
|
+
if (!procedure) {
|
|
451
|
+
throw new Error(`Procedure not found: ${name}`);
|
|
452
|
+
}
|
|
453
|
+
context.logger.info(` Executing procedure: ${name}`);
|
|
454
|
+
// Set up parameter variables with prefix
|
|
455
|
+
for (const [key, value] of Object.entries(args)) {
|
|
456
|
+
context.variables.set(`__param_${key}`, value);
|
|
457
|
+
}
|
|
458
|
+
// Execute procedure steps
|
|
459
|
+
const steps = this.extractStepsFromBlocklyState(procedure.steps);
|
|
460
|
+
let returnValue;
|
|
461
|
+
for (const step of steps) {
|
|
462
|
+
const result = await this.runStep(step, context);
|
|
463
|
+
if (result.status !== 'passed') {
|
|
464
|
+
throw new Error(`Procedure ${name} failed: ${result.error?.message}`);
|
|
465
|
+
}
|
|
466
|
+
// Check for return value
|
|
467
|
+
if (result.output && typeof result.output === 'object') {
|
|
468
|
+
const outputObj = result.output;
|
|
469
|
+
if (outputObj.procedureReturn) {
|
|
470
|
+
returnValue = outputObj.value;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Clean up parameter variables
|
|
476
|
+
for (const key of Object.keys(args)) {
|
|
477
|
+
context.variables.delete(`__param_${key}`);
|
|
478
|
+
}
|
|
479
|
+
return returnValue;
|
|
480
|
+
}
|
|
481
|
+
async resolveParams(params, context) {
|
|
482
|
+
const resolved = {};
|
|
483
|
+
for (const [key, value] of Object.entries(params)) {
|
|
484
|
+
if (value && typeof value === 'object' && 'type' in value) {
|
|
485
|
+
const nestedStep = value;
|
|
486
|
+
const blockDef = (0, core_1.getBlock)(nestedStep.type);
|
|
487
|
+
if (blockDef) {
|
|
488
|
+
const nestedParams = await this.resolveParams(nestedStep.params || {}, context);
|
|
489
|
+
resolved[key] = await blockDef.execute(nestedParams, context);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
resolved[key] = value;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return resolved;
|
|
497
|
+
}
|
|
498
|
+
createLogger() {
|
|
499
|
+
return {
|
|
500
|
+
info: (message, data) => {
|
|
501
|
+
console.log(` ${message}`, data !== undefined ? data : '');
|
|
502
|
+
},
|
|
503
|
+
warn: (message, data) => {
|
|
504
|
+
console.warn(` ⚠ ${message}`, data !== undefined ? data : '');
|
|
505
|
+
},
|
|
506
|
+
error: (message, data) => {
|
|
507
|
+
console.error(` ✗ ${message}`, data !== undefined ? data : '');
|
|
508
|
+
},
|
|
509
|
+
debug: (message, data) => {
|
|
510
|
+
if (process.env.DEBUG) {
|
|
511
|
+
console.debug(` [debug] ${message}`, data !== undefined ? data : '');
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
exports.TestExecutor = TestExecutor;
|