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.
Files changed (35) hide show
  1. package/LICENSE.md +209 -0
  2. package/README.md +474 -0
  3. package/bin/endorphin.js +256 -0
  4. package/examples/endorphin.config.js +22 -0
  5. package/examples/tests/QE-001-basic-login-test.js +18 -0
  6. package/examples/tests/sample-test.js +9 -0
  7. package/examples/tools/.gitkeep +0 -0
  8. package/framework/config/agent-config.js +53 -0
  9. package/framework/config/browser-config.js +53 -0
  10. package/framework/config/paths.js +40 -0
  11. package/framework/core/agent-setup.js +75 -0
  12. package/framework/core/browser-framework.js +766 -0
  13. package/framework/core/config-loader.js +309 -0
  14. package/framework/core/test-discovery.js +402 -0
  15. package/framework/core/test-manager.js +343 -0
  16. package/framework/core/test-recorder.js +302 -0
  17. package/framework/core/test-runner.js +133 -0
  18. package/framework/core/test-session.js +98 -0
  19. package/framework/index.js +44 -0
  20. package/framework/interactive/enhanced-interactive-recorder.js +223 -0
  21. package/framework/interactive/index.js +18 -0
  22. package/framework/interactive/interactive-test-clean.js +33 -0
  23. package/framework/interactive/interactive-test.js +33 -0
  24. package/framework/test-framework.js +29 -0
  25. package/framework/testing/index.js +15 -0
  26. package/framework/testing/test-interactive-recorder.js +83 -0
  27. package/framework/testing/test-modular-framework.js +47 -0
  28. package/framework/testing/verify-test-format.js +58 -0
  29. package/framework/tools/content.js +67 -0
  30. package/framework/tools/index.js +52 -0
  31. package/framework/tools/interaction.js +180 -0
  32. package/framework/tools/navigation.js +43 -0
  33. package/framework/tools/utilities.js +99 -0
  34. package/framework/tools/verification.js +84 -0
  35. package/package.json +84 -0
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Endorphin AI CLI - E2E Testing Reinvented with AI
5
+ */
6
+
7
+ import { fileURLToPath } from 'url';
8
+ import { dirname, join } from 'path';
9
+ import { readFileSync } from 'fs';
10
+ import { getConfig } from '../framework/core/config-loader.js';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // Get package info
16
+ const packagePath = join(__dirname, '..', 'package.json');
17
+ const packageInfo = JSON.parse(readFileSync(packagePath, 'utf8'));
18
+
19
+ const args = process.argv.slice(2);
20
+
21
+ // Parse CLI flags into configuration overrides
22
+ function parseCliFlags(args) {
23
+ const flags = {};
24
+
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ const nextArg = args[i + 1];
28
+
29
+ switch (arg) {
30
+ case '--headless':
31
+ flags.headless = true;
32
+ break;
33
+ case '--no-headless':
34
+ flags.headless = false;
35
+ break;
36
+ case '--viewport':
37
+ if (nextArg && nextArg.includes('x')) {
38
+ const [width, height] = nextArg.split('x').map(Number);
39
+ flags.viewport = { width, height };
40
+ i++; // Skip next argument
41
+ }
42
+ break;
43
+ case '--timeout':
44
+ if (nextArg && !isNaN(nextArg)) {
45
+ flags.timeout = parseInt(nextArg, 10);
46
+ i++;
47
+ }
48
+ break;
49
+ case '--parallel':
50
+ if (nextArg && !isNaN(nextArg)) {
51
+ flags.parallel = parseInt(nextArg, 10);
52
+ i++;
53
+ }
54
+ break;
55
+ case '--model':
56
+ if (nextArg) {
57
+ flags.model = nextArg;
58
+ i++;
59
+ }
60
+ break;
61
+ case '--env':
62
+ case '--environment':
63
+ if (nextArg) {
64
+ flags.environment = nextArg;
65
+ i++;
66
+ }
67
+ break;
68
+ case '--base-url':
69
+ if (nextArg) {
70
+ flags.baseUrl = nextArg;
71
+ i++;
72
+ }
73
+ break;
74
+ case '--temperature':
75
+ if (nextArg && !isNaN(nextArg)) {
76
+ flags.temperature = parseFloat(nextArg);
77
+ i++;
78
+ }
79
+ break;
80
+ case '--retries':
81
+ if (nextArg && !isNaN(nextArg)) {
82
+ flags.maxRetries = parseInt(nextArg, 10);
83
+ i++;
84
+ }
85
+ break;
86
+ case '--tests-dir':
87
+ if (nextArg) {
88
+ flags.testsDirectory = nextArg;
89
+ i++;
90
+ }
91
+ break;
92
+ case '--data-dir':
93
+ if (nextArg) {
94
+ flags.dataDirectory = nextArg;
95
+ i++;
96
+ }
97
+ break;
98
+ }
99
+ }
100
+
101
+ return flags;
102
+ }
103
+
104
+ // Show help
105
+ function showHelp() {
106
+ console.log(`
107
+ 🎉 Endorphin AI v${packageInfo.version} - E2E Testing Reinvented with AI
108
+
109
+ Usage:
110
+ endorphin <command> [options]
111
+
112
+ Commands:
113
+ run test <test-id> Run a specific test (e.g., QE-001)
114
+ run test all Run all tests
115
+ run test --tag <tag> Run tests by tag (e.g., authentication)
116
+ run test --priority <level> Run tests by priority (High, Medium, Low)
117
+ run test-recorder Start interactive test recorder
118
+ list List all available tests
119
+ help Show this help message
120
+
121
+ Options:
122
+ --headless Run browser in headless mode
123
+ --no-headless Run browser with visible UI
124
+ --viewport <WxH> Set browser viewport (e.g., 1920x1080)
125
+ --timeout <ms> Set test timeout in milliseconds
126
+ --parallel <n> Run tests in parallel (default: 1)
127
+ --model <name> Set AI model to use (e.g., gpt-4o-mini)
128
+ --env <environment> Set environment (development/staging/production)
129
+
130
+ Examples:
131
+ endorphin run test QE-001 # Run specific test
132
+ endorphin run test all --headless # Run all tests headless
133
+ endorphin run test --tag smoke --parallel 3 # Run smoke tests in parallel
134
+ endorphin run test --priority High --env staging # Run high priority tests on staging
135
+ endorphin run test-recorder # Start test recorder
136
+ endorphin list # Show all available tests
137
+
138
+ Configuration:
139
+ Create endorphin.config.js in your project root for default settings
140
+ CLI flags override configuration file settings
141
+ Set OPENAI_API_KEY in your .env file or environment variables
142
+
143
+ Documentation: https://github.com/andrewnovykov/endorphin-ai#readme
144
+ `);
145
+ }
146
+
147
+ // Main CLI handler
148
+ async function main() {
149
+ try {
150
+ if (args.length === 0 || args.includes('--help') || args.includes('-h') || args.includes('help')) {
151
+ showHelp();
152
+ process.exit(0);
153
+ }
154
+
155
+ if (args.includes('--version') || args.includes('-v')) {
156
+ console.log(`Endorphin AI v${packageInfo.version}`);
157
+ process.exit(0);
158
+ }
159
+
160
+ // Load configuration with CLI flag overrides
161
+ const cliFlags = parseCliFlags(args);
162
+ const config = await getConfig({ cwd: process.cwd(), cliFlags });
163
+
164
+ // Debug: show loaded config if --debug flag is present
165
+ if (args.includes('--debug')) {
166
+ console.log('🔧 Loaded configuration:', JSON.stringify(config, null, 2));
167
+ }
168
+
169
+ const command = args[0];
170
+ const subcommand = args[1];
171
+ const target = args[2];
172
+
173
+ // Handle list command
174
+ if (command === 'list') {
175
+ console.log('📋 Available Tests:');
176
+ const { listAllTests } = await import('../framework/core/test-discovery.js');
177
+ await listAllTests(config);
178
+ process.exit(0);
179
+ }
180
+
181
+ // Handle run commands
182
+ if (command === 'run') {
183
+ if (subcommand === 'test-recorder') {
184
+ console.log('🎬 Starting Interactive Test Recorder...');
185
+ const { runInteractiveRecorder } = await import('../framework/interactive/enhanced-interactive-recorder.js');
186
+ await runInteractiveRecorder(config);
187
+ process.exit(0);
188
+ }
189
+
190
+ if (subcommand === 'test') {
191
+ // Check for flags
192
+ if (args.includes('--tag')) {
193
+ const tagIndex = args.indexOf('--tag');
194
+ const tag = args[tagIndex + 1];
195
+ if (!tag) {
196
+ console.error('❌ Error: Please specify a tag value');
197
+ process.exit(1);
198
+ }
199
+ console.log(`🏷️ Running tests with tag: ${tag}`);
200
+ const { runTestsByTag } = await import('../framework/core/test-discovery.js');
201
+ await runTestsByTag(tag, config);
202
+ process.exit(0);
203
+ }
204
+
205
+ if (args.includes('--priority')) {
206
+ const priorityIndex = args.indexOf('--priority');
207
+ const priority = args[priorityIndex + 1];
208
+ if (!priority) {
209
+ console.error('❌ Error: Please specify a priority value');
210
+ process.exit(1);
211
+ }
212
+ console.log(`🎯 Running tests with priority: ${priority}`);
213
+ const { runTestsByPriority } = await import('../framework/core/test-discovery.js');
214
+ await runTestsByPriority(priority, config);
215
+ process.exit(0);
216
+ }
217
+
218
+ // Handle specific test or "all"
219
+ if (target === 'all') {
220
+ console.log('🚀 Running all tests...');
221
+ const { runAllTests } = await import('../framework/core/test-discovery.js');
222
+ await runAllTests(config);
223
+ process.exit(0);
224
+ } else if (target) {
225
+ console.log(`🧪 Running test: ${target}`);
226
+ const { runSingleTestById } = await import('../framework/core/test-discovery.js');
227
+ await runSingleTestById(target, config);
228
+ process.exit(0);
229
+ } else {
230
+ console.error('❌ Error: Please specify a test ID or "all" (e.g., endorphin run test QE-001)');
231
+ process.exit(1);
232
+ }
233
+ }
234
+
235
+ console.error(`❌ Unknown run command: ${subcommand}`);
236
+ console.log('Use "endorphin help" for usage information');
237
+ process.exit(1);
238
+ }
239
+
240
+ console.error(`❌ Unknown command: ${command}`);
241
+ console.log('Use "endorphin help" for usage information');
242
+ process.exit(1);
243
+
244
+ } catch (error) {
245
+ console.error('❌ Error:', error.message);
246
+
247
+ if (error.message.includes('OPENAI_API_KEY')) {
248
+ console.log('\n💡 Tip: Make sure to set your OPENAI_API_KEY in your .env file or environment variables');
249
+ }
250
+
251
+ process.exit(1);
252
+ }
253
+ }
254
+
255
+ // Run CLI
256
+ main();
@@ -0,0 +1,22 @@
1
+ export default {
2
+ browser: {
3
+ headless: false, // Make sure this is false to see browser
4
+ viewport: { width: 1280, height: 720 },
5
+ timeout: 30000
6
+ },
7
+
8
+ // Results configuration
9
+ results: {
10
+ directory: "./test-results",
11
+ keepHistory: 10,
12
+ format: ["json", "html"],
13
+ screenshots: true,
14
+ recordVideo: false
15
+ },
16
+
17
+ ai: {
18
+ model: "gpt-4o",
19
+ maxRetries: 3,
20
+ temperature: 0.1
21
+ }
22
+ };
@@ -0,0 +1,18 @@
1
+ // QE-001: Basic Login Test
2
+ // Description: Test the login functionality with valid credentials
3
+ // Priority: High
4
+ // Tags: authentication, login, smoke
5
+
6
+ export const QE_001 = {
7
+ id: "QE-001",
8
+ name: "Basic Login Test",
9
+ description: "Test the login functionality with valid credentials",
10
+ priority: "High",
11
+ tags: ["authentication", "login", "smoke"],
12
+ site: "https://qafromla.herokuapp.com/",
13
+ testData: {
14
+ email: "papapin888@gmail.com",
15
+ password: "lalalend"
16
+ },
17
+ task: "Navigate to https://qafromla.herokuapp.com/, click on 'Log In' button, fill email field with 'papapin888@gmail.com', fill password field with 'lalalend', click 'Sign In' button, and verify successful login by checking for dashboard or user menu elements."
18
+ };
@@ -0,0 +1,9 @@
1
+ export const HEALTH_001 = {
2
+ id: "HEALTH-001",
3
+ name: "Framework Health Check",
4
+ description: "Verify basic framework functionality",
5
+ priority: "High",
6
+ tags: ["health", "smoke"],
7
+ site: "https://example.com",
8
+ task: "Navigate to https://example.com and verify page loads successfully"
9
+ };
File without changes
@@ -0,0 +1,53 @@
1
+ // Endorphin e2e AI test framework>
2
+ // Copyright (C) 2025 Redstudio Agency
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as
6
+ // published by the Free Software Foundation, either version 3 of the
7
+ // License, or (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import * as dotenv from "dotenv";
18
+
19
+ dotenv.config();
20
+
21
+ export const AGENT_CONFIG = {
22
+ // OpenAI Configuration
23
+ openai: {
24
+ apiKey: process.env.OPENAI_API_KEY,
25
+ modelName: "gpt-4o"
26
+ },
27
+
28
+ // Agent behavior settings
29
+ agent: {
30
+ recursionLimit: 150,
31
+ timeout: 5 * 60 * 1000, // 5 minutes
32
+
33
+ // Stop phrases that indicate test completion
34
+ stopPhrases: [
35
+ 'test completed',
36
+ 'verification complete',
37
+ 'login successful',
38
+ 'test finished',
39
+ 'done',
40
+ 'stop',
41
+ 'stop - test completed'
42
+ ]
43
+ },
44
+
45
+ // Test execution settings
46
+ execution: {
47
+ stepDelay: 2000, // 2 seconds between test steps
48
+ maxRetries: 3,
49
+ retryDelay: 1000
50
+ }
51
+ };
52
+
53
+ export default AGENT_CONFIG;
@@ -0,0 +1,53 @@
1
+ // Endorphin e2e AI test framework>
2
+ // Copyright (C) 2025 Redstudio Agency
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as
6
+ // published by the Free Software Foundation, either version 3 of the
7
+ // License, or (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+
18
+ import dotenv from 'dotenv';
19
+
20
+ // Load environment variables
21
+ dotenv.config();
22
+
23
+ export const BROWSER_CONFIG = {
24
+ // Browser launch options
25
+ launchOptions: {
26
+ headless: process.env.HEADLESS === 'true' ? true : false,
27
+ args: ['--start-maximized']
28
+ },
29
+
30
+ // Browser context options
31
+ contextOptions: {
32
+ viewport: { width: 1920, height: 1080 }
33
+ },
34
+
35
+ // Default timeouts (in milliseconds)
36
+ timeouts: {
37
+ navigation: 60000,
38
+ element: 10000,
39
+ screenshot: 5000,
40
+ testExecution: 5 * 60 * 1000 // 5 minutes
41
+ },
42
+
43
+ // Screenshot options
44
+ screenshot: {
45
+ fullPage: false,
46
+ type: 'png'
47
+ },
48
+
49
+ // Base URL for testing
50
+ baseUrl: process.env.BASE_URL || 'https://qafromla.herokuapp.com/'
51
+ };
52
+
53
+ export default BROWSER_CONFIG;
@@ -0,0 +1,40 @@
1
+ // Endorphin e2e AI test framework>
2
+ // Copyright (C) 2025 Redstudio Agency
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as
6
+ // published by the Free Software Foundation, either version 3 of the
7
+ // License, or (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import path from 'path';
18
+ import { fileURLToPath } from 'url';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // Get project root directory (go up from framework/config/ to project root)
24
+ const projectRoot = path.resolve(__dirname, '..', '..');
25
+
26
+ export const PATHS = {
27
+ // Project directories
28
+ PROJECT_ROOT: projectRoot,
29
+ TEST_RESULT_DIR: path.join(projectRoot, 'test-result'),
30
+ TEST_RECORDER_DIR: path.join(projectRoot, 'test-recorder'),
31
+ TESTS_DIR: path.join(projectRoot, 'tests'),
32
+
33
+ // Framework directories
34
+ FRAMEWORK_DIR: path.join(projectRoot, 'framework'),
35
+ TOOLS_DIR: path.join(projectRoot, 'framework', 'tools'),
36
+ CORE_DIR: path.join(projectRoot, 'framework', 'core'),
37
+ CONFIG_DIR: path.join(projectRoot, 'framework', 'config'),
38
+ };
39
+
40
+ export default PATHS;
@@ -0,0 +1,75 @@
1
+ // Endorphin e2e AI test framework>
2
+ // Copyright (C) 2025 Redstudio Agency
3
+
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU Affero General Public License as
6
+ // published by the Free Software Foundation, either version 3 of the
7
+ // License, or (at your option) any later version.
8
+
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU Affero General Public License for more details.
13
+
14
+ // You should have received a copy of the GNU Affero General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ import { ChatOpenAI } from "@langchain/openai";
18
+ import { ToolNode } from "@langchain/langgraph/prebuilt";
19
+ import { StateGraph, MessagesAnnotation } from "@langchain/langgraph";
20
+ import { AGENT_CONFIG } from '../config/agent-config.js';
21
+
22
+ /**
23
+ * Setup AI agent with tools and workflow
24
+ * @param {Array} tools - Array of browser automation tools
25
+ * @returns {Object} Compiled agent workflow
26
+ */
27
+ export async function setupAgent(tools) {
28
+ console.log("🤖 Configuring AI agent with tools...");
29
+
30
+ const toolNode = new ToolNode(tools);
31
+
32
+ const model = new ChatOpenAI({
33
+ openAIApiKey: AGENT_CONFIG.openai.apiKey,
34
+ modelName: AGENT_CONFIG.openai.modelName,
35
+ }).bindTools(tools);
36
+
37
+ function shouldContinue({ messages }) {
38
+ const lastMessage = messages[messages.length - 1];
39
+
40
+ // Check for explicit stop conditions in the message content
41
+ const content = lastMessage.content?.toLowerCase() || '';
42
+ const hasStopPhrase = AGENT_CONFIG.agent.stopPhrases.some(phrase =>
43
+ content.includes(phrase)
44
+ );
45
+
46
+ if (hasStopPhrase) {
47
+ console.log(`🛑 Stop condition detected: ${content}`);
48
+ return "__end__";
49
+ }
50
+
51
+ if (lastMessage.tool_calls?.length) {
52
+ return "tools";
53
+ }
54
+ return "__end__";
55
+ }
56
+
57
+ async function callModel(state) {
58
+ const response = await model.invoke(state.messages);
59
+ return { messages: [response] };
60
+ }
61
+
62
+ const workflow = new StateGraph(MessagesAnnotation)
63
+ .addNode("agent", callModel)
64
+ .addEdge("__start__", "agent")
65
+ .addNode("tools", toolNode)
66
+ .addEdge("tools", "agent")
67
+ .addConditionalEdges("agent", shouldContinue);
68
+
69
+ const agent = workflow.compile({
70
+ recursionLimit: AGENT_CONFIG.agent.recursionLimit,
71
+ });
72
+
73
+ console.log("✅ AI agent configured successfully");
74
+ return agent;
75
+ }