Files
navegator/dev/issues/008-screenshot-elementos-especificos.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

8.3 KiB

Issue #008: Screenshot de Elementos Específicos

Tipo: Enhancement Prioridad: Media Estado: Pendiente

Descripción

Implementar capacidad de tomar screenshots de elementos específicos de la página en lugar de solo página completa.

Funcionalidad deseada

Operaciones

  • Screenshot de elemento específico por selector CSS
  • Screenshot de región (coordenadas x, y, width, height)
  • Screenshot con padding/margin alrededor del elemento
  • Scroll automático al elemento antes de capturar
  • Esperar a que elemento sea visible antes de capturar
  • Captura de múltiples elementos en batch
  • Captura con o sin sombras CSS

Implementación técnica

Archivo sugerido

Extender pkg/browser/navigation.go o crear pkg/browser/screenshots.go

CDP Methods

  • DOM.getBoxModel - Obtener dimensiones del elemento
  • Page.captureScreenshot - Capturar con clip region

API propuesta

// ScreenshotElementOptions opciones para screenshot de elemento
type ScreenshotElementOptions struct {
    Format     string  // "png" o "jpeg" (default: png)
    Quality    int     // 0-100 para JPEG (default: 80)
    Padding    int     // Padding en pixels alrededor del elemento
    WaitVisible bool   // Esperar a que sea visible (default: true)
    ScrollIntoView bool // Scroll al elemento antes (default: true)
    OmitBackground bool // Fondo transparente (default: false)
}

// DefaultScreenshotElementOptions retorna opciones por defecto
func DefaultScreenshotElementOptions() *ScreenshotElementOptions

// ScreenshotElement toma screenshot de un elemento específico
func (b *Browser) ScreenshotElement(ctx context.Context, selector string, opts *ScreenshotElementOptions) ([]byte, error)

// ScreenshotElementToFile guarda screenshot de elemento a archivo
func (b *Browser) ScreenshotElementToFile(ctx context.Context, selector string, filepath string, opts *ScreenshotElementOptions) error

// ScreenshotRegion toma screenshot de región específica
func (b *Browser) ScreenshotRegion(ctx context.Context, x, y, width, height int) ([]byte, error)

// ScreenshotElements toma screenshots de múltiples elementos
func (b *Browser) ScreenshotElements(ctx context.Context, selectors []string, opts *ScreenshotElementOptions) (map[string][]byte, error)

Casos de uso

Caso 1: Screenshot de botón específico

opts := browser.DefaultScreenshotElementOptions()
opts.Padding = 10 // 10px de margen

screenshot, _ := b.ScreenshotElement(ctx, "#submit-button", opts)
os.WriteFile("button.png", screenshot, 0644)

Caso 2: Screenshot de cada producto

products := []string{
    ".product:nth-child(1)",
    ".product:nth-child(2)",
    ".product:nth-child(3)",
}

screenshots, _ := b.ScreenshotElements(ctx, products, nil)
for selector, data := range screenshots {
    filename := strings.ReplaceAll(selector, ":", "-") + ".png"
    os.WriteFile(filename, data, 0644)
}

Caso 3: Screenshot con fondo transparente

opts := &browser.ScreenshotElementOptions{
    Format: "png",
    OmitBackground: true, // PNG transparente
}

screenshot, _ := b.ScreenshotElement(ctx, ".icon", opts)

Caso 4: Screenshot de región específica

// Capturar área de 300x200 en posición (100, 150)
screenshot, _ := b.ScreenshotRegion(ctx, 100, 150, 300, 200)

Implementación interna

