c165f2f788
Agrega 19 issues técnicas documentando funcionalidades implementadas y pendientes. Issues completadas (movidas a dev/issues/completed/): - 001-conversor-web-markdown.md - 002-accessibility-tree.md - 003-gestion-cookies-perfil.md - 004-gestion-extensiones-chrome.md - 005-eliminar-timeouts-innecesarios.md Issues implementadas: - 006-manejo-tabs-ventanas.md - 016-manejo-iframes.md - 017-actions-api.md - 018-file-uploads.md - 019-expected-conditions-mejoradas.md Issues pendientes (media prioridad): - 007-alert-prompt-confirm-handling.md - 008-screenshot-elementos-especificos.md - 009-pdf-generation.md - 010-device-emulation-completo.md - 011-downloads-handling.md Issues pendientes (baja prioridad / avanzado): - 012-browser-contexts-multi-sesion.md - 013-video-recording.md - 014-network-mocking-avanzado.md - 015-geolocation-permissions.md Incluye también dev/NUEVAS_FUNCIONALIDADES.md con resumen completo. Directorio: dev/
301 lines
7.8 KiB
Markdown
301 lines
7.8 KiB
Markdown
# Issue #007: Alert/Prompt/Confirm Handling
|
|
|
|
**Tipo**: Enhancement
|
|
**Prioridad**: Media
|
|
**Estado**: Pendiente
|
|
|
|
## Descripción
|
|
|
|
Implementar manejo de JavaScript dialogs (alert, prompt, confirm) que aparecen en páginas web.
|
|
|
|
## Funcionalidad deseada
|
|
|
|
### Tipos de dialogs
|
|
- **Alert**: `window.alert("mensaje")` - Solo botón OK
|
|
- **Confirm**: `window.confirm("¿Continuar?")` - OK/Cancel, retorna boolean
|
|
- **Prompt**: `window.prompt("Nombre:", "default")` - Input + OK/Cancel
|
|
|
|
### Operaciones
|
|
- Detectar cuando aparece un dialog
|
|
- Aceptar dialog (OK)
|
|
- Rechazar dialog (Cancel)
|
|
- Enviar texto a prompt
|
|
- Obtener mensaje del dialog
|
|
- Manejar dialogs automáticamente con reglas
|
|
|
|
## Implementación técnica
|
|
|
|
### Archivo sugerido
|
|
`pkg/browser/dialogs.go`
|
|
|
|
### CDP Domain
|
|
**Page.javascriptDialogOpening** - Evento cuando aparece dialog
|
|
**Page.handleJavaScriptDialog** - Responder al dialog
|
|
|
|
### API propuesta
|
|
|
|
```go
|
|
// DialogType tipo de dialog JavaScript
|
|
type DialogType string
|
|
|
|
const (
|
|
DialogTypeAlert DialogType = "alert"
|
|
DialogTypeConfirm DialogType = "confirm"
|
|
DialogTypePrompt DialogType = "prompt"
|
|
)
|
|
|
|
// DialogAction acción a tomar con el dialog
|
|
type DialogAction string
|
|
|
|
const (
|
|
DialogAccept DialogAction = "accept" // OK
|
|
DialogDismiss DialogAction = "dismiss" // Cancel
|
|
)
|
|
|
|
// Dialog representa un dialog JavaScript
|
|
type Dialog struct {
|
|
Type DialogType
|
|
Message string
|
|
DefaultPromptText string
|
|
}
|
|
|
|
// HandleDialog maneja un dialog JavaScript cuando aparece
|
|
func (b *Browser) HandleDialog(ctx context.Context, action DialogAction, promptText string) error
|
|
|
|
// OnDialog registra un handler para dialogs
|
|
func (b *Browser) OnDialog(handler func(*Dialog) (DialogAction, string)) error
|
|
|
|
// WaitForDialog espera a que aparezca un dialog
|
|
func (b *Browser) WaitForDialog(ctx context.Context) (*Dialog, error)
|
|
|
|
// AcceptDialog acepta el próximo dialog que aparezca
|
|
func (b *Browser) AcceptDialog(ctx context.Context) error
|
|
|
|
// DismissDialog rechaza el próximo dialog que aparezca
|
|
func (b *Browser) DismissDialog(ctx context.Context) error
|
|
|
|
// PromptDialog responde a un prompt con texto
|
|
func (b *Browser) PromptDialog(ctx context.Context, text string) error
|
|
|
|
// AutoHandleDialogs configura manejo automático de dialogs
|
|
func (b *Browser) AutoHandleDialogs(ctx context.Context, action DialogAction) error
|
|
```
|
|
|
|
## Casos de uso
|
|
|
|
### Caso 1: Aceptar alert automáticamente
|
|
```go
|
|
// Configurar manejo automático
|
|
b.AutoHandleDialogs(ctx, browser.DialogAccept)
|
|
|
|
// Cualquier alert será aceptado automáticamente
|
|
b.Click(ctx, "#trigger-alert")
|
|
```
|
|
|
|
### Caso 2: Manejar confirm con lógica
|
|
```go
|
|
b.OnDialog(func(dialog *browser.Dialog) (browser.DialogAction, string) {
|
|
log.Printf("Dialog: %s - %s", dialog.Type, dialog.Message)
|
|
|
|
if dialog.Type == browser.DialogTypeConfirm {
|
|
if strings.Contains(dialog.Message, "eliminar") {
|
|
return browser.DialogDismiss, "" // Cancelar eliminación
|
|
}
|
|
}
|
|
|
|
return browser.DialogAccept, ""
|
|
})
|
|
|
|
b.Click(ctx, "#delete-button")
|
|
```
|
|
|
|
### Caso 3: Responder a prompt
|
|
```go
|
|
// Esperar prompt y responder
|
|
go func() {
|
|
dialog, _ := b.WaitForDialog(ctx)
|
|
if dialog.Type == browser.DialogTypePrompt {
|
|
b.PromptDialog(ctx, "Mi nombre")
|
|
}
|
|
}()
|
|
|
|
b.Click(ctx, "#ask-name-button")
|
|
```
|
|
|
|
### Caso 4: Aceptar dialog específico
|
|
```go
|
|
// Preparar handler antes de la acción
|
|
b.AcceptDialog(ctx)
|
|
|
|
// Acción que genera dialog
|
|
b.Click(ctx, "#show-alert")
|
|
```
|
|
|
|
## Comandos CDP necesarios
|
|
|
|
```go
|
|
// 1. Habilitar eventos de dialog
|
|
{"method": "Page.enable"}
|
|
|
|
// 2. Escuchar evento de dialog
|
|
// Evento: "Page.javascriptDialogOpening"
|
|
// Params: {
|
|
// "url": "https://...",
|
|
// "message": "Mensaje del dialog",
|
|
// "type": "alert|confirm|prompt",
|
|
// "defaultPrompt": "texto default" // solo en prompt
|
|
// }
|
|
|
|
// 3. Responder al dialog
|
|
{"method": "Page.handleJavaScriptDialog", "params": {
|
|
"accept": true, // true = OK, false = Cancel
|
|
"promptText": "texto de respuesta" // opcional, solo para prompt
|
|
}}
|
|
```
|
|
|
|
## Implementación interna
|
|
|
|
```go
|
|
type dialogHandler struct {
|
|
action DialogAction
|
|
promptText string
|
|
callback func(*Dialog) (DialogAction, string)
|
|
done chan struct{}
|
|
}
|
|
|
|
func (b *Browser) setupDialogHandling() {
|
|
b.cdpClient.On("Page.javascriptDialogOpening", func(params json.RawMessage) {
|
|
var event struct {
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
DefaultPrompt string `json:"defaultPrompt"`
|
|
}
|
|
|
|
json.Unmarshal(params, &event)
|
|
|
|
dialog := &Dialog{
|
|
Type: DialogType(event.Type),
|
|
Message: event.Message,
|
|
DefaultPromptText: event.DefaultPrompt,
|
|
}
|
|
|
|
// Procesar con handler registrado
|
|
action, text := b.processDialog(dialog)
|
|
|
|
// Responder
|
|
b.cdpClient.SendCommand(context.Background(), "Page.handleJavaScriptDialog", map[string]interface{}{
|
|
"accept": action == DialogAccept,
|
|
"promptText": text,
|
|
})
|
|
})
|
|
}
|
|
```
|
|
|
|
## Consideraciones especiales
|
|
|
|
### Timing crítico
|
|
- Los dialogs **bloquean** JavaScript hasta que se responden
|
|
- Debe haber handler registrado ANTES de que aparezca el dialog
|
|
- Si no se maneja, Chrome esperará indefinidamente
|
|
|
|
### beforeunload dialogs
|
|
```go
|
|
// Dialogs de "¿Seguro que quieres salir?"
|
|
// Se generan al cerrar tab/navegador
|
|
b.OnDialog(func(dialog *Dialog) (browser.DialogAction, string) {
|
|
if dialog.Type == browser.DialogTypeBeforeUnload {
|
|
return browser.DialogAccept, "" // Permitir salir
|
|
}
|
|
return browser.DialogAccept, ""
|
|
})
|
|
```
|
|
|
|
### Headless mode
|
|
- En modo headless, los dialogs no se muestran visualmente
|
|
- Pero igual generan el evento y deben manejarse
|
|
- Importante para testing automatizado
|
|
|
|
### Timeout en dialogs
|
|
```go
|
|
// Implementar timeout para evitar quedar colgado
|
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
dialog, err := b.WaitForDialog(ctx)
|
|
if err == context.DeadlineExceeded {
|
|
log.Println("No apareció dialog en 5s")
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Página de prueba
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<body>
|
|
<button onclick="alert('Hola')">Alert</button>
|
|
<button onclick="confirm('¿Continuar?')">Confirm</button>
|
|
<button onclick="prompt('Nombre:')">Prompt</button>
|
|
|
|
<script>
|
|
// Test beforeunload
|
|
window.addEventListener('beforeunload', (e) => {
|
|
e.preventDefault();
|
|
e.returnValue = '';
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
### Tests
|
|
```go
|
|
func TestAlertHandling(t *testing.T) {
|
|
b.AutoHandleDialogs(ctx, browser.DialogAccept)
|
|
b.Navigate(ctx, "test.html", nil)
|
|
b.Click(ctx, "button:nth-child(1)")
|
|
// No debe quedar colgado
|
|
}
|
|
|
|
func TestPromptResponse(t *testing.T) {
|
|
b.OnDialog(func(d *browser.Dialog) (browser.DialogAction, string) {
|
|
if d.Type == browser.DialogTypePrompt {
|
|
return browser.DialogAccept, "Test Name"
|
|
}
|
|
return browser.DialogAccept, ""
|
|
})
|
|
|
|
b.Click(ctx, "button:nth-child(3)")
|
|
result, _ := b.Evaluate(ctx, "lastPromptResult")
|
|
assert.Equal(t, "Test Name", result.Value)
|
|
}
|
|
```
|
|
|
|
## Ejemplos de uso real
|
|
|
|
### Login con confirm
|
|
```go
|
|
b.OnDialog(func(d *browser.Dialog) (browser.DialogAction, string) {
|
|
if strings.Contains(d.Message, "logout") {
|
|
return browser.DialogAccept, ""
|
|
}
|
|
return browser.DialogDismiss, ""
|
|
})
|
|
|
|
b.Click(ctx, "#logout-button")
|
|
```
|
|
|
|
### Formulario con prompt
|
|
```go
|
|
b.PromptDialog(ctx, "usuario@example.com")
|
|
b.Click(ctx, "#ask-email-button")
|
|
```
|
|
|
|
## Referencias
|
|
|
|
- CDP Page.handleJavaScriptDialog: https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-handleJavaScriptDialog
|
|
- CDP Page.javascriptDialogOpening: https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-javascriptDialogOpening
|
|
- Playwright Dialogs: https://playwright.dev/docs/dialogs
|
|
- Selenium Alerts: https://www.selenium.dev/documentation/webdriver/interactions/alerts/
|