@apiquest/fracture 1.0.2 → 1.0.5
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/README.md +207 -0
- package/bin/cli.js +2 -2
- package/dist/CollectionRunner.d.ts +2 -0
- package/dist/CollectionRunner.d.ts.map +1 -1
- package/dist/CollectionRunner.js +23 -5
- package/dist/CollectionRunner.js.map +1 -1
- package/dist/LibraryLoader.d.ts +49 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +198 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PluginLoader.d.ts.map +1 -1
- package/dist/PluginLoader.js +9 -6
- package/dist/PluginLoader.js.map +1 -1
- package/dist/PluginResolver.d.ts +1 -1
- package/dist/PluginResolver.d.ts.map +1 -1
- package/dist/PluginResolver.js +1 -1
- package/dist/PluginResolver.js.map +1 -1
- package/dist/ScriptEngine.d.ts +2 -1
- package/dist/ScriptEngine.d.ts.map +1 -1
- package/dist/ScriptEngine.js +19 -12
- package/dist/ScriptEngine.js.map +1 -1
- package/dist/cli/index.js +35 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-commands.d.ts.map +1 -1
- package/dist/cli/plugin-commands.js +48 -81
- package/dist/cli/plugin-commands.js.map +1 -1
- package/dist/cli/plugin-installer.d.ts +48 -0
- package/dist/cli/plugin-installer.d.ts.map +1 -0
- package/dist/cli/plugin-installer.js +136 -0
- package/dist/cli/plugin-installer.js.map +1 -0
- package/dist/cli/plugin-registry.d.ts +17 -0
- package/dist/cli/plugin-registry.d.ts.map +1 -0
- package/dist/cli/plugin-registry.js +77 -0
- package/dist/cli/plugin-registry.js.map +1 -0
- package/package.json +55 -50
- package/tsconfig.json +20 -20
- package/tsconfig.test.json +5 -5
- package/vitest.config.ts +22 -22
- package/dist/ExecutionTree.d.ts +0 -77
- package/dist/ExecutionTree.d.ts.map +0 -1
- package/dist/ExecutionTree.js +0 -265
- package/dist/ExecutionTree.js.map +0 -1
- package/dist/fracture/src/CollectionAnalyzer.d.ts +0 -17
- package/dist/fracture/src/CollectionAnalyzer.d.ts.map +0 -1
- package/dist/fracture/src/CollectionAnalyzer.js +0 -70
- package/dist/fracture/src/CollectionAnalyzer.js.map +0 -1
- package/dist/fracture/src/CollectionRunner.d.ts +0 -39
- package/dist/fracture/src/CollectionRunner.d.ts.map +0 -1
- package/dist/fracture/src/CollectionRunner.js +0 -802
- package/dist/fracture/src/CollectionRunner.js.map +0 -1
- package/dist/fracture/src/CollectionRunner.types.d.ts +0 -8
- package/dist/fracture/src/CollectionRunner.types.d.ts.map +0 -1
- package/dist/fracture/src/CollectionRunner.types.js +0 -2
- package/dist/fracture/src/CollectionRunner.types.js.map +0 -1
- package/dist/fracture/src/CollectionValidator.d.ts +0 -14
- package/dist/fracture/src/CollectionValidator.d.ts.map +0 -1
- package/dist/fracture/src/CollectionValidator.js +0 -145
- package/dist/fracture/src/CollectionValidator.js.map +0 -1
- package/dist/fracture/src/ConsoleReporter.d.ts +0 -24
- package/dist/fracture/src/ConsoleReporter.d.ts.map +0 -1
- package/dist/fracture/src/ConsoleReporter.js +0 -123
- package/dist/fracture/src/ConsoleReporter.js.map +0 -1
- package/dist/fracture/src/CookieJar.d.ts +0 -70
- package/dist/fracture/src/CookieJar.d.ts.map +0 -1
- package/dist/fracture/src/CookieJar.js +0 -233
- package/dist/fracture/src/CookieJar.js.map +0 -1
- package/dist/fracture/src/ExecutionTree.d.ts +0 -77
- package/dist/fracture/src/ExecutionTree.d.ts.map +0 -1
- package/dist/fracture/src/ExecutionTree.js +0 -258
- package/dist/fracture/src/ExecutionTree.js.map +0 -1
- package/dist/fracture/src/Logger.d.ts +0 -25
- package/dist/fracture/src/Logger.d.ts.map +0 -1
- package/dist/fracture/src/Logger.js +0 -78
- package/dist/fracture/src/Logger.js.map +0 -1
- package/dist/fracture/src/PluginLoader.d.ts +0 -23
- package/dist/fracture/src/PluginLoader.d.ts.map +0 -1
- package/dist/fracture/src/PluginLoader.js +0 -102
- package/dist/fracture/src/PluginLoader.js.map +0 -1
- package/dist/fracture/src/PluginManager.d.ts +0 -64
- package/dist/fracture/src/PluginManager.d.ts.map +0 -1
- package/dist/fracture/src/PluginManager.js +0 -162
- package/dist/fracture/src/PluginManager.js.map +0 -1
- package/dist/fracture/src/PluginResolver.d.ts +0 -35
- package/dist/fracture/src/PluginResolver.d.ts.map +0 -1
- package/dist/fracture/src/PluginResolver.js +0 -128
- package/dist/fracture/src/PluginResolver.js.map +0 -1
- package/dist/fracture/src/QuestAPI.d.ts +0 -9
- package/dist/fracture/src/QuestAPI.d.ts.map +0 -1
- package/dist/fracture/src/QuestAPI.js +0 -679
- package/dist/fracture/src/QuestAPI.js.map +0 -1
- package/dist/fracture/src/QuestAPI.types.d.ts +0 -35
- package/dist/fracture/src/QuestAPI.types.d.ts.map +0 -1
- package/dist/fracture/src/QuestAPI.types.js +0 -3
- package/dist/fracture/src/QuestAPI.types.js.map +0 -1
- package/dist/fracture/src/QuestTestAPI.d.ts +0 -12
- package/dist/fracture/src/QuestTestAPI.d.ts.map +0 -1
- package/dist/fracture/src/QuestTestAPI.js +0 -133
- package/dist/fracture/src/QuestTestAPI.js.map +0 -1
- package/dist/fracture/src/ScriptEngine.d.ts +0 -21
- package/dist/fracture/src/ScriptEngine.d.ts.map +0 -1
- package/dist/fracture/src/ScriptEngine.js +0 -183
- package/dist/fracture/src/ScriptEngine.js.map +0 -1
- package/dist/fracture/src/ScriptValidator.d.ts +0 -68
- package/dist/fracture/src/ScriptValidator.d.ts.map +0 -1
- package/dist/fracture/src/ScriptValidator.js +0 -351
- package/dist/fracture/src/ScriptValidator.js.map +0 -1
- package/dist/fracture/src/TestCounter.d.ts +0 -18
- package/dist/fracture/src/TestCounter.d.ts.map +0 -1
- package/dist/fracture/src/TestCounter.js +0 -82
- package/dist/fracture/src/TestCounter.js.map +0 -1
- package/dist/fracture/src/VariableResolver.d.ts +0 -20
- package/dist/fracture/src/VariableResolver.d.ts.map +0 -1
- package/dist/fracture/src/VariableResolver.js +0 -100
- package/dist/fracture/src/VariableResolver.js.map +0 -1
- package/dist/fracture/src/cli/index.d.ts +0 -3
- package/dist/fracture/src/cli/index.d.ts.map +0 -1
- package/dist/fracture/src/cli/index.js +0 -347
- package/dist/fracture/src/cli/index.js.map +0 -1
- package/dist/fracture/src/cli/plugin-commands.d.ts +0 -6
- package/dist/fracture/src/cli/plugin-commands.d.ts.map +0 -1
- package/dist/fracture/src/cli/plugin-commands.js +0 -263
- package/dist/fracture/src/cli/plugin-commands.js.map +0 -1
- package/dist/fracture/src/cli/plugin-discovery.d.ts +0 -11
- package/dist/fracture/src/cli/plugin-discovery.d.ts.map +0 -1
- package/dist/fracture/src/cli/plugin-discovery.js +0 -64
- package/dist/fracture/src/cli/plugin-discovery.js.map +0 -1
- package/dist/fracture/src/index.d.ts +0 -13
- package/dist/fracture/src/index.d.ts.map +0 -1
- package/dist/fracture/src/index.js +0 -17
- package/dist/fracture/src/index.js.map +0 -1
- package/dist/fracture/src/utils.d.ts +0 -28
- package/dist/fracture/src/utils.d.ts.map +0 -1
- package/dist/fracture/src/utils.js +0 -48
- package/dist/fracture/src/utils.js.map +0 -1
- package/dist/plugin-auth/src/apikey-auth.d.ts +0 -3
- package/dist/plugin-auth/src/apikey-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/apikey-auth.js +0 -73
- package/dist/plugin-auth/src/apikey-auth.js.map +0 -1
- package/dist/plugin-auth/src/basic-auth.d.ts +0 -3
- package/dist/plugin-auth/src/basic-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/basic-auth.js +0 -61
- package/dist/plugin-auth/src/basic-auth.js.map +0 -1
- package/dist/plugin-auth/src/bearer-auth.d.ts +0 -3
- package/dist/plugin-auth/src/bearer-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/bearer-auth.js +0 -49
- package/dist/plugin-auth/src/bearer-auth.js.map +0 -1
- package/dist/plugin-auth/src/helpers.d.ts +0 -3
- package/dist/plugin-auth/src/helpers.d.ts.map +0 -1
- package/dist/plugin-auth/src/helpers.js +0 -8
- package/dist/plugin-auth/src/helpers.js.map +0 -1
- package/dist/plugin-auth/src/index.d.ts +0 -10
- package/dist/plugin-auth/src/index.d.ts.map +0 -1
- package/dist/plugin-auth/src/index.js +0 -25
- package/dist/plugin-auth/src/index.js.map +0 -1
- package/dist/plugin-auth/src/oauth2-auth.d.ts +0 -35
- package/dist/plugin-auth/src/oauth2-auth.d.ts.map +0 -1
- package/dist/plugin-auth/src/oauth2-auth.js +0 -266
- package/dist/plugin-auth/src/oauth2-auth.js.map +0 -1
- package/dist/plugin-http/src/index.d.ts +0 -4
- package/dist/plugin-http/src/index.d.ts.map +0 -1
- package/dist/plugin-http/src/index.js +0 -266
- package/dist/plugin-http/src/index.js.map +0 -1
- package/dist/plugin-vault-file/src/index.d.ts +0 -67
- package/dist/plugin-vault-file/src/index.d.ts.map +0 -1
- package/dist/plugin-vault-file/src/index.js +0 -171
- package/dist/plugin-vault-file/src/index.js.map +0 -1
- package/dist/types.d.ts +0 -374
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -13
- package/dist/types.js.map +0 -1
- package/src/CollectionAnalyzer.ts +0 -102
- package/src/CollectionRunner.ts +0 -1423
- package/src/CollectionRunner.types.ts +0 -9
- package/src/CollectionValidator.ts +0 -289
- package/src/ConsoleReporter.ts +0 -143
- package/src/CookieJar.ts +0 -258
- package/src/DagScheduler.ts +0 -439
- package/src/Logger.ts +0 -85
- package/src/PluginLoader.ts +0 -126
- package/src/PluginManager.ts +0 -208
- package/src/PluginResolver.ts +0 -154
- package/src/QuestAPI.ts +0 -764
- package/src/QuestAPI.types.ts +0 -33
- package/src/QuestTestAPI.ts +0 -164
- package/src/RequestFilter.ts +0 -224
- package/src/ScriptEngine.ts +0 -219
- package/src/ScriptValidator.ts +0 -428
- package/src/TaskGraph.ts +0 -598
- package/src/TestCounter.ts +0 -109
- package/src/VariableResolver.ts +0 -114
- package/src/cli/index.ts +0 -480
- package/src/cli/plugin-commands.ts +0 -341
- package/src/cli/plugin-discovery.ts +0 -44
- package/src/index.ts +0 -24
- package/src/utils.ts +0 -52
|
@@ -1,802 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events';
|
|
2
|
-
import { ScriptType, LogLevel } from '@apiquest/types';
|
|
3
|
-
import { VariableResolver } from './VariableResolver.js';
|
|
4
|
-
import { PluginManager } from './PluginManager.js';
|
|
5
|
-
import { PluginLoader } from './PluginLoader.js';
|
|
6
|
-
import { PluginResolver } from './PluginResolver.js';
|
|
7
|
-
import { CollectionAnalyzer } from './CollectionAnalyzer.js';
|
|
8
|
-
import { CollectionValidator } from './CollectionValidator.js';
|
|
9
|
-
import { TestCounter } from './TestCounter.js';
|
|
10
|
-
import { ScriptEngine } from './ScriptEngine.js';
|
|
11
|
-
import { TreeBuilder } from './ExecutionTree.js';
|
|
12
|
-
import { Logger } from './Logger.js';
|
|
13
|
-
import { CookieJar } from './CookieJar.js';
|
|
14
|
-
import { isNullOrEmpty, isNullOrWhitespace } from './utils.js';
|
|
15
|
-
export class CollectionRunner extends EventEmitter {
|
|
16
|
-
variableResolver;
|
|
17
|
-
pluginManager;
|
|
18
|
-
pluginResolver;
|
|
19
|
-
pluginLoader;
|
|
20
|
-
collectionAnalyzer;
|
|
21
|
-
collectionValidator;
|
|
22
|
-
testCounter;
|
|
23
|
-
scriptEngine;
|
|
24
|
-
scriptQueue = Promise.resolve(); // Script execution mutex
|
|
25
|
-
pluginResolutionPromise; // Track plugin resolution (Phase 1)
|
|
26
|
-
resolvedPlugins = []; // Available plugins
|
|
27
|
-
logger;
|
|
28
|
-
constructor(options) {
|
|
29
|
-
super();
|
|
30
|
-
const logLevel = options?.logLevel ?? LogLevel.INFO;
|
|
31
|
-
// Create logger first so it can be passed to other components
|
|
32
|
-
this.logger = new Logger('CollectionRunner', logLevel, this);
|
|
33
|
-
this.variableResolver = new VariableResolver(this.logger);
|
|
34
|
-
this.pluginManager = new PluginManager(this, logLevel);
|
|
35
|
-
this.pluginResolver = new PluginResolver(this.logger);
|
|
36
|
-
this.pluginLoader = new PluginLoader(this.pluginManager, this.logger);
|
|
37
|
-
this.collectionAnalyzer = new CollectionAnalyzer();
|
|
38
|
-
this.collectionValidator = new CollectionValidator(this.pluginManager);
|
|
39
|
-
this.testCounter = new TestCounter(this.pluginManager);
|
|
40
|
-
this.scriptEngine = new ScriptEngine(this.logger);
|
|
41
|
-
// Phase 1: Resolve plugins if directories provided (fast - just scans, no loading)
|
|
42
|
-
if (options?.pluginsDir !== undefined) {
|
|
43
|
-
const dirs = Array.isArray(options.pluginsDir)
|
|
44
|
-
? options.pluginsDir
|
|
45
|
-
: [options.pluginsDir];
|
|
46
|
-
// Start plugin resolution (but don't block constructor)
|
|
47
|
-
this.pluginResolutionPromise = this.pluginResolver.scanDirectories(dirs);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// No plugins to resolve
|
|
51
|
-
this.pluginResolutionPromise = Promise.resolve([]);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Queue script execution to ensure sequential execution (thread-safe)
|
|
56
|
-
*/
|
|
57
|
-
async queueScript(fn) {
|
|
58
|
-
const resultPromise = this.scriptQueue.then(fn);
|
|
59
|
-
this.scriptQueue = resultPromise.then(() => { }, () => { });
|
|
60
|
-
return resultPromise;
|
|
61
|
-
}
|
|
62
|
-
registerPlugin(plugin) {
|
|
63
|
-
this.pluginManager.registerPlugin(plugin);
|
|
64
|
-
}
|
|
65
|
-
registerAuthPlugin(plugin) {
|
|
66
|
-
this.pluginManager.registerAuthPlugin(plugin);
|
|
67
|
-
}
|
|
68
|
-
emitConsoleOutput(messages) {
|
|
69
|
-
if (messages === undefined || messages.length === 0)
|
|
70
|
-
return;
|
|
71
|
-
for (const message of messages) {
|
|
72
|
-
let level = 'log';
|
|
73
|
-
let cleanMessage = message;
|
|
74
|
-
if (message.startsWith('[INFO] ')) {
|
|
75
|
-
level = 'info';
|
|
76
|
-
cleanMessage = message.replace('[INFO] ', '');
|
|
77
|
-
}
|
|
78
|
-
else if (message.startsWith('[WARN] ')) {
|
|
79
|
-
level = 'warn';
|
|
80
|
-
cleanMessage = message.replace('[WARN] ', '');
|
|
81
|
-
}
|
|
82
|
-
else if (message.startsWith('[ERROR] ')) {
|
|
83
|
-
level = 'error';
|
|
84
|
-
cleanMessage = message.replace('[ERROR] ', '');
|
|
85
|
-
}
|
|
86
|
-
this.emit('console', { message: cleanMessage, level });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Subscribe to ALL events emitted by the runner (including custom plugin events)
|
|
91
|
-
* Overrides emit to intercept all event emissions
|
|
92
|
-
*/
|
|
93
|
-
onAll(handler) {
|
|
94
|
-
// Store original emit
|
|
95
|
-
const originalEmit = this.emit.bind(this);
|
|
96
|
-
// Override emit to intercept all events
|
|
97
|
-
this.emit = function (event, ...args) {
|
|
98
|
-
// Call original handler first
|
|
99
|
-
const result = originalEmit(event, ...args);
|
|
100
|
-
// Skip internal EventEmitter events
|
|
101
|
-
if (event !== 'newListener' && event !== 'removeListener') {
|
|
102
|
-
handler(String(event), args[0]);
|
|
103
|
-
}
|
|
104
|
-
return result;
|
|
105
|
-
};
|
|
106
|
-
// Return cleanup function
|
|
107
|
-
return () => {
|
|
108
|
-
// Restore original emit
|
|
109
|
-
this.emit = originalEmit;
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Create event envelope for all events (except console)
|
|
114
|
-
*/
|
|
115
|
-
createEventEnvelope(collectionInfo, path, context, request) {
|
|
116
|
-
const pathType = path === 'collection:/' ? 'collection' : path.startsWith('folder:/') ? 'folder' : 'request';
|
|
117
|
-
const envelope = {
|
|
118
|
-
path,
|
|
119
|
-
pathType,
|
|
120
|
-
collectionInfo
|
|
121
|
-
};
|
|
122
|
-
// Add iteration info if available
|
|
123
|
-
if (context !== undefined) {
|
|
124
|
-
const currentRow = context.currentIterationData;
|
|
125
|
-
envelope.iteration = {
|
|
126
|
-
current: context.iterationCurrent,
|
|
127
|
-
total: context.iterationCount,
|
|
128
|
-
source: context.iterationSource,
|
|
129
|
-
rowIndex: currentRow !== undefined ? context.iterationCurrent - 1 : undefined,
|
|
130
|
-
rowKeys: currentRow !== undefined ? Object.keys(currentRow) : undefined,
|
|
131
|
-
row: currentRow
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
// Add request if provided
|
|
135
|
-
if (request !== undefined) {
|
|
136
|
-
envelope.request = request;
|
|
137
|
-
}
|
|
138
|
-
return envelope;
|
|
139
|
-
}
|
|
140
|
-
async run(collection, options = {}) {
|
|
141
|
-
const startTime = new Date();
|
|
142
|
-
// Set log level from options (if provided, overrides constructor level)
|
|
143
|
-
if (options.logLevel !== undefined) {
|
|
144
|
-
this.logger.setLevel(options.logLevel);
|
|
145
|
-
this.pluginManager.setLogLevel(options.logLevel);
|
|
146
|
-
}
|
|
147
|
-
// Phase 1: Wait for plugin resolution
|
|
148
|
-
this.resolvedPlugins = await this.pluginResolutionPromise;
|
|
149
|
-
this.logger.debug(`Plugin resolution complete: ${this.resolvedPlugins.length} plugins available`);
|
|
150
|
-
// Analyze collection to determine required plugins
|
|
151
|
-
const requirements = this.collectionAnalyzer.analyzeRequirements(collection);
|
|
152
|
-
this.logger.debug(`Collection requires: protocols=[${Array.from(requirements.protocols)}], auth=[${Array.from(requirements.authTypes)}], providers=[${Array.from(requirements.valueProviders)}]`);
|
|
153
|
-
// Phase 2: Load ONLY required plugins
|
|
154
|
-
await this.pluginLoader.loadRequiredPlugins(this.resolvedPlugins, requirements);
|
|
155
|
-
this.logger.debug('Required plugins loaded');
|
|
156
|
-
this.logger.info(`Starting collection: ${collection.info.name}`);
|
|
157
|
-
this.logger.debug(`Collection ID: ${collection.info.id}, Protocol: ${collection.protocol}`);
|
|
158
|
-
// Validate and cache protocol plugin
|
|
159
|
-
const protocolPlugin = this.pluginManager.getPlugin(collection.protocol);
|
|
160
|
-
if (protocolPlugin === undefined) {
|
|
161
|
-
throw new Error(`No plugin registered for protocol '${collection.protocol}'. ` +
|
|
162
|
-
`Available protocols: ${this.pluginManager.getAllPlugins().flatMap(p => p.protocols).join(', ')}`);
|
|
163
|
-
}
|
|
164
|
-
// Merge runtime options (needed for validation)
|
|
165
|
-
const runtimeOptions = this.mergeOptions(collection.options, options);
|
|
166
|
-
// Get strict mode with precedence: RunOptions > Collection.options > Default (true)
|
|
167
|
-
const strictMode = options.strictMode ?? collection.options?.strictMode ?? true;
|
|
168
|
-
// PRE-RUN VALIDATION
|
|
169
|
-
const validationResult = await this.collectionValidator.validateCollection(collection, runtimeOptions, strictMode);
|
|
170
|
-
// COUNT EXPECTED TESTS (only in strict mode for deterministic counting)
|
|
171
|
-
const expectedTestCount = strictMode ? this.testCounter.countTests(collection) : -1;
|
|
172
|
-
const treeBuilder = new TreeBuilder(options.data, this.logger);
|
|
173
|
-
const tree = treeBuilder.build(collection);
|
|
174
|
-
const collectionItem = tree.item;
|
|
175
|
-
const iterationData = collectionItem.testData ?? [];
|
|
176
|
-
const iterationCount = iterationData.length > 0 ? iterationData.length : 1;
|
|
177
|
-
// Determine iteration source for event envelopes
|
|
178
|
-
const iterationSource = options.data !== undefined ? 'cli' : (collection.testData !== undefined ? 'collection' : 'none');
|
|
179
|
-
// Emit beforeRun with validation results and expected test count
|
|
180
|
-
// Note: -1 means dynamic (can't determine), undefined means not calculated
|
|
181
|
-
this.emit('beforeRun', {
|
|
182
|
-
collectionInfo: {
|
|
183
|
-
id: collection.info.id,
|
|
184
|
-
name: collection.info.name,
|
|
185
|
-
version: collection.info.version,
|
|
186
|
-
description: collection.info.description
|
|
187
|
-
},
|
|
188
|
-
options,
|
|
189
|
-
validationResult,
|
|
190
|
-
expectedTestCount // Include -1 for dynamic tests
|
|
191
|
-
});
|
|
192
|
-
// STOP if validation failed
|
|
193
|
-
if (validationResult.valid === false) {
|
|
194
|
-
const endTime = new Date();
|
|
195
|
-
return {
|
|
196
|
-
collectionId: collection.info.id,
|
|
197
|
-
collectionName: collection.info.name,
|
|
198
|
-
startTime,
|
|
199
|
-
endTime,
|
|
200
|
-
duration: endTime.getTime() - startTime.getTime(),
|
|
201
|
-
requestResults: [],
|
|
202
|
-
totalTests: 0,
|
|
203
|
-
passedTests: 0,
|
|
204
|
-
failedTests: 0,
|
|
205
|
-
skippedTests: 0,
|
|
206
|
-
validationErrors: validationResult.errors
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const requestResults = [];
|
|
210
|
-
// Initialize cookie jar - always create one
|
|
211
|
-
// If jar.persist = true, cookies carry across requests
|
|
212
|
-
// If jar.persist = false (default), each request gets cookies from response only
|
|
213
|
-
const cookieJar = new CookieJar(runtimeOptions.jar ?? { persist: false });
|
|
214
|
-
// Inject initial cookies if provided in options
|
|
215
|
-
if (runtimeOptions.cookies !== null && runtimeOptions.cookies !== undefined && runtimeOptions.cookies.length > 0) {
|
|
216
|
-
for (const cookie of runtimeOptions.cookies) {
|
|
217
|
-
// Skip cookies without domain - domain is required for RFC 6265 compliance
|
|
218
|
-
if (cookie.domain === null || cookie.domain === undefined) {
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
cookieJar.set(cookie.name, cookie.value, {
|
|
222
|
-
domain: cookie.domain,
|
|
223
|
-
path: cookie.path,
|
|
224
|
-
expires: cookie.expires,
|
|
225
|
-
httpOnly: cookie.httpOnly,
|
|
226
|
-
secure: cookie.secure,
|
|
227
|
-
sameSite: cookie.sameSite
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Initialize scope stack with collection scope
|
|
232
|
-
const scopeStack = [{
|
|
233
|
-
level: 'collection',
|
|
234
|
-
id: collection.info.id,
|
|
235
|
-
vars: {}
|
|
236
|
-
}];
|
|
237
|
-
// Execute collection pre-request script once before all iterations
|
|
238
|
-
if (!isNullOrWhitespace(collection.collectionPreScript)) {
|
|
239
|
-
// Emit event before collection pre-script execution
|
|
240
|
-
const envelope = this.createEventEnvelope(collection.info, 'collection:/', undefined);
|
|
241
|
-
this.emit('beforeCollectionPreScript', {
|
|
242
|
-
...envelope,
|
|
243
|
-
path: 'collection:/'
|
|
244
|
-
});
|
|
245
|
-
const tempContext = {
|
|
246
|
-
collectionInfo: collection.info,
|
|
247
|
-
protocol: collection.protocol,
|
|
248
|
-
collectionVariables: collection.variables ?? {},
|
|
249
|
-
globalVariables: options.globalVariables ?? {},
|
|
250
|
-
scopeStack: [...scopeStack], // Clone scope stack
|
|
251
|
-
environment: options.environment,
|
|
252
|
-
iterationCurrent: 1,
|
|
253
|
-
iterationCount,
|
|
254
|
-
iterationData,
|
|
255
|
-
currentIterationData: iterationData[0],
|
|
256
|
-
iterationSource,
|
|
257
|
-
executionHistory: [],
|
|
258
|
-
options: this.mergeOptions(collection.options, options),
|
|
259
|
-
cookieJar,
|
|
260
|
-
eventEmitter: this,
|
|
261
|
-
protocolPlugin
|
|
262
|
-
};
|
|
263
|
-
const preScriptResult = await this.queueScript(() => this.scriptEngine.execute(collection.collectionPreScript, tempContext, ScriptType.CollectionPre, () => { } // noop - collection pre-scripts cannot have tests
|
|
264
|
-
));
|
|
265
|
-
this.emitConsoleOutput(preScriptResult.consoleOutput);
|
|
266
|
-
// Emit event after collection pre-script completion
|
|
267
|
-
const afterEnvelope = this.createEventEnvelope(collection.info, 'collection:/', tempContext);
|
|
268
|
-
this.emit('afterCollectionPreScript', {
|
|
269
|
-
...afterEnvelope,
|
|
270
|
-
path: 'collection:/',
|
|
271
|
-
result: preScriptResult
|
|
272
|
-
});
|
|
273
|
-
if (preScriptResult.success === false) {
|
|
274
|
-
throw new Error(`Collection pre-script error: ${preScriptResult.error}`);
|
|
275
|
-
}
|
|
276
|
-
// Update context variables for iterations
|
|
277
|
-
options.globalVariables = tempContext.globalVariables;
|
|
278
|
-
options.environment = tempContext.environment;
|
|
279
|
-
// Update collection scope with any changes from script
|
|
280
|
-
Object.assign(scopeStack[0].vars, tempContext.scopeStack[0].vars);
|
|
281
|
-
}
|
|
282
|
-
for (let i = 0; i < iterationCount; i++) {
|
|
283
|
-
const iterationStart = Date.now();
|
|
284
|
-
const context = {
|
|
285
|
-
collectionInfo: collection.info,
|
|
286
|
-
protocol: collection.protocol,
|
|
287
|
-
collectionVariables: collection.variables ?? {},
|
|
288
|
-
globalVariables: options.globalVariables ?? {},
|
|
289
|
-
scopeStack: [...scopeStack], // Clone scope stack for each iteration
|
|
290
|
-
environment: options.environment,
|
|
291
|
-
iterationCurrent: i + 1,
|
|
292
|
-
iterationCount,
|
|
293
|
-
iterationData,
|
|
294
|
-
currentIterationData: iterationData[i],
|
|
295
|
-
iterationSource,
|
|
296
|
-
executionHistory: [],
|
|
297
|
-
options: this.mergeOptions(collection.options, options),
|
|
298
|
-
cookieJar,
|
|
299
|
-
eventEmitter: this,
|
|
300
|
-
protocolPlugin
|
|
301
|
-
};
|
|
302
|
-
// Emit beforeIteration event
|
|
303
|
-
const iterationEnvelope = this.createEventEnvelope(collection.info, 'collection:/', context);
|
|
304
|
-
this.emit('beforeIteration', {
|
|
305
|
-
...iterationEnvelope,
|
|
306
|
-
iteration: iterationEnvelope.iteration
|
|
307
|
-
});
|
|
308
|
-
await this.executeTree(tree, context, requestResults);
|
|
309
|
-
// Emit afterIteration event
|
|
310
|
-
const iterationDuration = Date.now() - iterationStart;
|
|
311
|
-
const afterIterationEnvelope = this.createEventEnvelope(collection.info, 'collection:/', context);
|
|
312
|
-
this.emit('afterIteration', {
|
|
313
|
-
...afterIterationEnvelope,
|
|
314
|
-
iteration: afterIterationEnvelope.iteration,
|
|
315
|
-
duration: iterationDuration
|
|
316
|
-
});
|
|
317
|
-
// Update collection scope with changes from iteration
|
|
318
|
-
Object.assign(scopeStack[0].vars, context.scopeStack[0].vars);
|
|
319
|
-
// Update global variables and environment for next iteration
|
|
320
|
-
options.globalVariables = context.globalVariables;
|
|
321
|
-
options.environment = context.environment;
|
|
322
|
-
}
|
|
323
|
-
// Execute collection post-request script once after all iterations
|
|
324
|
-
if (!isNullOrWhitespace(collection.collectionPostScript)) {
|
|
325
|
-
// Emit event before collection post-script execution
|
|
326
|
-
const beforePostEnvelope = this.createEventEnvelope(collection.info, 'collection:/', undefined);
|
|
327
|
-
this.emit('beforeCollectionPostScript', {
|
|
328
|
-
...beforePostEnvelope,
|
|
329
|
-
path: 'collection:/'
|
|
330
|
-
});
|
|
331
|
-
const tempContext = {
|
|
332
|
-
collectionInfo: collection.info,
|
|
333
|
-
protocol: collection.protocol,
|
|
334
|
-
collectionVariables: collection.variables ?? {},
|
|
335
|
-
globalVariables: options.globalVariables ?? {},
|
|
336
|
-
scopeStack: [...scopeStack], // Clone scope stack
|
|
337
|
-
environment: options.environment,
|
|
338
|
-
iterationCurrent: iterationCount,
|
|
339
|
-
iterationCount,
|
|
340
|
-
iterationData,
|
|
341
|
-
currentIterationData: iterationData[iterationCount - 1],
|
|
342
|
-
iterationSource,
|
|
343
|
-
executionHistory: [],
|
|
344
|
-
options: this.mergeOptions(collection.options, options),
|
|
345
|
-
cookieJar,
|
|
346
|
-
eventEmitter: this,
|
|
347
|
-
protocolPlugin
|
|
348
|
-
};
|
|
349
|
-
const postScriptResult = await this.queueScript(() => this.scriptEngine.execute(collection.collectionPostScript, tempContext, ScriptType.CollectionPost, () => { } // noop - collection post-scripts cannot have tests
|
|
350
|
-
));
|
|
351
|
-
this.emitConsoleOutput(postScriptResult.consoleOutput);
|
|
352
|
-
// Emit event for collection post-script completion
|
|
353
|
-
const afterPostEnvelope = this.createEventEnvelope(collection.info, 'collection:/', tempContext);
|
|
354
|
-
this.emit('afterCollectionPostScript', {
|
|
355
|
-
...afterPostEnvelope,
|
|
356
|
-
path: 'collection:/',
|
|
357
|
-
result: postScriptResult
|
|
358
|
-
});
|
|
359
|
-
if (postScriptResult.success === false) {
|
|
360
|
-
throw new Error(`Collection post-script error: ${postScriptResult.error}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
const endTime = new Date();
|
|
364
|
-
const duration = endTime.getTime() - startTime.getTime();
|
|
365
|
-
const totalTests = requestResults.reduce((sum, r) => sum + r.tests.length, 0);
|
|
366
|
-
const passedTests = requestResults.reduce((sum, r) => sum + r.tests.filter(t => t.passed && !t.skipped).length, 0);
|
|
367
|
-
const failedTests = requestResults.reduce((sum, r) => sum + r.tests.filter(t => !t.passed && !t.skipped).length, 0);
|
|
368
|
-
const skippedTests = requestResults.reduce((sum, r) => sum + r.tests.filter(t => t.skipped).length, 0);
|
|
369
|
-
const result = {
|
|
370
|
-
collectionId: collection.info.id,
|
|
371
|
-
collectionName: collection.info.name,
|
|
372
|
-
startTime,
|
|
373
|
-
endTime,
|
|
374
|
-
duration,
|
|
375
|
-
requestResults,
|
|
376
|
-
totalTests,
|
|
377
|
-
passedTests,
|
|
378
|
-
failedTests,
|
|
379
|
-
skippedTests
|
|
380
|
-
};
|
|
381
|
-
this.emit('afterRun', {
|
|
382
|
-
collectionInfo: collection.info,
|
|
383
|
-
result
|
|
384
|
-
});
|
|
385
|
-
return result;
|
|
386
|
-
}
|
|
387
|
-
async executeTree(node, context, results) {
|
|
388
|
-
if (node.type === 'request') {
|
|
389
|
-
const result = await this.executeRequest(node, context);
|
|
390
|
-
results.push(result);
|
|
391
|
-
// If there was a script error, stop execution immediately
|
|
392
|
-
if (!isNullOrEmpty(result.scriptError)) {
|
|
393
|
-
throw new Error(`${result.scriptError}`);
|
|
394
|
-
}
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
// Handle folder node - execute folder scripts
|
|
398
|
-
if (node.type === 'folder') {
|
|
399
|
-
const folder = node.item;
|
|
400
|
-
const folderStart = Date.now();
|
|
401
|
-
// Emit beforeFolder event
|
|
402
|
-
const beforeFolderEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
403
|
-
this.emit('beforeFolder', beforeFolderEnvelope);
|
|
404
|
-
// PUSH folder scope
|
|
405
|
-
context.scopeStack.push({
|
|
406
|
-
level: 'folder',
|
|
407
|
-
id: folder.id,
|
|
408
|
-
vars: {}
|
|
409
|
-
});
|
|
410
|
-
// Execute folder pre-script
|
|
411
|
-
if (!isNullOrWhitespace(folder.folderPreScript)) {
|
|
412
|
-
// Emit event before folder pre-script execution
|
|
413
|
-
const beforePreEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
414
|
-
this.emit('beforeFolderPreScript', beforePreEnvelope);
|
|
415
|
-
const preScriptResult = await this.queueScript(() => this.scriptEngine.execute(folder.folderPreScript, context, ScriptType.FolderPre, () => { } // noop - folder pre-scripts cannot have tests
|
|
416
|
-
));
|
|
417
|
-
this.emitConsoleOutput(preScriptResult.consoleOutput);
|
|
418
|
-
// Emit event after folder pre-script completion
|
|
419
|
-
const afterPreEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
420
|
-
this.emit('afterFolderPreScript', {
|
|
421
|
-
...afterPreEnvelope,
|
|
422
|
-
result: preScriptResult
|
|
423
|
-
});
|
|
424
|
-
if (preScriptResult.success === false) {
|
|
425
|
-
throw new Error(`Folder pre-script error (${folder.name}): ${preScriptResult.error}`);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
// Execute children
|
|
429
|
-
for (const child of node.children) {
|
|
430
|
-
await this.executeTree(child, context, results);
|
|
431
|
-
}
|
|
432
|
-
// Execute folder post-script
|
|
433
|
-
if (!isNullOrWhitespace(folder.folderPostScript)) {
|
|
434
|
-
// Emit event before folder post-script execution
|
|
435
|
-
const beforePostEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
436
|
-
this.emit('beforeFolderPostScript', beforePostEnvelope);
|
|
437
|
-
const postScriptResult = await this.queueScript(() => this.scriptEngine.execute(folder.folderPostScript, context, ScriptType.FolderPost, () => { } // noop - folder post-scripts cannot have tests
|
|
438
|
-
));
|
|
439
|
-
this.emitConsoleOutput(postScriptResult.consoleOutput);
|
|
440
|
-
// Emit event for folder post-script completion
|
|
441
|
-
const afterPostEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
442
|
-
this.emit('afterFolderPostScript', {
|
|
443
|
-
...afterPostEnvelope,
|
|
444
|
-
result: postScriptResult
|
|
445
|
-
});
|
|
446
|
-
if (postScriptResult.success === false) {
|
|
447
|
-
throw new Error(`Folder post-script error (${folder.name}): ${postScriptResult.error}`);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
// POP folder scope (folder variables don't persist)
|
|
451
|
-
context.scopeStack.pop();
|
|
452
|
-
// Emit afterFolder event
|
|
453
|
-
const folderDuration = Date.now() - folderStart;
|
|
454
|
-
const afterFolderEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context);
|
|
455
|
-
this.emit('afterFolder', {
|
|
456
|
-
...afterFolderEnvelope,
|
|
457
|
-
duration: folderDuration
|
|
458
|
-
});
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
// Default: execute children
|
|
462
|
-
for (const child of node.children) {
|
|
463
|
-
await this.executeTree(child, context, results);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
async executeRequest(node, context) {
|
|
467
|
-
const request = node.item;
|
|
468
|
-
const requestStart = Date.now();
|
|
469
|
-
context.currentRequest = request;
|
|
470
|
-
context.currentPath = node.path;
|
|
471
|
-
// Emit beforeItem event
|
|
472
|
-
const beforeItemEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
473
|
-
this.emit('beforeItem', {
|
|
474
|
-
...beforeItemEnvelope,
|
|
475
|
-
request,
|
|
476
|
-
path: node.path
|
|
477
|
-
});
|
|
478
|
-
// PUSH request scope
|
|
479
|
-
context.scopeStack.push({
|
|
480
|
-
level: 'request',
|
|
481
|
-
id: request.id,
|
|
482
|
-
vars: {}
|
|
483
|
-
});
|
|
484
|
-
try {
|
|
485
|
-
// Queue all pre-request scripts
|
|
486
|
-
const preScripts = [
|
|
487
|
-
...(node.inheritedScripts.preRequest ?? []).map(script => ({
|
|
488
|
-
script,
|
|
489
|
-
source: 'inherited'
|
|
490
|
-
})),
|
|
491
|
-
...(!isNullOrWhitespace(request.preRequestScript)
|
|
492
|
-
? [{ script: request.preRequestScript, source: 'request' }]
|
|
493
|
-
: [])
|
|
494
|
-
];
|
|
495
|
-
if (preScripts.length > 0) {
|
|
496
|
-
await this.queueScript(async () => {
|
|
497
|
-
for (const { script, source } of preScripts) {
|
|
498
|
-
// Emit beforePreScript event
|
|
499
|
-
const beforePreEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
500
|
-
this.emit('beforePreScript', {
|
|
501
|
-
...beforePreEnvelope,
|
|
502
|
-
request,
|
|
503
|
-
path: node.path
|
|
504
|
-
});
|
|
505
|
-
const preScriptResult = await this.scriptEngine.execute(script, context, ScriptType.PreRequest, () => { } // noop - pre-request scripts cannot have tests
|
|
506
|
-
);
|
|
507
|
-
this.emitConsoleOutput(preScriptResult.consoleOutput);
|
|
508
|
-
// Emit afterPreScript event
|
|
509
|
-
const afterPreEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
510
|
-
this.emit('afterPreScript', {
|
|
511
|
-
...afterPreEnvelope,
|
|
512
|
-
request,
|
|
513
|
-
path: node.path,
|
|
514
|
-
result: preScriptResult
|
|
515
|
-
});
|
|
516
|
-
if (preScriptResult.success === false) {
|
|
517
|
-
const error = new Error(`${source === 'inherited' ? 'Inherited pre-request' : 'Pre-request'} script error: ${preScriptResult.error}`);
|
|
518
|
-
error.phase = 'prerequest';
|
|
519
|
-
throw error;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
// HTTP execution NOT queued - runs in parallel
|
|
525
|
-
const resolvedRequest = this.resolveRequest(request, context);
|
|
526
|
-
let resolvedAuth = node.effectiveAuth;
|
|
527
|
-
if (resolvedAuth?.data !== undefined) {
|
|
528
|
-
resolvedAuth = {
|
|
529
|
-
...resolvedAuth,
|
|
530
|
-
data: this.variableResolver.resolveAll(resolvedAuth.data, context)
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
const requestWithAuth = { ...resolvedRequest, auth: resolvedAuth };
|
|
534
|
-
// Track event indices and tests for plugin events (per request)
|
|
535
|
-
const eventIndices = new Map();
|
|
536
|
-
const pluginEventTests = []; // Collect tests from plugin events
|
|
537
|
-
// Create emitEvent callback for plugin event execution
|
|
538
|
-
const emitEvent = async (eventName, eventData) => {
|
|
539
|
-
this.logger.trace(`Plugin event emitted: ${eventName}`, { hasScripts: request.data.scripts !== undefined });
|
|
540
|
-
// Find matching script (validation ensures at most one per event type)
|
|
541
|
-
const eventScript = request.data.scripts?.find(s => s.event === eventName);
|
|
542
|
-
this.logger.trace(`Event script found: ${eventScript !== undefined}`);
|
|
543
|
-
if (eventScript === undefined)
|
|
544
|
-
return;
|
|
545
|
-
// Get current event index (starts at 0)
|
|
546
|
-
const currentIndex = eventIndices.get(eventName) ?? 0;
|
|
547
|
-
// Set event context (wrapped in try/finally to prevent state leak)
|
|
548
|
-
try {
|
|
549
|
-
context.currentEvent = {
|
|
550
|
-
eventName: eventName,
|
|
551
|
-
requestId: request.id,
|
|
552
|
-
timestamp: new Date(),
|
|
553
|
-
data: eventData,
|
|
554
|
-
index: currentIndex
|
|
555
|
-
};
|
|
556
|
-
// Execute plugin event script
|
|
557
|
-
const result = await this.scriptEngine.execute(eventScript.script, context, ScriptType.PluginEvent, (test) => {
|
|
558
|
-
// Emit assertion event for plugin event test
|
|
559
|
-
const eventDef = context.protocolPlugin.events?.find(e => e.name === eventName);
|
|
560
|
-
if (eventDef?.canHaveTests === true) {
|
|
561
|
-
const envelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
562
|
-
this.emit('assertion', {
|
|
563
|
-
...envelope,
|
|
564
|
-
test,
|
|
565
|
-
event: eventName
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
// Handle test results - add directly to collection
|
|
570
|
-
if (result.tests !== undefined && result.tests.length > 0) {
|
|
571
|
-
this.logger.trace(`Plugin event tests collected: ${result.tests.length}`);
|
|
572
|
-
pluginEventTests.push(...result.tests);
|
|
573
|
-
}
|
|
574
|
-
else {
|
|
575
|
-
this.logger.trace('No tests in plugin event result');
|
|
576
|
-
}
|
|
577
|
-
// Handle script errors (log but don't throw - allow other events to continue)
|
|
578
|
-
if (!isNullOrEmpty(result.error)) {
|
|
579
|
-
this.logger.error(`Plugin event script error (${eventName}):`, result.error);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
finally {
|
|
583
|
-
// CRITICAL: Always reset event context to prevent state leak
|
|
584
|
-
context.currentEvent = undefined;
|
|
585
|
-
// Increment event index for next event of same type
|
|
586
|
-
eventIndices.set(eventName, currentIndex + 1);
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
// Emit beforeRequest event
|
|
590
|
-
const beforeRequestEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
591
|
-
this.emit('beforeRequest', {
|
|
592
|
-
...beforeRequestEnvelope,
|
|
593
|
-
request,
|
|
594
|
-
path: node.path
|
|
595
|
-
});
|
|
596
|
-
const response = await this.pluginManager.execute(context.protocol, requestWithAuth, context, context.options, emitEvent // Pass event emitter to plugin
|
|
597
|
-
);
|
|
598
|
-
context.currentResponse = response;
|
|
599
|
-
// Emit afterRequest event (after HTTP execution, before post-scripts)
|
|
600
|
-
const requestDuration = Date.now() - requestStart;
|
|
601
|
-
const afterRequestEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
602
|
-
this.emit('afterRequest', {
|
|
603
|
-
...afterRequestEnvelope,
|
|
604
|
-
request,
|
|
605
|
-
response,
|
|
606
|
-
duration: requestDuration
|
|
607
|
-
});
|
|
608
|
-
// Add preliminary execution record to history
|
|
609
|
-
this.logger.trace(`Creating execution record with ${pluginEventTests.length} plugin event tests`);
|
|
610
|
-
const executionRecord = {
|
|
611
|
-
id: request.id,
|
|
612
|
-
name: request.name,
|
|
613
|
-
path: node.path,
|
|
614
|
-
iteration: context.iterationCurrent,
|
|
615
|
-
response,
|
|
616
|
-
tests: [...pluginEventTests], // Add plugin event tests first
|
|
617
|
-
timestamp: new Date().toISOString()
|
|
618
|
-
};
|
|
619
|
-
this.logger.trace(`Execution record has ${executionRecord.tests.length} total tests`);
|
|
620
|
-
context.executionHistory.push(executionRecord);
|
|
621
|
-
// Queue all post-request scripts
|
|
622
|
-
const postScripts = [
|
|
623
|
-
...(!isNullOrWhitespace(request.postRequestScript)
|
|
624
|
-
? [{ script: request.postRequestScript, source: 'request' }]
|
|
625
|
-
: []),
|
|
626
|
-
...(node.inheritedScripts.postRequest ?? []).map(script => ({
|
|
627
|
-
script,
|
|
628
|
-
source: 'inherited'
|
|
629
|
-
}))
|
|
630
|
-
];
|
|
631
|
-
const scriptResult = postScripts.length > 0
|
|
632
|
-
? await this.queueScript(async () => {
|
|
633
|
-
const combinedResult = { success: true, tests: [], consoleOutput: [] };
|
|
634
|
-
for (const { script, source } of postScripts) {
|
|
635
|
-
// Emit beforePostScript event
|
|
636
|
-
const beforePostEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
637
|
-
this.emit('beforePostScript', {
|
|
638
|
-
...beforePostEnvelope,
|
|
639
|
-
request,
|
|
640
|
-
path: node.path,
|
|
641
|
-
response
|
|
642
|
-
});
|
|
643
|
-
const postScriptResult = await this.scriptEngine.execute(script, context, ScriptType.PostRequest, (test) => {
|
|
644
|
-
// Emit assertion event for post-request test
|
|
645
|
-
const envelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
646
|
-
this.emit('assertion', {
|
|
647
|
-
...envelope,
|
|
648
|
-
test,
|
|
649
|
-
response
|
|
650
|
-
});
|
|
651
|
-
});
|
|
652
|
-
this.emitConsoleOutput(postScriptResult.consoleOutput);
|
|
653
|
-
// Emit afterPostScript event
|
|
654
|
-
const afterPostEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
655
|
-
this.emit('afterPostScript', {
|
|
656
|
-
...afterPostEnvelope,
|
|
657
|
-
request,
|
|
658
|
-
path: node.path,
|
|
659
|
-
response,
|
|
660
|
-
result: postScriptResult
|
|
661
|
-
});
|
|
662
|
-
if (postScriptResult.success === false) {
|
|
663
|
-
const error = new Error(`${source === 'inherited' ? 'Inherited post-request' : 'Post-request'} script error: ${postScriptResult.error}`);
|
|
664
|
-
error.phase = 'postrequest';
|
|
665
|
-
throw error;
|
|
666
|
-
}
|
|
667
|
-
combinedResult.tests.push(...postScriptResult.tests);
|
|
668
|
-
combinedResult.consoleOutput.push(...postScriptResult.consoleOutput);
|
|
669
|
-
}
|
|
670
|
-
return combinedResult;
|
|
671
|
-
})
|
|
672
|
-
: { success: true, tests: [], consoleOutput: [] };
|
|
673
|
-
// Update the execution record with all tests (plugin events + post-request scripts)
|
|
674
|
-
const allTests = [...pluginEventTests, ...scriptResult.tests];
|
|
675
|
-
executionRecord.tests = allTests;
|
|
676
|
-
const duration = Date.now() - requestStart;
|
|
677
|
-
const result = {
|
|
678
|
-
requestId: request.id,
|
|
679
|
-
requestName: request.name,
|
|
680
|
-
path: node.path,
|
|
681
|
-
success: isNullOrEmpty(response.error),
|
|
682
|
-
response,
|
|
683
|
-
tests: allTests,
|
|
684
|
-
duration,
|
|
685
|
-
iteration: context.iterationCurrent
|
|
686
|
-
};
|
|
687
|
-
// Emit afterItem event
|
|
688
|
-
const afterItemEnvelope = this.createEventEnvelope(context.collectionInfo, node.path, context, request);
|
|
689
|
-
this.emit('afterItem', {
|
|
690
|
-
...afterItemEnvelope,
|
|
691
|
-
request,
|
|
692
|
-
path: node.path,
|
|
693
|
-
response,
|
|
694
|
-
result
|
|
695
|
-
});
|
|
696
|
-
// Clear cookies if persist is false (default behavior)
|
|
697
|
-
if (context.options.jar?.persist !== true) {
|
|
698
|
-
context.cookieJar.clear();
|
|
699
|
-
}
|
|
700
|
-
// POP request scope
|
|
701
|
-
context.scopeStack.pop();
|
|
702
|
-
return result;
|
|
703
|
-
}
|
|
704
|
-
catch (error) {
|
|
705
|
-
const duration = Date.now() - requestStart;
|
|
706
|
-
const err = error;
|
|
707
|
-
const result = {
|
|
708
|
-
requestId: request.id,
|
|
709
|
-
requestName: request.name,
|
|
710
|
-
path: node.path,
|
|
711
|
-
success: false,
|
|
712
|
-
tests: [],
|
|
713
|
-
duration,
|
|
714
|
-
scriptError: err.message ?? String(error),
|
|
715
|
-
iteration: context.iterationCurrent
|
|
716
|
-
};
|
|
717
|
-
const phase = err.phase ?? 'request';
|
|
718
|
-
this.emit('exception', {
|
|
719
|
-
error,
|
|
720
|
-
phase,
|
|
721
|
-
request,
|
|
722
|
-
path: node.path,
|
|
723
|
-
response: context.currentResponse
|
|
724
|
-
});
|
|
725
|
-
// POP request scope even on error
|
|
726
|
-
context.scopeStack.pop();
|
|
727
|
-
return result;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
resolveRequest(request, context) {
|
|
731
|
-
const resolved = {
|
|
732
|
-
...request,
|
|
733
|
-
data: this.variableResolver.resolveAll(request.data, context)
|
|
734
|
-
};
|
|
735
|
-
// Also resolve variables in auth configuration
|
|
736
|
-
if (resolved.auth?.data !== undefined) {
|
|
737
|
-
resolved.auth = {
|
|
738
|
-
...resolved.auth,
|
|
739
|
-
data: this.variableResolver.resolveAll(resolved.auth.data, context)
|
|
740
|
-
};
|
|
741
|
-
}
|
|
742
|
-
return resolved;
|
|
743
|
-
}
|
|
744
|
-
mergeOptions(collectionOptions, runOptions) {
|
|
745
|
-
// Since RunOptions extends RuntimeOptions, merge is straightforward
|
|
746
|
-
// RunOptions takes precedence over collectionOptions
|
|
747
|
-
const merged = {
|
|
748
|
-
...collectionOptions,
|
|
749
|
-
...runOptions,
|
|
750
|
-
// Deep merge nested objects
|
|
751
|
-
execution: {
|
|
752
|
-
...(collectionOptions?.execution ?? {}),
|
|
753
|
-
...(runOptions?.execution ?? {})
|
|
754
|
-
},
|
|
755
|
-
// Ensure defaults
|
|
756
|
-
logLevel: runOptions?.logLevel ?? collectionOptions?.logLevel ?? LogLevel.INFO,
|
|
757
|
-
strictMode: runOptions?.strictMode ?? collectionOptions?.strictMode ?? true
|
|
758
|
-
};
|
|
759
|
-
// Conditionally merge optional nested objects
|
|
760
|
-
if (collectionOptions?.timeout !== undefined || runOptions?.timeout !== undefined) {
|
|
761
|
-
merged.timeout = {
|
|
762
|
-
...(collectionOptions?.timeout ?? {}),
|
|
763
|
-
...(runOptions?.timeout ?? {})
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
if (collectionOptions?.ssl !== undefined || runOptions?.ssl !== undefined) {
|
|
767
|
-
merged.ssl = {
|
|
768
|
-
...(collectionOptions?.ssl ?? {}),
|
|
769
|
-
...(runOptions?.ssl ?? {})
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
if (collectionOptions?.jar !== undefined || runOptions?.jar !== undefined) {
|
|
773
|
-
merged.jar = {
|
|
774
|
-
persist: runOptions?.jar?.persist ?? collectionOptions?.jar?.persist ?? false
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
if (runOptions?.proxy !== null && runOptions?.proxy !== undefined) {
|
|
778
|
-
merged.proxy = runOptions.proxy;
|
|
779
|
-
}
|
|
780
|
-
else if (collectionOptions?.proxy !== null && collectionOptions?.proxy !== undefined) {
|
|
781
|
-
merged.proxy = collectionOptions.proxy;
|
|
782
|
-
}
|
|
783
|
-
// Merge cookies arrays (runOptions cookies + collectionOptions cookies)
|
|
784
|
-
const collectionCookies = collectionOptions?.cookies ?? [];
|
|
785
|
-
const runCookies = runOptions?.cookies ?? [];
|
|
786
|
-
if (collectionCookies.length > 0 || runCookies.length > 0) {
|
|
787
|
-
// RunOptions cookies override collection cookies with same name
|
|
788
|
-
const cookieMap = new Map();
|
|
789
|
-
// Add collection cookies first
|
|
790
|
-
for (const cookie of collectionCookies) {
|
|
791
|
-
cookieMap.set(cookie.name, cookie);
|
|
792
|
-
}
|
|
793
|
-
// Then add/override with run cookies
|
|
794
|
-
for (const cookie of runCookies) {
|
|
795
|
-
cookieMap.set(cookie.name, cookie);
|
|
796
|
-
}
|
|
797
|
-
merged.cookies = Array.from(cookieMap.values());
|
|
798
|
-
}
|
|
799
|
-
return merged;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
//# sourceMappingURL=CollectionRunner.js.map
|