diff --git a/LLM_CODING_AGENT_GUIDE.md b/LLM_CODING_AGENT_GUIDE.md index cb7f176..6a5255b 100644 --- a/LLM_CODING_AGENT_GUIDE.md +++ b/LLM_CODING_AGENT_GUIDE.md @@ -130,9 +130,13 @@ cremote submit-form --selector="form#login-form" ### File Upload Testing ```bash -# Upload files to file inputs +# Upload files to file inputs (automatically transfers to daemon container first) cremote upload-file --selector="input[type=file]" --file="/path/to/test-file.pdf" -cremote upload-file --selector="#profile-photo" --file="/tmp/test-image.jpg" +cremote upload-file --selector="#profile-photo" --file="/home/user/test-image.jpg" + +# The command automatically: +# 1. Transfers the file from local machine to daemon container +# 2. Uploads the file to the web form input element ``` ### Element Interaction diff --git a/README.md b/README.md index ad67b2b..be2a417 100644 --- a/README.md +++ b/README.md @@ -168,9 +168,15 @@ Any other value will uncheck the checkbox or deselect the radio button. cremote upload-file --tab="" --selector="input[type=file]" --file="/path/to/file.jpg" [--selection-timeout=5] [--action-timeout=5] ``` +This command automatically: +1. **Transfers the file** from your local machine to the daemon container (if running in a container) +2. **Uploads the file** to the specified file input element on the web page + The `--selection-timeout` parameter specifies how many seconds to wait for the element to appear in the DOM (default: 5 seconds). The `--action-timeout` parameter specifies how many seconds to wait for the upload action to complete (default: 5 seconds). +**Note**: The file path should be the local path on your machine. The command will handle transferring it to the daemon container automatically. + #### Submit a form ```bash diff --git a/daemon/daemon.go b/daemon/daemon.go index 6d0e5a9..7e919c3 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -3564,30 +3564,84 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in if timeout > 0 { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) element, err = form.Context(ctx).Element(selector) - cancel() + // Don't cancel yet if element found - we need context for filling + if err == nil { + fieldResult.Selector = selector + // Fill the field while context is still valid + err = element.SelectAllText() + if err == nil { + err = element.Input("") + } + if err == nil { + err = element.Input(fieldValue) + } + cancel() // Now we can cancel + break + } + cancel() // Cancel if element not found } else { element, err = form.Element(selector) - } - if err == nil { - fieldResult.Selector = selector - break + if err == nil { + fieldResult.Selector = selector + // Fill the field + err = element.SelectAllText() + if err == nil { + err = element.Input("") + } + if err == nil { + err = element.Input(fieldValue) + } + break + } } } } // If not found in form or no form, search in entire page if element == nil { + // Generate selectors if not already done + if selectors == nil { + selectors = []string{ + fmt.Sprintf("[name='%s']", fieldName), + fmt.Sprintf("#%s", fieldName), + fmt.Sprintf("[id='%s']", fieldName), + fieldName, // In case it's already a full selector + } + } + for _, selector := range selectors { if timeout > 0 { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) element, err = page.Context(ctx).Element(selector) - cancel() + // Don't cancel yet - we need the context for filling + if err == nil { + fieldResult.Selector = selector + // Fill the field while context is still valid + err = element.SelectAllText() + if err == nil { + err = element.Input("") + } + if err == nil { + err = element.Input(fieldValue) + } + cancel() // Now we can cancel + break + } + cancel() // Cancel if element not found } else { element, err = page.Element(selector) - } - if err == nil { - fieldResult.Selector = selector - break + if err == nil { + fieldResult.Selector = selector + // Fill the field + err = element.SelectAllText() + if err == nil { + err = element.Input("") + } + if err == nil { + err = element.Input(fieldValue) + } + break + } } } } @@ -3599,15 +3653,6 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in continue } - // Fill the field - err = element.SelectAllText() - if err == nil { - err = element.Input("") - } - if err == nil { - err = element.Input(fieldValue) - } - if err != nil { fieldResult.Error = fmt.Sprintf("failed to fill field: %v", err) result.ErrorCount++ diff --git a/formtest.php b/formtest.php new file mode 100644 index 0000000..363bdfa --- /dev/null +++ b/formtest.php @@ -0,0 +1,323 @@ + + + + + + + Cremote Form Testing - Multiple Field Types + + + +
+

