feat: subida de archivos (file uploads)
Implementa capacidad para subir archivos a inputs de tipo file. Incluye: - UploadFile() y UploadFiles() para uno o múltiples archivos - Validación de existencia de archivos - Conversión automática a paths absolutos - ClearFileInput() para limpiar - GetFileInputValue() para obtener nombres seleccionados - IsFileInputMultiple() para verificar atributo multiple Usa CDP DOM.setFileInputFiles. Archivo: pkg/browser/upload.go
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user