Files
navegator/dev/issues/006-manejo-tabs-ventanas.md
T
Developer c165f2f788 docs: issues técnicas para nuevas funcionalidades
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/
2026-03-25 00:49:06 +01:00

7.4 KiB

Issue #006: Manejo de Tabs/Ventanas

Tipo: Enhancement Prioridad: Alta Estado: En progreso

Descripción

Implementar gestión completa de múltiples tabs y ventanas en el navegador.

Funcionalidad deseada

  • Listar todos los tabs abiertos
  • Crear nuevos tabs
  • Cerrar tabs
  • Cambiar entre tabs (focus)
  • Obtener información de cada tab (URL, título)
  • Detectar cuando se abre un nuevo tab
  • Esperar a que nuevo tab cargue

Implementación técnica

Archivo sugerido

pkg/browser/tabs.go

CDP Domains

  • Target.getTargets - Listar targets (tabs)
  • Target.createTarget - Crear nuevo tab
  • Target.closeTarget - Cerrar tab
  • Target.activateTarget - Activar tab
  • Target.attachToTarget - Conectar a tab
  • Target.targetCreated - Evento nuevo tab

API propuesta

// Tab representa un tab del navegador
type Tab struct {
    ID          string
    URL         string
    Title       string
    Type        string // "page" | "background_page" | ...
    Attached    bool
}

// GetTabs obtiene todos los tabs abiertos
func (b *Browser) GetTabs(ctx context.Context) ([]*Tab, error)

// NewTab crea un nuevo tab y retorna su ID
func (b *Browser) NewTab(ctx context.Context, url string) (string, error)

// CloseTab cierra un tab específico
func (b *Browser) CloseTab(ctx context.Context, tabID string) error

// SwitchToTab cambia el foco a un tab
func (b *Browser) SwitchToTab(ctx context.Context, tabID string) error

// GetCurrentTab obtiene el tab actual
func (b *Browser) GetCurrentTab(ctx context.Context) (*Tab, error)

// WaitForNewTab espera a que se abra un nuevo tab
func (b *Browser) WaitForNewTab(ctx context.Context, action func()) (*Tab, error)

// OnTabCreated registra callback para tabs nuevos
func (b *Browser) OnTabCreated(handler func(*Tab)) error

Casos de uso

Caso 1: Listar tabs

tabs, _ := b.GetTabs(ctx)
for _, tab := range tabs {
    log.Printf("Tab %s: %s", tab.ID, tab.Title)
}

Caso 2: Abrir nuevo tab

tabID, _ := b.NewTab(ctx, "https://example.com")
log.Printf("Nuevo tab creado: %s", tabID)

Caso 3: Esperar y cambiar a nuevo tab

newTab, _ := b.WaitForNewTab(ctx, func() {
    b.Click(ctx, "a[target='_blank']")
})

// Cambiar al nuevo tab
b.SwitchToTab(ctx, newTab.ID)

// Trabajar en el nuevo tab
b.WaitForNavigation(ctx, nil)
log.Printf("Nuevo tab URL: %s", newTab.URL)

Caso 4: Cerrar tabs excepto el principal

tabs, _ := b.GetTabs(ctx)
currentTab, _ := b.GetCurrentTab(ctx)

for _, tab := range tabs {
    if tab.ID != currentTab.ID {
        b.CloseTab(ctx, tab.ID)
    }
}

Caso 5: Trabajar con múltiples tabs

// Abrir múltiples tabs
tab1, _ := b.NewTab(ctx, "https://site1.com")
tab2, _ := b.NewTab(ctx, "https://site2.com")
tab3, _ := b.NewTab(ctx, "https://site3.com")

// Hacer algo en cada tab
for _, tabID := range []string{tab1, tab2, tab3} {
    b.SwitchToTab(ctx, tabID)
    b.WaitForNavigation(ctx, nil)

    title, _ := b.Evaluate(ctx, "document.title")
    log.Printf("Tab %s: %v", tabID, title.Value)
}

Implementación interna

