Files
Developer 6de1b08aa3 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
2026-03-25 00:48:31 +01:00

154 lines
3.7 KiB
Go

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
}