๐Ÿงช Cremote Form Testing Suite

+ + +
+

๐Ÿ“‹ Form Submission Results

+

Submission Time:

+

Total Fields Submitted:

+ + $value): ?> +
+ : + + + +
+ + +
+

๐Ÿ” Raw POST Data (for debugging):

+
+
+
+ + +
+

๐Ÿ“ Personal Information

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +

๐Ÿ  Address Information

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +

๐ŸŽฏ Preferences

+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ +
+ +

๐Ÿ’ฌ Additional Information

+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+

๐Ÿงช Testing Instructions for Cremote MCP Tools

+

Form ID: #test-form

+

Submit Button: #submitBtn

+ +

Example MCP Tool Usage:

+
+// Test bulk form filling
+web_form_fill_bulk_cremotemcp:
+  form_selector: "#test-form"
+  fields:
+    firstName: "John"
+    lastName: "Doe"
+    email: "john.doe@example.com"
+    phone: "+1-555-123-4567"
+    address: "123 Test Street"
+    city: "Test City"
+    state: "CA"
+    zipCode: "12345"
+    contactMethod: "email"
+    interests: ["technology", "travel"]
+    newsletter: "yes"
+    comments: "This is a test submission"
+            
+
+
+ + + + diff --git a/main.go b/main.go index 124cda5..50e5f06 100644 --- a/main.go +++ b/main.go @@ -216,14 +216,29 @@ func main() { // Create a client c := client.NewClient(*uploadFileHost, *uploadFilePort) - // Upload the file - err := c.UploadFile(*uploadFileTabID, *uploadFileSelector, *uploadFilePath, *uploadFileSelectionTimeout, *uploadFileActionTimeout) - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) + // Check if the local file exists + if _, err := os.Stat(*uploadFilePath); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "Error: Local file does not exist: %s\n", *uploadFilePath) os.Exit(1) } - fmt.Println("File uploaded successfully") + // First, transfer the file to the daemon container + fmt.Printf("Transferring file to daemon container: %s\n", *uploadFilePath) + containerPath, err := c.UploadFileToContainer(*uploadFilePath, "") + if err != nil { + fmt.Fprintf(os.Stderr, "Error transferring file to container: %v\n", err) + os.Exit(1) + } + fmt.Printf("File transferred to container: %s\n", containerPath) + + // Then upload the file to the web form using the container path + err = c.UploadFile(*uploadFileTabID, *uploadFileSelector, containerPath, *uploadFileSelectionTimeout, *uploadFileActionTimeout) + if err != nil { + fmt.Fprintf(os.Stderr, "Error uploading file to web form: %v\n", err) + os.Exit(1) + } + + fmt.Println("File uploaded to web form successfully") case "submit-form": submitFormCmd.Parse(os.Args[2:]) @@ -475,7 +490,7 @@ func printUsage() { fmt.Println(" open-tab Open a new tab and return its ID") fmt.Println(" load-url Load a URL in a tab") fmt.Println(" fill-form Fill a form field with a value") - fmt.Println(" upload-file Upload a file to a file input") + fmt.Println(" upload-file Transfer a file to daemon container and upload to a file input") fmt.Println(" submit-form Submit a form") fmt.Println(" get-source Get the source code of a page") fmt.Println(" get-element Get the HTML of an element") diff --git a/mcp/README.md b/mcp/README.md index fc1cc05..508b2e1 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -130,6 +130,8 @@ Upload files from client to container for use in form uploads. } ``` +**Note**: The CLI `cremote upload-file` command now automatically transfers files to the daemon container first, making file uploads seamless even when the daemon runs in a container. + #### 8. `file_download_cremotemcp` Download files from container to client (e.g., downloaded files from browser). diff --git a/mcp/cremote-mcp b/mcp/cremote-mcp index 31396e2..2923d02 100755 Binary files a/mcp/cremote-mcp and b/mcp/cremote-mcp differ