func (b *Browser) GetTabs(ctx context.Context) ([]*Tab, error) {
    var result struct {
        TargetInfos []struct {
            TargetID string `json:"targetId"`
            Type     string `json:"type"`
            Title    string `json:"title"`
            URL      string `json:"url"`
            Attached bool   `json:"attached"`
        } `json:"targetInfos"`
    }

    if err := b.cdpClient.Execute(ctx, "Target.getTargets", nil, &result); err != nil {
        return nil, fmt.Errorf("failed to get targets: %w", err)
    }

    var tabs []*Tab
    for _, info := range result.TargetInfos {
        if info.Type == "page" {
            tabs = append(tabs, &Tab{
                ID:       info.TargetID,
                URL:      info.URL,
                Title:    info.Title,
                Type:     info.Type,
                Attached: info.Attached,
            })
        }
    }

    return tabs, nil
}

func (b *Browser) NewTab(ctx context.Context, url string) (string, error) {
    var result struct {
        TargetID string `json:"targetId"`
    }

    params := map[string]interface{}{
        "url": url,
    }

    if err := b.cdpClient.Execute(ctx, "Target.createTarget", params, &result); err != nil {
        return "", fmt.Errorf("failed to create tab: %w", err)
    }

    return result.TargetID, nil
}

func (b *Browser) SwitchToTab(ctx context.Context, tabID string) error {
    // Activar tab
    if err := b.cdpClient.Execute(ctx, "Target.activateTarget", map[string]interface{}{
        "targetId": tabID,
    }, nil); err != nil {
        return fmt.Errorf("failed to activate tab: %w", err)
    }

    // Attach al tab si no está attached
    if err := b.cdpClient.Execute(ctx, "Target.attachToTarget", map[string]interface{}{
        "targetId": tabID,
        "flatten":  true,
    }, nil); err != nil {
        return fmt.Errorf("failed to attach to tab: %w", err)
    }

    // Actualizar targetID actual del browser
    b.targetID = tabID

    return nil
}

CDP Commands

Listar tabs

{"method": "Target.getTargets"}

Crear tab

{"method": "Target.createTarget", "params": {"url": "https://example.com"}}

Cerrar tab

{"method": "Target.closeTarget", "params": {"targetId": "ABC123"}}

Activar tab

{"method": "Target.activateTarget", "params": {"targetId": "ABC123"}}

Attach a tab

{"method": "Target.attachToTarget", "params": {"targetId": "ABC123", "flatten": true}}

Eventos CDP

Nuevo tab creado

b.cdpClient.On("Target.targetCreated", func(params json.RawMessage) {
    var event struct {
        TargetInfo struct {
            TargetID string `json:"targetId"`
            Type     string `json:"type"`
            Title    string `json:"title"`
            URL      string `json:"url"`
        } `json:"targetInfo"`
    }

    json.Unmarshal(params, &event)

    if event.TargetInfo.Type == "page" {
        // Nuevo tab creado
        log.Printf("New tab: %s", event.TargetInfo.TargetID)
    }
})

Consideraciones especiales

Session management

  • Cada tab requiere su propia sesión CDP
  • Mantener mapa de tabID -> sessionID
  • Enviar comandos al tab correcto

Popup handling

// Detectar popups automáticamente
b.OnTabCreated(func(tab *Tab) {
    if strings.Contains(tab.URL, "popup") {
        b.SwitchToTab(ctx, tab.ID)
        // Manejar popup
        b.CloseTab(ctx, tab.ID)
    }
})

Memory management

  • Cerrar tabs que no se usan
  • Detach de tabs inactivos
  • Limpiar event listeners

Testing

func TestMultipleTabs(t *testing.T) {
    // Crear 3 tabs
    tab1, _ := b.NewTab(ctx, "https://example.com")
    tab2, _ := b.NewTab(ctx, "https://google.com")
    tab3, _ := b.NewTab(ctx, "https://github.com")

    // Verificar que existen
    tabs, _ := b.GetTabs(ctx)
    assert.Len(t, tabs, 4) // 3 + tab inicial

    // Cambiar entre tabs
    b.SwitchToTab(ctx, tab1)
    current, _ := b.GetCurrentTab(ctx)
    assert.Equal(t, tab1, current.ID)

    // Cerrar tabs
    b.CloseTab(ctx, tab1)
    b.CloseTab(ctx, tab2)
    b.CloseTab(ctx, tab3)

    tabs, _ = b.GetTabs(ctx)
    assert.Len(t, tabs, 1) // Solo tab inicial
}

Referencias