diff --git a/pkg/browser/upload.go b/pkg/browser/upload.go new file mode 100644 index 0000000..4ca234b --- /dev/null +++ b/pkg/browser/upload.go @@ -0,0 +1,153 @@ +package browser + +import ( + "context" + "fmt" + "os" + "path/filepath" +) + +// UploadFile sube un archivo a un input de tipo file +func (b *Browser) UploadFile(ctx context.Context, selector string, filePath string) error { + return b.UploadFiles(ctx, selector, []string{filePath}) +} + +// UploadFiles sube múltiples archivos a un input de tipo file +func (b *Browser) UploadFiles(ctx context.Context, selector string, filePaths []string) error { + // Validar que todos los archivos existen + var absolutePaths []string + for _, path := range filePaths { + // Convertir a path absoluto + absPath, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("invalid file path %s: %w", path, err) + } + + // Verificar que existe + if _, err := os.Stat(absPath); os.IsNotExist(err) { + return fmt.Errorf("file does not exist: %s", absPath) + } + + absolutePaths = append(absolutePaths, absPath) + } + + // Obtener el nodeId del input + nodeID, err := b.querySelector(ctx, selector) + if err != nil { + return fmt.Errorf("file input not found: %w", err) + } + + // Verificar que es un input de tipo file + script := fmt.Sprintf(` + (() => { + const input = document.querySelector('%s'); + return input && input.tagName === 'INPUT' && input.type === 'file'; + })() + `, selector) + + result, err := b.Evaluate(ctx, script) + if err != nil { + return err + } + + isFileInput, ok := result.Value.(bool) + if !ok || !isFileInput { + return fmt.Errorf("element is not a file input: %s", selector) + } + + // Establecer archivos usando CDP + params := map[string]interface{}{ + "files": absolutePaths, + "nodeId": nodeID, + } + + if err := b.cdpClient.Execute(ctx, "DOM.setFileInputFiles", params, nil); err != nil { + return fmt.Errorf("failed to set files: %w", err) + } + + return nil +} + +// SetFileInput es un alias de UploadFiles +func (b *Browser) SetFileInput(ctx context.Context, selector string, files []string) error { + return b.UploadFiles(ctx, selector, files) +} + +// ClearFileInput limpia un input de tipo file +func (b *Browser) ClearFileInput(ctx context.Context, selector string) error { + nodeID, err := b.querySelector(ctx, selector) + if err != nil { + return fmt.Errorf("file input not found: %w", err) + } + + // Establecer array vacío + params := map[string]interface{}{ + "files": []string{}, + "nodeId": nodeID, + } + + if err := b.cdpClient.Execute(ctx, "DOM.setFileInputFiles", params, nil); err != nil { + return fmt.Errorf("failed to clear files: %w", err) + } + + return nil +} + +// GetFileInputValue obtiene los nombres de archivos seleccionados +func (b *Browser) GetFileInputValue(ctx context.Context, selector string) ([]string, error) { + script := fmt.Sprintf(` + (() => { + const input = document.querySelector('%s'); + if (!input || input.type !== 'file') return null; + + const files = Array.from(input.files); + return files.map(f => f.name); + })() + `, selector) + + result, err := b.Evaluate(ctx, script) + if err != nil { + return nil, err + } + + if result.Value == nil { + return []string{}, nil + } + + // Convertir resultado a []string + filesInterface, ok := result.Value.([]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected result type") + } + + var files []string + for _, fileInterface := range filesInterface { + if fileName, ok := fileInterface.(string); ok { + files = append(files, fileName) + } + } + + return files, nil +} + +// IsFileInputMultiple verifica si un input acepta múltiples archivos +func (b *Browser) IsFileInputMultiple(ctx context.Context, selector string) (bool, error) { + script := fmt.Sprintf(` + (() => { + const input = document.querySelector('%s'); + return input && input.multiple === true; + })() + `, selector) + + result, err := b.Evaluate(ctx, script) + if err != nil { + return false, err + } + + isMultiple, ok := result.Value.(bool) + if !ok { + return false, nil + } + + return isMultiple, nil +}