blob: b3168da0b1f7865dee4cd8f827b7cd52b08e24b0 [file] [log] [blame]
Philip Zeyliger33d282f2025-05-03 04:01:54 +00001package browse
2
3import (
4 "context"
5 "encoding/json"
6 "os"
7 "slices"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/chromedp/chromedp"
13 "sketch.dev/llm"
14)
15
16func TestToolCreation(t *testing.T) {
17 // Create browser tools instance
18 tools := NewBrowseTools(context.Background())
19
20 // Test each tool has correct name and description
21 toolTests := []struct {
22 tool *llm.Tool
23 expectedName string
24 shortDesc string
25 requiredProps []string
26 }{
27 {tools.NewNavigateTool(), "browser_navigate", "Navigate", []string{"url"}},
28 {tools.NewClickTool(), "browser_click", "Click", []string{"selector"}},
29 {tools.NewTypeTool(), "browser_type", "Type", []string{"selector", "text"}},
30 {tools.NewWaitForTool(), "browser_wait_for", "Wait", []string{"selector"}},
31 {tools.NewGetTextTool(), "browser_get_text", "Get", []string{"selector"}},
32 {tools.NewEvalTool(), "browser_eval", "Evaluate", []string{"expression"}},
33 {tools.NewScreenshotTool(), "browser_screenshot", "Take", nil},
34 {tools.NewScrollIntoViewTool(), "browser_scroll_into_view", "Scroll", []string{"selector"}},
35 }
36
37 for _, tt := range toolTests {
38 t.Run(tt.expectedName, func(t *testing.T) {
39 if tt.tool.Name != tt.expectedName {
40 t.Errorf("expected name %q, got %q", tt.expectedName, tt.tool.Name)
41 }
42
43 if !strings.Contains(tt.tool.Description, tt.shortDesc) {
44 t.Errorf("description %q should contain %q", tt.tool.Description, tt.shortDesc)
45 }
46
47 // Verify schema has required properties
48 if len(tt.requiredProps) > 0 {
49 var schema struct {
50 Required []string `json:"required"`
51 }
52 if err := json.Unmarshal(tt.tool.InputSchema, &schema); err != nil {
53 t.Fatalf("failed to unmarshal schema: %v", err)
54 }
55
56 for _, prop := range tt.requiredProps {
57 if !slices.Contains(schema.Required, prop) {
58 t.Errorf("property %q should be required", prop)
59 }
60 }
61 }
62 })
63 }
64}
65
66func TestGetAllTools(t *testing.T) {
67 // Create browser tools instance
68 tools := NewBrowseTools(context.Background())
69
70 // Get all tools
71 allTools := tools.GetAllTools()
72
73 // We should have 8 tools
74 if len(allTools) != 8 {
75 t.Errorf("expected 8 tools, got %d", len(allTools))
76 }
77
78 // Check that each tool has the expected name prefix
79 for _, tool := range allTools {
80 if !strings.HasPrefix(tool.Name, "browser_") {
81 t.Errorf("tool name %q does not have prefix 'browser_'", tool.Name)
82 }
83 }
84}
85
86// TestBrowserInitialization verifies that the browser can start correctly
87func TestBrowserInitialization(t *testing.T) {
88 // Skip long tests in short mode
89 if testing.Short() {
90 t.Skip("skipping browser initialization test in short mode")
91 }
92
93 // Create browser tools instance
94 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
95 defer cancel()
96
97 tools := NewBrowseTools(ctx)
98
99 // Initialize the browser
100 err := tools.Initialize()
101 if err != nil {
102 // If browser automation is not available, skip the test
103 if strings.Contains(err.Error(), "browser automation not available") {
104 t.Skip("Browser automation not available in this environment")
105 } else {
106 t.Fatalf("Failed to initialize browser: %v", err)
107 }
108 }
109
110 // Clean up
111 defer tools.Close()
112
113 // Get browser context to verify it's working
114 browserCtx, err := tools.GetBrowserContext()
115 if err != nil {
116 t.Fatalf("Failed to get browser context: %v", err)
117 }
118
119 // Try to navigate to a simple page
120 var title string
121 err = chromedp.Run(browserCtx,
122 chromedp.Navigate("about:blank"),
123 chromedp.Title(&title),
124 )
125 if err != nil {
126 t.Fatalf("Failed to navigate to about:blank: %v", err)
127 }
128
129 t.Logf("Successfully navigated to about:blank, title: %q", title)
130}
131
132// TestNavigateTool verifies that the navigate tool works correctly
133func TestNavigateTool(t *testing.T) {
134 // Skip long tests in short mode
135 if testing.Short() {
136 t.Skip("skipping navigate tool test in short mode")
137 }
138
139 // Create browser tools instance
140 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
141 defer cancel()
142
143 tools := NewBrowseTools(ctx)
144 defer tools.Close()
145
146 // Check if browser initialization works
147 if err := tools.Initialize(); err != nil {
148 if strings.Contains(err.Error(), "browser automation not available") {
149 t.Skip("Browser automation not available in this environment")
150 }
151 }
152
153 // Get the navigate tool
154 navTool := tools.NewNavigateTool()
155
156 // Create input for the navigate tool
157 input := map[string]string{"url": "https://example.com"}
158 inputJSON, _ := json.Marshal(input)
159
160 // Call the tool
161 result, err := navTool.Run(ctx, json.RawMessage(inputJSON))
162 if err != nil {
163 t.Fatalf("Error running navigate tool: %v", err)
164 }
165
166 // Verify the response is successful
167 var response struct {
168 Status string `json:"status"`
169 Error string `json:"error,omitempty"`
170 }
171
172 if err := json.Unmarshal([]byte(result), &response); err != nil {
173 t.Fatalf("Error unmarshaling response: %v", err)
174 }
175
176 if response.Status != "success" {
177 // If browser automation is not available, skip the test
178 if strings.Contains(response.Error, "browser automation not available") {
179 t.Skip("Browser automation not available in this environment")
180 } else {
181 t.Errorf("Expected status 'success', got '%s' with error: %s", response.Status, response.Error)
182 }
183 }
184
185 // Try to get the page title to verify the navigation worked
186 browserCtx, err := tools.GetBrowserContext()
187 if err != nil {
188 // If browser automation is not available, skip the test
189 if strings.Contains(err.Error(), "browser automation not available") {
190 t.Skip("Browser automation not available in this environment")
191 } else {
192 t.Fatalf("Failed to get browser context: %v", err)
193 }
194 }
195
196 var title string
197 err = chromedp.Run(browserCtx, chromedp.Title(&title))
198 if err != nil {
199 t.Fatalf("Failed to get page title: %v", err)
200 }
201
202 t.Logf("Successfully navigated to example.com, title: %q", title)
203 if title != "Example Domain" {
204 t.Errorf("Expected title 'Example Domain', got '%s'", title)
205 }
206}
207
208// TestScreenshotTool tests that the screenshot tool properly saves files
209func TestScreenshotTool(t *testing.T) {
210 // Create browser tools instance
211 ctx := context.Background()
212 tools := NewBrowseTools(ctx)
213
214 // Test SaveScreenshot function directly
215 testData := []byte("test image data")
216 id := tools.SaveScreenshot(testData)
217 if id == "" {
218 t.Fatal("SaveScreenshot returned empty ID")
219 }
220
221 // Get the file path and check if the file exists
222 filePath := GetScreenshotPath(id)
223 _, err := os.Stat(filePath)
224 if err != nil {
225 t.Fatalf("Failed to find screenshot file: %v", err)
226 }
227
228 // Read the file contents
229 contents, err := os.ReadFile(filePath)
230 if err != nil {
231 t.Fatalf("Failed to read screenshot file: %v", err)
232 }
233
234 // Check the file contents
235 if string(contents) != string(testData) {
236 t.Errorf("File contents don't match: expected %q, got %q", string(testData), string(contents))
237 }
238
239 // Clean up the test file
240 os.Remove(filePath)
241}