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,83 @@
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
+ // Simple test to validate the enhanced interactive recorder setup
18
+ import { EnhancedBrowserTestFramework } from '../index.js';
19
+ import { TestRecorder } from '../core/test-recorder.js';
20
+
21
+ async function testRecorderSetup() {
22
+ console.log('๐Ÿงช Testing Enhanced Interactive Recorder Setup...');
23
+ console.log('โ•'.repeat(50));
24
+
25
+ try {
26
+ // Test 1: Framework instantiation
27
+ console.log('1๏ธโƒฃ Testing framework instantiation...');
28
+ const framework = new EnhancedBrowserTestFramework();
29
+ console.log('โœ… Framework created successfully');
30
+
31
+ // Test 2: Test recorder instantiation
32
+ console.log('2๏ธโƒฃ Testing recorder instantiation...');
33
+ const testData = {
34
+ id: 'TEST-001',
35
+ name: 'Validation Test',
36
+ description: 'Testing recorder setup',
37
+ site: 'https://example.com'
38
+ };
39
+ const recorder = new TestRecorder(framework, testData);
40
+ console.log('โœ… Recorder created successfully');
41
+
42
+ // Test 3: Natural language command parsing
43
+ console.log('3๏ธโƒฃ Testing command parsing...');
44
+
45
+ // Mock the framework with basic structure
46
+ framework.tools = {
47
+ click: async (params) => `Clicked ${params.selector}`,
48
+ fill: async (params) => `Filled ${params.selector} with ${params.value}`,
49
+ navigate: async (params) => `Navigated to ${params.url}`,
50
+ screenshot: async () => 'Screenshot taken'
51
+ };
52
+
53
+ // Test command parsing
54
+ const clickResult = framework.extractSelectorFromCommand('click login button');
55
+ console.log(` Click parsing: "${clickResult}"`);
56
+
57
+ const fillResult = framework.extractFillFromCommand('fill email with test@example.com');
58
+ console.log(` Fill parsing: selector="${fillResult.selector}", value="${fillResult.value}"`);
59
+
60
+ const urlResult = framework.extractUrlFromCommand('navigate to https://google.com');
61
+ console.log(` URL parsing: "${urlResult}"`);
62
+
63
+ console.log('โœ… Command parsing works correctly');
64
+
65
+ console.log('\n๐ŸŽ‰ All tests passed! Enhanced Interactive Recorder is ready to use.');
66
+ console.log('\nTo start recording:');
67
+ console.log(' npm run interactive');
68
+ console.log('\nOr run directly:');
69
+ console.log(' node framework/interactive/enhanced-interactive-recorder.js');
70
+
71
+ } catch (error) {
72
+ console.error('โŒ Test failed:', error.message);
73
+ console.error(error.stack);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ // Run if this file is executed directly
79
+ if (import.meta.url === `file://${process.argv[1]}`) {
80
+ testRecorderSetup().catch(console.error);
81
+ }
82
+
83
+ export { testRecorderSetup };
@@ -0,0 +1,47 @@
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 { EnhancedBrowserTestFramework } from '../index.js';
18
+
19
+ async function testModularFramework() {
20
+ console.log('๐Ÿงช Testing Modular Framework...');
21
+
22
+ const framework = new EnhancedBrowserTestFramework();
23
+
24
+ try {
25
+ // Initialize framework
26
+ await framework.initialize();
27
+ console.log('โœ… Framework initialized successfully');
28
+
29
+ // Test simple navigation
30
+ const result = await framework.runTask(
31
+ "Navigate to https://httpbin.org. Take a screenshot. Get simple page content to verify the site loaded. STOP - test completed.",
32
+ "Modular Framework Test"
33
+ );
34
+
35
+ console.log('โœ… Test completed:', result.status);
36
+ console.log('๐Ÿ“ Results saved to:', result.sessionDir);
37
+
38
+ } catch (error) {
39
+ console.error('โŒ Test failed:', error.message);
40
+ } finally {
41
+ await framework.cleanup();
42
+ console.log('๐Ÿงน Framework cleanup completed');
43
+ }
44
+ }
45
+
46
+ // Run the test
47
+ testModularFramework().catch(console.error);
@@ -0,0 +1,58 @@
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
+ // Verification script to test the updated test format and test-manager
18
+ import { TestManager } from '../core/test-manager.js';
19
+
20
+ async function verifyTestFormat() {
21
+ console.log("๐Ÿ”ง Verifying Test Format Updates...");
22
+ console.log("โ•".repeat(50));
23
+
24
+ try {
25
+ const testManager = new TestManager();
26
+ const tests = await testManager.loadTests();
27
+
28
+ console.log(`โœ… Successfully loaded ${tests.size} test cases`);
29
+ console.log("\n๐Ÿ“‹ Test Summary:");
30
+
31
+ // Verify each test has the new format
32
+ for (const [testId, test] of tests) {
33
+ const hasUID = test.testData && test.testData.uid;
34
+ const hasSimpleTask = test.task && !test.task.includes("Step-by-step");
35
+ const hasExportConst = true; // Already loaded, so format is correct
36
+
37
+ console.log(`\n๐Ÿ”น ${testId}: ${test.name}`);
38
+ console.log(` โœ… Export Format: ${hasExportConst ? 'Updated (export const)' : 'Old (export default)'}`);
39
+ console.log(` โœ… Task Format: ${hasSimpleTask ? 'Simplified' : 'Detailed Steps'}`);
40
+ console.log(` โœ… Test Data: ${hasUID ? 'Enhanced with UID' : 'Basic'}`);
41
+ console.log(` ๐ŸŽฏ Priority: ${test.priority}`);
42
+ console.log(` ๐Ÿท๏ธ Tags: ${test.tags.join(', ')}`);
43
+ }
44
+
45
+ console.log("\n๐ŸŽ‰ All tests verified successfully!");
46
+ console.log("โœ… Export format updated to 'export const'");
47
+ console.log("โœ… Task format simplified to natural language");
48
+ console.log("โœ… Test data enhanced with unique identifiers");
49
+ console.log("โœ… Test-manager handles both export formats");
50
+
51
+ } catch (error) {
52
+ console.error("โŒ Verification failed:", error);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ // Run verification
58
+ verifyTestFormat().catch(console.error);
@@ -0,0 +1,67 @@
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 { tool } from '@langchain/core/tools';
18
+ import { z } from 'zod';
19
+
20
+ export function createGetPageContentTool(framework) {
21
+ return tool(async ({ includeTitle = true, maxLength = 8000 }) => {
22
+ const stepDesc = "Get page content for analysis";
23
+ console.log(`๐Ÿ“„ ${stepDesc}`);
24
+
25
+ try {
26
+ let content = '';
27
+
28
+ if (includeTitle) {
29
+ const title = await framework.page.title();
30
+ const url = framework.page.url();
31
+ content += `Page Title: ${title}\nURL: ${url}\n\n`;
32
+ }
33
+
34
+ const htmlContent = await framework.page.content();
35
+ const truncatedContent = htmlContent.length > maxLength
36
+ ? htmlContent.substring(0, maxLength) + '\n... (truncated for brevity)'
37
+ : htmlContent;
38
+ content += truncatedContent;
39
+
40
+ framework.logTestStep(stepDesc, 'getPageContent', { includeTitle, maxLength }, `Retrieved ${content.length} characters`, true);
41
+ return content;
42
+ } catch (error) {
43
+ framework.logTestStep(stepDesc, 'getPageContent', { includeTitle, maxLength }, error.message, false);
44
+ throw error;
45
+ }
46
+ }, {
47
+ name: 'getPageContent',
48
+ description: 'Get HTML content of the current page for analysis.',
49
+ schema: z.object({
50
+ includeTitle: z.boolean().optional(),
51
+ maxLength: z.number().optional(),
52
+ })
53
+ });
54
+ }
55
+
56
+ export function createGetSimplePageContentTool(framework) {
57
+ return tool(async () => {
58
+ console.log("๐Ÿ“„ Getting page content");
59
+ const content = await framework.page.content();
60
+ framework.logTestStep("Get simple page content", 'getSimplePageContent', {}, `Retrieved ${content.length} characters`, true);
61
+ return content;
62
+ }, {
63
+ name: 'getSimplePageContent',
64
+ description: 'Get the current HTML content of the page to analyze its structure.',
65
+ schema: z.object({})
66
+ });
67
+ }
@@ -0,0 +1,52 @@
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 { createNavigationTool } from './navigation.js';
18
+ import { createGetPageContentTool, createGetSimplePageContentTool } from './content.js';
19
+ import { createClickTool, createFillTool, createClearFieldTool } from './interaction.js';
20
+ import { createVerifyElementTool, createGetElementInfoTool } from './verification.js';
21
+ import { createWaitTool, createScreenshotTool } from './utilities.js';
22
+
23
+ /**
24
+ * Create all browser automation tools for the framework
25
+ * @param {EnhancedBrowserTestFramework} framework - Framework instance
26
+ * @returns {Array} Array of all configured tools
27
+ */
28
+ export function createAllTools(framework) {
29
+ return [
30
+ // Navigation tools
31
+ createNavigationTool(framework),
32
+
33
+ // Content analysis tools
34
+ createGetPageContentTool(framework),
35
+ createGetSimplePageContentTool(framework),
36
+
37
+ // Interaction tools
38
+ createClickTool(framework),
39
+ createFillTool(framework),
40
+ createClearFieldTool(framework),
41
+
42
+ // Verification tools
43
+ createVerifyElementTool(framework),
44
+ createGetElementInfoTool(framework),
45
+
46
+ // Utility tools
47
+ createWaitTool(framework),
48
+ createScreenshotTool(framework)
49
+ ];
50
+ }
51
+
52
+ export default createAllTools;
@@ -0,0 +1,180 @@
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 { tool } from '@langchain/core/tools';
18
+ import { z } from 'zod';
19
+
20
+ export function createClickTool(framework) {
21
+ return tool(async ({ selector, strategy = 'css', timeout = 10000, force = false }) => {
22
+ const stepDesc = `Click ${selector} using ${strategy} strategy`;
23
+ console.log(`๐Ÿ”˜ ${stepDesc}`);
24
+
25
+ try {
26
+ let locator;
27
+
28
+ switch (strategy) {
29
+ case 'text':
30
+ locator = framework.page.getByText(selector, { exact: false });
31
+ break;
32
+ case 'exact-text':
33
+ locator = framework.page.getByText(selector, { exact: true });
34
+ break;
35
+ case 'role':
36
+ const [role, name] = selector.split(':');
37
+ locator = framework.page.getByRole(role, { name });
38
+ break;
39
+ case 'placeholder':
40
+ locator = framework.page.getByPlaceholder(selector);
41
+ break;
42
+ case 'label':
43
+ locator = framework.page.getByLabel(selector);
44
+ break;
45
+ case 'title':
46
+ locator = framework.page.getByTitle(selector);
47
+ break;
48
+ case 'alt':
49
+ locator = framework.page.getByAltText(selector);
50
+ break;
51
+ default:
52
+ locator = framework.page.locator(selector);
53
+ }
54
+
55
+ // Check if element exists first
56
+ const count = await locator.count();
57
+ if (count === 0) {
58
+ const result = `Element ${selector} not found on page`;
59
+ framework.logTestStep(stepDesc, 'click', { selector, strategy, timeout, force }, result, false);
60
+ return `โŒ ${result}`;
61
+ }
62
+
63
+ await locator.waitFor({ state: 'visible', timeout });
64
+ await locator.click({ force });
65
+ await framework.takeStepScreenshot(`After clicking ${selector}`);
66
+
67
+ const result = `Successfully clicked ${selector} using ${strategy} strategy`;
68
+ framework.logTestStep(stepDesc, 'click', { selector, strategy, timeout, force }, result, true);
69
+ return result;
70
+ } catch (error) {
71
+ await framework.takeStepScreenshot(`Failed to click ${selector}`);
72
+ framework.logTestStep(stepDesc, 'click', { selector, strategy, timeout, force }, error.message, false);
73
+ return `โŒ Error clicking ${selector}: ${error.message}`;
74
+ }
75
+ }, {
76
+ name: 'click',
77
+ description: 'Click any element with multiple selection strategies. For buttons with text like "Log In", "Sign In", "Submit", use strategy="text" and selector="Log In". For CSS selectors use strategy="css".',
78
+ schema: z.object({
79
+ selector: z.string().describe("Element selector - for buttons use the button text (e.g. 'Log In', 'Sign In'), for CSS use actual selector"),
80
+ strategy: z.enum(['css', 'text', 'exact-text', 'role', 'placeholder', 'label', 'title', 'alt']).optional().describe("Selection strategy - use 'text' for button text, 'css' for CSS selectors"),
81
+ timeout: z.number().optional(),
82
+ force: z.boolean().optional(),
83
+ })
84
+ });
85
+ }
86
+
87
+ export function createFillTool(framework) {
88
+ return tool(async ({ selector, value, strategy = 'fill', clearFirst = true, pressEnter = false }) => {
89
+ const stepDesc = `Fill ${selector} with "${value}"`;
90
+ console.log(`๐Ÿ“ ${stepDesc}`);
91
+
92
+ try {
93
+ await framework.page.waitForSelector(selector, { state: 'visible', timeout: 10000 });
94
+
95
+ if (clearFirst) {
96
+ // Focus and clear the field properly
97
+ await framework.page.focus(selector);
98
+ await framework.page.keyboard.press('Control+a');
99
+ await framework.page.keyboard.press('Delete');
100
+
101
+ // Wait a moment for the field to clear
102
+ await framework.page.waitForTimeout(100);
103
+ }
104
+
105
+ if (strategy === 'type') {
106
+ await framework.page.type(selector, value, { delay: 50 });
107
+ } else {
108
+ await framework.page.fill(selector, value);
109
+ }
110
+
111
+ if (pressEnter) {
112
+ await framework.page.keyboard.press('Enter');
113
+ }
114
+
115
+ await framework.takeStepScreenshot(`After filling ${selector}`);
116
+
117
+ // Verify the value was set correctly
118
+ const actualValue = await framework.page.locator(selector).inputValue();
119
+ const success = actualValue === value;
120
+ const result = success
121
+ ? `Successfully filled ${selector} with "${value}"`
122
+ : `Filled ${selector} but value is "${actualValue}" instead of "${value}"`;
123
+
124
+ framework.logTestStep(stepDesc, 'fill', { selector, value, strategy, clearFirst, pressEnter }, result, success);
125
+ return success ? `โœ… ${result}` : `โš ๏ธ ${result}`;
126
+ } catch (error) {
127
+ await framework.takeStepScreenshot(`Failed to fill ${selector}`);
128
+ framework.logTestStep(stepDesc, 'fill', { selector, value, strategy, clearFirst, pressEnter }, error.message, false);
129
+ return `โŒ Error filling ${selector}: ${error.message}`;
130
+ }
131
+ }, {
132
+ name: 'fill',
133
+ description: 'Fill any input field with text. Use common selectors like input[type="email"], input[name="email"], #email for email fields, input[type="password"], input[name="password"], #password for password fields.',
134
+ schema: z.object({
135
+ selector: z.string().describe("CSS selector of input field - use input[type='email'] for email, input[type='password'] for password"),
136
+ value: z.string().describe("Text to enter"),
137
+ strategy: z.enum(['fill', 'type']).optional(),
138
+ clearFirst: z.boolean().optional(),
139
+ pressEnter: z.boolean().optional(),
140
+ })
141
+ });
142
+ }
143
+
144
+ export function createClearFieldTool(framework) {
145
+ return tool(async ({ selector }) => {
146
+ const stepDesc = `Clear field: ${selector}`;
147
+ console.log(`๐Ÿงน ${stepDesc}`);
148
+
149
+ try {
150
+ await framework.page.waitForSelector(selector, { state: 'visible', timeout: 10000 });
151
+
152
+ // Focus the field first
153
+ await framework.page.focus(selector);
154
+
155
+ // Select all content and delete
156
+ await framework.page.keyboard.press('Control+a');
157
+ await framework.page.keyboard.press('Delete');
158
+
159
+ // Verify field is cleared
160
+ const value = await framework.page.locator(selector).inputValue();
161
+ const isCleared = value === '';
162
+
163
+ const result = isCleared
164
+ ? `Successfully cleared field ${selector}`
165
+ : `Field ${selector} still contains: "${value}"`;
166
+
167
+ framework.logTestStep(stepDesc, 'clearField', { selector }, result, isCleared);
168
+ return isCleared ? `โœ… ${result}` : `โš ๏ธ ${result}`;
169
+ } catch (error) {
170
+ framework.logTestStep(stepDesc, 'clearField', { selector }, error.message, false);
171
+ return `โŒ Error clearing field ${selector}: ${error.message}`;
172
+ }
173
+ }, {
174
+ name: 'clearField',
175
+ description: 'Clear an input field completely.',
176
+ schema: z.object({
177
+ selector: z.string().describe("CSS selector of the input field to clear"),
178
+ })
179
+ });
180
+ }
@@ -0,0 +1,43 @@
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 { tool } from '@langchain/core/tools';
18
+ import { z } from 'zod';
19
+
20
+ export function createNavigationTool(framework) {
21
+ return tool(async ({ location, waitUntil = 'domcontentloaded' }) => {
22
+ const stepDesc = `Navigate to: ${location}`;
23
+ console.log(`๐ŸŒ ${stepDesc}`);
24
+
25
+ try {
26
+ await framework.page.goto(location, { waitUntil, timeout: 60000 });
27
+ await framework.takeStepScreenshot(`Page loaded: ${location}`);
28
+
29
+ framework.logTestStep(stepDesc, 'navigate', { location, waitUntil }, `Successfully navigated to: ${location}`, true);
30
+ return `Successfully navigated to: ${location}`;
31
+ } catch (error) {
32
+ framework.logTestStep(stepDesc, 'navigate', { location, waitUntil }, error.message, false);
33
+ throw error;
34
+ }
35
+ }, {
36
+ name: 'navigate',
37
+ description: 'Navigate to a URL with enhanced options.',
38
+ schema: z.object({
39
+ location: z.string().describe("URL to navigate to"),
40
+ waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle']).optional(),
41
+ })
42
+ });
43
+ }
@@ -0,0 +1,99 @@
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 { tool } from '@langchain/core/tools';
18
+ import { z } from 'zod';
19
+ import path from 'path';
20
+
21
+ export function createWaitTool(framework) {
22
+ return tool(async ({ milliseconds = 500, reason, selector, state = 'visible' }) => {
23
+ const stepDesc = selector
24
+ ? `Wait for ${selector} to be ${state}`
25
+ : `Wait for ${milliseconds}ms${reason ? ` (${reason})` : ''}`;
26
+ console.log(`โฑ๏ธ ${stepDesc}`);
27
+
28
+ try {
29
+ if (selector) {
30
+ await framework.page.waitForSelector(selector, { state, timeout: milliseconds });
31
+ const result = `Element ${selector} is now ${state}`;
32
+ framework.logTestStep(stepDesc, 'wait', { milliseconds, reason, selector, state }, result, true);
33
+ return `โœ… ${result}`;
34
+ } else {
35
+ await framework.page.waitForTimeout(milliseconds);
36
+ const result = `Waited for ${milliseconds}ms${reason ? ` - ${reason}` : ''}`;
37
+ framework.logTestStep(stepDesc, 'wait', { milliseconds, reason, selector, state }, result, true);
38
+ return result;
39
+ }
40
+ } catch (error) {
41
+ framework.logTestStep(stepDesc, 'wait', { milliseconds, reason, selector, state }, error.message, false);
42
+ return `โŒ Timeout waiting for ${selector} to be ${state}`;
43
+ }
44
+ }, {
45
+ name: 'wait',
46
+ description: 'Wait for time or element state.',
47
+ schema: z.object({
48
+ milliseconds: z.number().optional(),
49
+ reason: z.string().optional(),
50
+ selector: z.string().optional(),
51
+ state: z.enum(['visible', 'hidden', 'attached', 'detached']).optional(),
52
+ })
53
+ });
54
+ }
55
+
56
+ export function createScreenshotTool(framework) {
57
+ return tool(async ({ name, selector, fullPage = false }) => {
58
+ if (!framework.currentTestSession) {
59
+ framework.currentTestSession.screenshotCounter++;
60
+ }
61
+
62
+ const filename = name || `manual-screenshot-${Date.now()}.png`;
63
+ const stepDesc = `Take screenshot: ${filename}`;
64
+ console.log(`๐Ÿ“ธ ${stepDesc}`);
65
+
66
+ try {
67
+ let filePath;
68
+ if (framework.currentTestSession) {
69
+ filePath = path.join(framework.currentTestSession.screenshotsDir, filename);
70
+ } else {
71
+ filePath = filename;
72
+ }
73
+
74
+ if (selector) {
75
+ await framework.page.locator(selector).screenshot({ path: filePath });
76
+ } else {
77
+ await framework.page.screenshot({ path: filePath, fullPage });
78
+ }
79
+
80
+ const result = selector
81
+ ? `Screenshot of ${selector} saved as ${filename}`
82
+ : `${fullPage ? 'Full page' : 'Viewport'} screenshot saved as ${filename}`;
83
+
84
+ framework.logTestStep(stepDesc, 'screenshot', { name, selector, fullPage }, result, true);
85
+ return `๐Ÿ“ธ ${result}`;
86
+ } catch (error) {
87
+ framework.logTestStep(stepDesc, 'screenshot', { name, selector, fullPage }, error.message, false);
88
+ return `โŒ Error taking screenshot: ${error.message}`;
89
+ }
90
+ }, {
91
+ name: 'screenshot',
92
+ description: 'Take screenshot for documentation/debugging.',
93
+ schema: z.object({
94
+ name: z.string().optional(),
95
+ selector: z.string().optional(),
96
+ fullPage: z.boolean().optional(),
97
+ })
98
+ });
99
+ }