endorphin-ai 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.md +209 -0
- package/README.md +474 -0
- package/bin/endorphin.js +256 -0
- package/examples/endorphin.config.js +22 -0
- package/examples/tests/QE-001-basic-login-test.js +18 -0
- package/examples/tests/sample-test.js +9 -0
- package/examples/tools/.gitkeep +0 -0
- package/framework/config/agent-config.js +53 -0
- package/framework/config/browser-config.js +53 -0
- package/framework/config/paths.js +40 -0
- package/framework/core/agent-setup.js +75 -0
- package/framework/core/browser-framework.js +766 -0
- package/framework/core/config-loader.js +309 -0
- package/framework/core/test-discovery.js +402 -0
- package/framework/core/test-manager.js +343 -0
- package/framework/core/test-recorder.js +302 -0
- package/framework/core/test-runner.js +133 -0
- package/framework/core/test-session.js +98 -0
- package/framework/index.js +44 -0
- package/framework/interactive/enhanced-interactive-recorder.js +223 -0
- package/framework/interactive/index.js +18 -0
- package/framework/interactive/interactive-test-clean.js +33 -0
- package/framework/interactive/interactive-test.js +33 -0
- package/framework/test-framework.js +29 -0
- package/framework/testing/index.js +15 -0
- package/framework/testing/test-interactive-recorder.js +83 -0
- package/framework/testing/test-modular-framework.js +47 -0
- package/framework/testing/verify-test-format.js +58 -0
- package/framework/tools/content.js +67 -0
- package/framework/tools/index.js +52 -0
- package/framework/tools/interaction.js +180 -0
- package/framework/tools/navigation.js +43 -0
- package/framework/tools/utilities.js +99 -0
- package/framework/tools/verification.js +84 -0
- package/package.json +84 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// Configuration loader for Endorphin AI
|
|
2
|
+
// Handles loading and merging of configuration from multiple sources
|
|
3
|
+
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { resolve, join } from 'path';
|
|
6
|
+
import { pathToFileURL } from 'url';
|
|
7
|
+
|
|
8
|
+
export class ConfigLoader {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.defaultConfig = {
|
|
11
|
+
// Browser Configuration
|
|
12
|
+
browser: {
|
|
13
|
+
headless: false,
|
|
14
|
+
viewport: { width: 1280, height: 720 },
|
|
15
|
+
timeout: 30000,
|
|
16
|
+
slowMo: 0,
|
|
17
|
+
deviceScaleFactor: 1,
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// AI Configuration
|
|
21
|
+
ai: {
|
|
22
|
+
model: "gpt-4o",
|
|
23
|
+
maxRetries: 3,
|
|
24
|
+
temperature: 0.1,
|
|
25
|
+
maxTokens: 4000,
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
// Test Execution Settings
|
|
29
|
+
execution: {
|
|
30
|
+
screenshots: true,
|
|
31
|
+
recordVideo: false,
|
|
32
|
+
pauseOnError: false,
|
|
33
|
+
continueOnError: false,
|
|
34
|
+
maxConcurrency: 1,
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// Global Test Data
|
|
38
|
+
testData: {
|
|
39
|
+
baseUrl: "https://example.com",
|
|
40
|
+
timeout: 5000,
|
|
41
|
+
retryCount: 2,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Result Storage
|
|
45
|
+
results: {
|
|
46
|
+
directory: "./test-results",
|
|
47
|
+
keepHistory: 10,
|
|
48
|
+
format: ["json", "html"],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Environment Settings
|
|
52
|
+
environments: {
|
|
53
|
+
development: {
|
|
54
|
+
baseUrl: "http://localhost:3000",
|
|
55
|
+
headless: false,
|
|
56
|
+
},
|
|
57
|
+
staging: {
|
|
58
|
+
baseUrl: "https://staging.example.com",
|
|
59
|
+
headless: true,
|
|
60
|
+
},
|
|
61
|
+
production: {
|
|
62
|
+
baseUrl: "https://example.com",
|
|
63
|
+
headless: true,
|
|
64
|
+
screenshots: false,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load configuration from all sources and merge them
|
|
72
|
+
* Priority: CLI flags > User config > Environment > Defaults
|
|
73
|
+
*/
|
|
74
|
+
async loadConfig(options = {}) {
|
|
75
|
+
let config = { ...this.defaultConfig };
|
|
76
|
+
|
|
77
|
+
// 1. Load user config file if it exists
|
|
78
|
+
const userConfig = await this.loadUserConfig();
|
|
79
|
+
if (userConfig) {
|
|
80
|
+
config = this.mergeConfig(config, userConfig);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 2. Apply environment variables
|
|
84
|
+
const envConfig = this.loadEnvironmentConfig();
|
|
85
|
+
config = this.mergeConfig(config, envConfig);
|
|
86
|
+
|
|
87
|
+
// 3. Apply CLI options (highest priority)
|
|
88
|
+
if (options) {
|
|
89
|
+
// Transform CLI flags to proper config structure
|
|
90
|
+
const transformedOptions = this.transformCliFlags(options);
|
|
91
|
+
config = this.mergeConfig(config, transformedOptions);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Apply environment-specific overrides
|
|
95
|
+
const environment = process.env.NODE_ENV || 'development';
|
|
96
|
+
if (config.environments && config.environments[environment]) {
|
|
97
|
+
config = this.mergeConfig(config, config.environments[environment]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return config;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Load user's endorphin.config.js file
|
|
105
|
+
*/
|
|
106
|
+
async loadUserConfig() {
|
|
107
|
+
const configPaths = [
|
|
108
|
+
resolve(process.cwd(), 'endorphin.config.js'),
|
|
109
|
+
resolve(process.cwd(), 'endorphin.config.mjs'),
|
|
110
|
+
resolve(process.cwd(), '.endorphin.config.js'),
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const configPath of configPaths) {
|
|
114
|
+
if (existsSync(configPath)) {
|
|
115
|
+
try {
|
|
116
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
117
|
+
const module = await import(`${configUrl}?t=${Date.now()}`);
|
|
118
|
+
const config = module.default || module;
|
|
119
|
+
|
|
120
|
+
console.log(`๐ Loaded config from: ${configPath}`);
|
|
121
|
+
return config;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn(`โ ๏ธ Failed to load config from ${configPath}:`, error.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Load configuration from environment variables
|
|
133
|
+
*/
|
|
134
|
+
loadEnvironmentConfig() {
|
|
135
|
+
const envConfig = {};
|
|
136
|
+
|
|
137
|
+
// Browser settings
|
|
138
|
+
if (process.env.ENDORPHIN_HEADLESS !== undefined) {
|
|
139
|
+
envConfig.browser = { headless: process.env.ENDORPHIN_HEADLESS === 'true' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (process.env.ENDORPHIN_VIEWPORT_WIDTH || process.env.ENDORPHIN_VIEWPORT_HEIGHT) {
|
|
143
|
+
envConfig.browser = envConfig.browser || {};
|
|
144
|
+
envConfig.browser.viewport = {
|
|
145
|
+
width: parseInt(process.env.ENDORPHIN_VIEWPORT_WIDTH) || 1280,
|
|
146
|
+
height: parseInt(process.env.ENDORPHIN_VIEWPORT_HEIGHT) || 720,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// AI settings
|
|
151
|
+
if (process.env.ENDORPHIN_AI_MODEL) {
|
|
152
|
+
envConfig.ai = { model: process.env.ENDORPHIN_AI_MODEL };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (process.env.ENDORPHIN_AI_TEMPERATURE) {
|
|
156
|
+
envConfig.ai = envConfig.ai || {};
|
|
157
|
+
envConfig.ai.temperature = parseFloat(process.env.ENDORPHIN_AI_TEMPERATURE);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Test data
|
|
161
|
+
if (process.env.ENDORPHIN_BASE_URL) {
|
|
162
|
+
envConfig.testData = { baseUrl: process.env.ENDORPHIN_BASE_URL };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return envConfig;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Transform CLI flags to proper config structure
|
|
170
|
+
*/
|
|
171
|
+
transformCliFlags(options) {
|
|
172
|
+
const result = { ...options };
|
|
173
|
+
|
|
174
|
+
// Extract cliFlags if present
|
|
175
|
+
if (options.cliFlags) {
|
|
176
|
+
const { cliFlags, ...otherOptions } = options;
|
|
177
|
+
|
|
178
|
+
// Map CLI flags to config structure
|
|
179
|
+
const configFromFlags = {};
|
|
180
|
+
|
|
181
|
+
if (cliFlags.headless !== undefined) {
|
|
182
|
+
configFromFlags.browser = { ...configFromFlags.browser, headless: cliFlags.headless };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (cliFlags.timeout !== undefined) {
|
|
186
|
+
configFromFlags.execution = { ...configFromFlags.execution, timeout: cliFlags.timeout };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (cliFlags.viewport !== undefined) {
|
|
190
|
+
configFromFlags.browser = {
|
|
191
|
+
...configFromFlags.browser,
|
|
192
|
+
viewport: cliFlags.viewport
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (cliFlags.parallel !== undefined) {
|
|
197
|
+
configFromFlags.execution = {
|
|
198
|
+
...configFromFlags.execution,
|
|
199
|
+
parallel: cliFlags.parallel
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (cliFlags.model !== undefined) {
|
|
204
|
+
configFromFlags.ai = { ...configFromFlags.ai, model: cliFlags.model };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (cliFlags.environment !== undefined) {
|
|
208
|
+
configFromFlags.environment = cliFlags.environment;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Merge transformed flags with other options
|
|
212
|
+
return this.mergeConfig(otherOptions, configFromFlags);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Deep merge two configuration objects
|
|
220
|
+
*/
|
|
221
|
+
mergeConfig(target, source) {
|
|
222
|
+
const result = { ...target };
|
|
223
|
+
|
|
224
|
+
for (const key in source) {
|
|
225
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
226
|
+
result[key] = this.mergeConfig(result[key] || {}, source[key]);
|
|
227
|
+
} else {
|
|
228
|
+
result[key] = source[key];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Validate configuration
|
|
237
|
+
*/
|
|
238
|
+
validateConfig(config) {
|
|
239
|
+
const errors = [];
|
|
240
|
+
|
|
241
|
+
// Validate browser config
|
|
242
|
+
if (config.browser) {
|
|
243
|
+
if (config.browser.viewport) {
|
|
244
|
+
if (!config.browser.viewport.width || !config.browser.viewport.height) {
|
|
245
|
+
errors.push('Browser viewport must have width and height');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Validate AI config
|
|
251
|
+
if (config.ai) {
|
|
252
|
+
if (config.ai.temperature !== undefined) {
|
|
253
|
+
if (config.ai.temperature < 0 || config.ai.temperature > 1) {
|
|
254
|
+
errors.push('AI temperature must be between 0 and 1');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (errors.length > 0) {
|
|
260
|
+
throw new Error(`Configuration validation failed:\n${errors.join('\n')}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get configuration value by path (e.g., 'browser.viewport.width')
|
|
268
|
+
*/
|
|
269
|
+
getConfigValue(config, path) {
|
|
270
|
+
return path.split('.').reduce((obj, key) => obj && obj[key], config);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Set configuration value by path
|
|
275
|
+
*/
|
|
276
|
+
setConfigValue(config, path, value) {
|
|
277
|
+
const keys = path.split('.');
|
|
278
|
+
const lastKey = keys.pop();
|
|
279
|
+
const target = keys.reduce((obj, key) => {
|
|
280
|
+
if (!obj[key]) obj[key] = {};
|
|
281
|
+
return obj[key];
|
|
282
|
+
}, config);
|
|
283
|
+
|
|
284
|
+
target[lastKey] = value;
|
|
285
|
+
return config;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Global config instance
|
|
290
|
+
let configInstance = null;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get the global configuration instance
|
|
294
|
+
*/
|
|
295
|
+
export async function getConfig(options = {}) {
|
|
296
|
+
if (!configInstance) {
|
|
297
|
+
const loader = new ConfigLoader();
|
|
298
|
+
configInstance = await loader.loadConfig(options);
|
|
299
|
+
loader.validateConfig(configInstance);
|
|
300
|
+
}
|
|
301
|
+
return configInstance;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Reset the global configuration (useful for testing)
|
|
306
|
+
*/
|
|
307
|
+
export function resetConfig() {
|
|
308
|
+
configInstance = null;
|
|
309
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// Test Discovery Module
|
|
2
|
+
// Discovers and loads tests from user's tests/ directory
|
|
3
|
+
|
|
4
|
+
import { readdir, stat } from 'fs/promises';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import { pathToFileURL } from 'url';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if we're running in test environment
|
|
10
|
+
*/
|
|
11
|
+
function isTestEnvironment() {
|
|
12
|
+
return process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Safe exit that doesn't break tests
|
|
17
|
+
*/
|
|
18
|
+
function safeExit(code) {
|
|
19
|
+
if (isTestEnvironment()) {
|
|
20
|
+
throw new Error(`process.exit called with code ${code}`);
|
|
21
|
+
} else {
|
|
22
|
+
process.exit(code);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TestDiscovery {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.tests = new Map();
|
|
29
|
+
this.testsDirectory = resolve(process.cwd(), 'tests');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Discover and load all test files from tests/ directory
|
|
34
|
+
*/
|
|
35
|
+
async discoverTests() {
|
|
36
|
+
try {
|
|
37
|
+
// Check if tests directory exists
|
|
38
|
+
try {
|
|
39
|
+
await stat(this.testsDirectory);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log(`๐ Tests directory not found: ${this.testsDirectory}`);
|
|
42
|
+
console.log('๐ก Create a "tests/" directory and add your test files there.');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`๐ Discovering tests in: ${this.testsDirectory}`);
|
|
47
|
+
|
|
48
|
+
const files = await readdir(this.testsDirectory);
|
|
49
|
+
const testFiles = files.filter(file =>
|
|
50
|
+
file.endsWith('.js') || file.endsWith('.mjs')
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (testFiles.length === 0) {
|
|
54
|
+
console.log('๐ No test files found in tests/ directory');
|
|
55
|
+
console.log('๐ก Add .js or .mjs files with exported test objects');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`๐ Found ${testFiles.length} test file(s):`);
|
|
60
|
+
|
|
61
|
+
for (const file of testFiles) {
|
|
62
|
+
console.log(` ๐ ${file}`);
|
|
63
|
+
await this.loadTestFile(file);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`โ
Loaded ${this.tests.size} test(s) total\n`);
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('โ Error discovering tests:', error.message);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Load a specific test file and extract test objects
|
|
75
|
+
*/
|
|
76
|
+
async loadTestFile(filename) {
|
|
77
|
+
try {
|
|
78
|
+
const filePath = join(this.testsDirectory, filename);
|
|
79
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
80
|
+
|
|
81
|
+
// Dynamic import with cache busting
|
|
82
|
+
const module = await import(`${fileUrl}?t=${Date.now()}`);
|
|
83
|
+
|
|
84
|
+
// Extract all exported test objects
|
|
85
|
+
for (const [exportName, exportValue] of Object.entries(module)) {
|
|
86
|
+
if (this.isValidTest(exportValue)) {
|
|
87
|
+
this.tests.set(exportValue.id, {
|
|
88
|
+
...exportValue,
|
|
89
|
+
sourceFile: filename,
|
|
90
|
+
exportName
|
|
91
|
+
});
|
|
92
|
+
console.log(` โ ${exportValue.id}: ${exportValue.name}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(`โ Error loading ${filename}:`, error.message);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate if an object is a valid test
|
|
102
|
+
*/
|
|
103
|
+
isValidTest(obj) {
|
|
104
|
+
return (
|
|
105
|
+
obj &&
|
|
106
|
+
typeof obj === 'object' &&
|
|
107
|
+
typeof obj.id === 'string' &&
|
|
108
|
+
typeof obj.name === 'string' &&
|
|
109
|
+
(typeof obj.task === 'string' || typeof obj.execute === 'function')
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get test by ID
|
|
115
|
+
*/
|
|
116
|
+
getTest(id) {
|
|
117
|
+
return this.tests.get(id);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get all tests
|
|
122
|
+
*/
|
|
123
|
+
getAllTests() {
|
|
124
|
+
return Array.from(this.tests.values());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get tests by tag
|
|
129
|
+
*/
|
|
130
|
+
getTestsByTag(tag) {
|
|
131
|
+
return this.getAllTests().filter(test =>
|
|
132
|
+
test.tags && test.tags.includes(tag)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get tests by priority
|
|
138
|
+
*/
|
|
139
|
+
getTestsByPriority(priority) {
|
|
140
|
+
return this.getAllTests().filter(test =>
|
|
141
|
+
test.priority === priority
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* List all available tests
|
|
147
|
+
*/
|
|
148
|
+
listTests() {
|
|
149
|
+
const tests = this.getAllTests();
|
|
150
|
+
|
|
151
|
+
if (tests.length === 0) {
|
|
152
|
+
console.log('๐ No tests found');
|
|
153
|
+
console.log('๐ก Create test files in the tests/ directory');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log('\n๐ Available Tests:');
|
|
158
|
+
console.log('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ');
|
|
159
|
+
|
|
160
|
+
const grouped = {};
|
|
161
|
+
tests.forEach(test => {
|
|
162
|
+
const priority = test.priority || 'Unknown';
|
|
163
|
+
if (!grouped[priority]) grouped[priority] = [];
|
|
164
|
+
grouped[priority].push(test);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
for (const [priority, priorityTests] of Object.entries(grouped)) {
|
|
168
|
+
console.log(`\n๐ฏ ${priority} Priority:`);
|
|
169
|
+
priorityTests.forEach(test => {
|
|
170
|
+
const tags = test.tags ? `[${test.tags.join(', ')}]` : '';
|
|
171
|
+
console.log(` ${test.id}: ${test.name} ${tags}`);
|
|
172
|
+
console.log(` ๐ File: ${test.sourceFile}`);
|
|
173
|
+
if (test.description) {
|
|
174
|
+
console.log(` ๐ ${test.description}`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
console.log('');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Standalone functions for CLI usage
|
|
183
|
+
let discoveryInstance = null;
|
|
184
|
+
|
|
185
|
+
async function ensureDiscovery(config = null) {
|
|
186
|
+
if (!discoveryInstance || config) {
|
|
187
|
+
discoveryInstance = new TestDiscovery();
|
|
188
|
+
if (config?.execution?.testsDirectory) {
|
|
189
|
+
discoveryInstance.testsDirectory = resolve(process.cwd(), config.execution.testsDirectory);
|
|
190
|
+
}
|
|
191
|
+
await discoveryInstance.discoverTests();
|
|
192
|
+
}
|
|
193
|
+
return discoveryInstance;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function runSingleTestById(testId, config = null) {
|
|
197
|
+
const discovery = await ensureDiscovery(config);
|
|
198
|
+
const test = discovery.getTest(testId);
|
|
199
|
+
|
|
200
|
+
if (!test) {
|
|
201
|
+
console.error(`โ Test not found: ${testId}`);
|
|
202
|
+
console.log('๐ก Use "endorphin list" to see available tests');
|
|
203
|
+
if (isTestEnvironment()) {
|
|
204
|
+
return { success: false, message: `Test not found: ${testId}` };
|
|
205
|
+
}
|
|
206
|
+
safeExit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isTestEnvironment()) {
|
|
210
|
+
return { success: true, test };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(`๐งช Running test: ${test.id} - ${test.name}`);
|
|
214
|
+
|
|
215
|
+
const { EnhancedBrowserTestFramework } = await import('./browser-framework.js');
|
|
216
|
+
const framework = new EnhancedBrowserTestFramework(config);
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await framework.initialize();
|
|
220
|
+
const result = await framework.runSingleTest(test);
|
|
221
|
+
console.log(`โ
Test completed: ${result.status}`);
|
|
222
|
+
return { success: true, result };
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('โ Test failed:', error.message);
|
|
225
|
+
if (isTestEnvironment()) {
|
|
226
|
+
return { success: false, error: error.message };
|
|
227
|
+
}
|
|
228
|
+
safeExit(1);
|
|
229
|
+
} finally {
|
|
230
|
+
await framework.cleanup();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function runTestsByTag(tag, config = null) {
|
|
235
|
+
const discovery = await ensureDiscovery(config);
|
|
236
|
+
const tests = discovery.getTestsByTag(tag);
|
|
237
|
+
|
|
238
|
+
if (tests.length === 0) {
|
|
239
|
+
console.error(`โ No tests found with tag: ${tag}`);
|
|
240
|
+
if (isTestEnvironment()) {
|
|
241
|
+
return { success: false, message: `No tests found with tag: ${tag}` };
|
|
242
|
+
}
|
|
243
|
+
safeExit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (isTestEnvironment()) {
|
|
247
|
+
return { success: true, tests };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(`๐ท๏ธ Running ${tests.length} test(s) with tag: ${tag}`);
|
|
251
|
+
|
|
252
|
+
const { EnhancedBrowserTestFramework } = await import('./browser-framework.js');
|
|
253
|
+
const framework = new EnhancedBrowserTestFramework(config);
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
await framework.initialize();
|
|
257
|
+
|
|
258
|
+
const results = [];
|
|
259
|
+
for (const test of tests) {
|
|
260
|
+
console.log(`\n๐งช Running: ${test.id} - ${test.name}`);
|
|
261
|
+
const result = await framework.runSingleTest(test);
|
|
262
|
+
console.log(`โ
Result: ${result.status}`);
|
|
263
|
+
results.push(result);
|
|
264
|
+
}
|
|
265
|
+
return { success: true, results };
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.error('โ Test execution failed:', error.message);
|
|
268
|
+
if (isTestEnvironment()) {
|
|
269
|
+
return { success: false, error: error.message };
|
|
270
|
+
}
|
|
271
|
+
safeExit(1);
|
|
272
|
+
} finally {
|
|
273
|
+
await framework.cleanup();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function runTestsByPriority(priority, config = null) {
|
|
278
|
+
const discovery = await ensureDiscovery(config);
|
|
279
|
+
const tests = discovery.getTestsByPriority(priority);
|
|
280
|
+
|
|
281
|
+
if (tests.length === 0) {
|
|
282
|
+
console.error(`โ No tests found with priority: ${priority}`);
|
|
283
|
+
if (isTestEnvironment()) {
|
|
284
|
+
return { success: false, message: `No tests found with priority: ${priority}` };
|
|
285
|
+
}
|
|
286
|
+
safeExit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (isTestEnvironment()) {
|
|
290
|
+
return { success: true, tests };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(`๐ฏ Running ${tests.length} test(s) with priority: ${priority}`);
|
|
294
|
+
|
|
295
|
+
const { EnhancedBrowserTestFramework } = await import('./browser-framework.js');
|
|
296
|
+
const framework = new EnhancedBrowserTestFramework(config);
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
await framework.initialize();
|
|
300
|
+
|
|
301
|
+
const results = [];
|
|
302
|
+
for (const test of tests) {
|
|
303
|
+
console.log(`\n๐งช Running: ${test.id} - ${test.name}`);
|
|
304
|
+
const result = await framework.runSingleTest(test);
|
|
305
|
+
console.log(`โ
Result: ${result.status}`);
|
|
306
|
+
results.push(result);
|
|
307
|
+
}
|
|
308
|
+
return { success: true, results };
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error('โ Test execution failed:', error.message);
|
|
311
|
+
if (isTestEnvironment()) {
|
|
312
|
+
return { success: false, error: error.message };
|
|
313
|
+
}
|
|
314
|
+
safeExit(1);
|
|
315
|
+
} finally {
|
|
316
|
+
await framework.cleanup();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function runAllTests(config = null) {
|
|
321
|
+
const discovery = await ensureDiscovery(config);
|
|
322
|
+
const tests = discovery.getAllTests();
|
|
323
|
+
|
|
324
|
+
if (tests.length === 0) {
|
|
325
|
+
console.error('โ No tests found');
|
|
326
|
+
if (isTestEnvironment()) {
|
|
327
|
+
return { success: false, message: 'No tests found' };
|
|
328
|
+
}
|
|
329
|
+
safeExit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (isTestEnvironment()) {
|
|
333
|
+
return { success: true, tests };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log(`๐ Running all ${tests.length} test(s)`);
|
|
337
|
+
|
|
338
|
+
const { EnhancedBrowserTestFramework } = await import('./browser-framework.js');
|
|
339
|
+
const framework = new EnhancedBrowserTestFramework(config);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
await framework.initialize();
|
|
343
|
+
|
|
344
|
+
let passed = 0;
|
|
345
|
+
let failed = 0;
|
|
346
|
+
|
|
347
|
+
for (const test of tests) {
|
|
348
|
+
console.log(`\n๐งช Running: ${test.id} - ${test.name}`);
|
|
349
|
+
try {
|
|
350
|
+
const result = await framework.runSingleTest(test);
|
|
351
|
+
console.log(`โ
Result: ${result.status}`);
|
|
352
|
+
passed++;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error(`โ Failed: ${error.message}`);
|
|
355
|
+
failed++;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log(`\n๐ Test Summary: ${passed} passed, ${failed} failed`);
|
|
360
|
+
return { success: true, passed, failed, total: tests.length };
|
|
361
|
+
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.error('โ Test execution failed:', error.message);
|
|
364
|
+
if (isTestEnvironment()) {
|
|
365
|
+
return { success: false, error: error.message };
|
|
366
|
+
}
|
|
367
|
+
safeExit(1);
|
|
368
|
+
} finally {
|
|
369
|
+
await framework.cleanup();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export async function listAllTests(config = null) {
|
|
374
|
+
const discovery = await ensureDiscovery(config);
|
|
375
|
+
const tests = discovery.getAllTests();
|
|
376
|
+
|
|
377
|
+
if (isTestEnvironment()) {
|
|
378
|
+
return { success: true, tests };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
discovery.listTests();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Discover tests and return them as an array
|
|
386
|
+
* @param {Object} config - Configuration object
|
|
387
|
+
* @returns {Array} Array of discovered tests
|
|
388
|
+
*/
|
|
389
|
+
export async function discoverTests(config) {
|
|
390
|
+
const discovery = new TestDiscovery();
|
|
391
|
+
if (config?.execution?.testsDirectory) {
|
|
392
|
+
discovery.testsDirectory = resolve(process.cwd(), config.execution.testsDirectory);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
await discovery.discoverTests();
|
|
397
|
+
return Array.from(discovery.tests.values());
|
|
398
|
+
} catch (error) {
|
|
399
|
+
console.error('โ Error discovering tests:', error.message);
|
|
400
|
+
return [];
|
|
401
|
+
}
|
|
402
|
+
}
|