func (b *Browser) ScreenshotElement(ctx context.Context, selector string, opts *ScreenshotElementOptions) ([]byte, error) {
    if opts == nil {
        opts = DefaultScreenshotElementOptions()
    }

    // 1. Esperar a que elemento sea visible si se especificó
    if opts.WaitVisible {
        if err := b.WaitForElement(ctx, selector, nil); err != nil {
            return nil, fmt.Errorf("element not visible: %w", err)
        }
    }

    // 2. Scroll al elemento si se especificó
    if opts.ScrollIntoView {
        script := fmt.Sprintf(`
            document.querySelector('%s').scrollIntoView({
                behavior: 'instant',
                block: 'center'
            })
        `, selector)
        b.Evaluate(ctx, script)
    }

    // 3. Obtener dimensiones del elemento
    var result struct {
        Model struct {
            Content []float64 `json:"content"` // [x1, y1, x2, y2, x3, y3, x4, y4]
        } `json:"model"`
    }

    // Primero obtener nodeId
    nodeID, err := b.querySelector(ctx, selector)
    if err != nil {
        return nil, err
    }

    // Obtener box model
    if err := b.cdpClient.Execute(ctx, "DOM.getBoxModel", map[string]interface{}{
        "nodeId": nodeID,
    }, &result); err != nil {
        return nil, fmt.Errorf("failed to get box model: %w", err)
    }

    // Calcular clip region
    content := result.Model.Content
    x := content[0]
    y := content[1]
    width := content[4] - content[0]
    height := content[5] - content[1]

    // Aplicar padding
    if opts.Padding > 0 {
        x -= float64(opts.Padding)
        y -= float64(opts.Padding)
        width += float64(opts.Padding * 2)
        height += float64(opts.Padding * 2)
    }

    // 4. Capturar screenshot con clip
    params := map[string]interface{}{
        "format": opts.Format,
        "clip": map[string]interface{}{
            "x":      x,
            "y":      y,
            "width":  width,
            "height": height,
            "scale":  1,
        },
    }

    if opts.OmitBackground {
        params["captureBeyondViewport"] = true
        params["fromSurface"] = true
    }

    if opts.Format == "jpeg" && opts.Quality > 0 {
        params["quality"] = opts.Quality
    }

    var screenshotResult struct {
        Data string `json:"data"`
    }

    if err := b.cdpClient.Execute(ctx, "Page.captureScreenshot", params, &screenshotResult); err != nil {
        return nil, fmt.Errorf("failed to capture screenshot: %w", err)
    }

    // 5. Decodificar base64
    data, err := base64.StdEncoding.DecodeString(screenshotResult.Data)
    if err != nil {
        return nil, fmt.Errorf("failed to decode screenshot: %w", err)
    }

    return data, nil
}

Comandos CDP

Obtener dimensiones del elemento

{
  "method": "DOM.getBoxModel",
  "params": {
    "nodeId": 123
  }
}

// Response:
{
  "model": {
    "content": [x1, y1, x2, y2, x3, y3, x4, y4],
    "padding": [...],
    "border": [...],
    "margin": [...],
    "width": 200,
    "height": 100
  }
}

Capturar con clip

{
  "method": "Page.captureScreenshot",
  "params": {
    "format": "png",
    "clip": {
      "x": 100,
      "y": 200,
      "width": 300,
      "height": 150,
      "scale": 1
    },
    "captureBeyondViewport": true
  }
}

Casos de uso avanzados

Comparación visual

// Capturar antes y después de una acción
before, _ := b.ScreenshotElement(ctx, "#component", nil)

b.Click(ctx, "#toggle-button")

after, _ := b.ScreenshotElement(ctx, "#component", nil)

// Comparar imágenes
if !bytes.Equal(before, after) {
    log.Println("El componente cambió visualmente")
}

Generación de thumbnails

opts := &browser.ScreenshotElementOptions{
    Format: "jpeg",
    Quality: 60, // Compresión para thumbnails
}

// Capturar todos los artículos
articles := []string{".article-1", ".article-2", ".article-3"}
thumbnails, _ := b.ScreenshotElements(ctx, articles, opts)

Screenshot de elemento fuera de viewport

// Elemento muy abajo en la página
opts := &browser.ScreenshotElementOptions{
    ScrollIntoView: true, // Scroll automático
    WaitVisible: true,
}

screenshot, _ := b.ScreenshotElement(ctx, "#footer-logo", opts)

Mejoras adicionales

Screenshot de elemento con sombra

// Incluir box-shadow en captura
opts.IncludeShadow = true

Screenshot de elemento rotado

// Calcular bounding box considerando rotación CSS
opts.ConsiderTransform = true

Screenshot de SVG específico

// Elementos SVG pueden necesitar manejo especial
screenshot, _ := b.ScreenshotElement(ctx, "svg#chart", opts)

Referencias