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/
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
# 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
|
||||
|
||||
```go
|
||||
// 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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
opts := &browser.ScreenshotElementOptions{
|
||||
Format: "png",
|
||||
OmitBackground: true, // PNG transparente
|
||||
}
|
||||
|
||||
screenshot, _ := b.ScreenshotElement(ctx, ".icon", opts)
|
||||
```
|
||||
|
||||
### Caso 4: Screenshot de región específica
|
||||
```go
|
||||
// Capturar área de 300x200 en posición (100, 150)
|
||||
screenshot, _ := b.ScreenshotRegion(ctx, 100, 150, 300, 200)
|
||||
```
|
||||
|
||||
## Implementación interna
|
||||
|
||||
```go
|
||||
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
|
||||
```json
|
||||
{
|
||||
"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
|
||||
```json
|
||||
{
|
||||
"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
|
||||
```go
|
||||
// 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
|
||||
```go
|
||||
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
|
||||
```go
|
||||
// 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
|
||||
```go
|
||||
// Incluir box-shadow en captura
|
||||
opts.IncludeShadow = true
|
||||
```
|
||||
|
||||
### Screenshot de elemento rotado
|
||||
```go
|
||||
// Calcular bounding box considerando rotación CSS
|
||||
opts.ConsiderTransform = true
|
||||
```
|
||||
|
||||
### Screenshot de SVG específico
|
||||
```go
|
||||
// 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
|
||||
Reference in New Issue
Block a user