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/
8.3 KiB
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
- CDP DOM.getBoxModel: https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getBoxModel
- CDP Page.captureScreenshot: https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-captureScreenshot
- Playwright elementHandle.screenshot: https://playwright.dev/docs/api/class-elementhandle#element-handle-screenshot
- Puppeteer element screenshots: https://pptr.dev/api/puppeteer.elementhandle.screenshot