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,65 @@
|
||||
# Issue #001: Conversor de página web a markdown
|
||||
|
||||
**Tipo**: Enhancement
|
||||
**Prioridad**: Media
|
||||
**Estado**: Pendiente
|
||||
|
||||
## Descripción
|
||||
|
||||
Implementar utilidad para convertir el contenido HTML de una página web a formato Markdown.
|
||||
|
||||
## Funcionalidad deseada
|
||||
|
||||
- Convertir títulos (h1-h6) a markdown (#, ##, ###, etc.)
|
||||
- Convertir enlaces a formato `[texto](url)`
|
||||
- Convertir imágenes a formato ``
|
||||
- Convertir listas (ol, ul) a markdown
|
||||
- Convertir tablas a markdown
|
||||
- Mantener estructura de párrafos
|
||||
- Extraer texto limpio sin CSS/JS inline
|
||||
- Opción para incluir/excluir imágenes
|
||||
- Manejar código y bloques de código (pre, code)
|
||||
- Preservar énfasis (bold, italic)
|
||||
|
||||
## Implementación técnica
|
||||
|
||||
### Archivo sugerido
|
||||
`pkg/browser/markdown.go`
|
||||
|
||||
### API propuesta
|
||||
|
||||
```go
|
||||
// ToMarkdown convierte el contenido de la página actual a Markdown
|
||||
func (b *Browser) ToMarkdown(ctx context.Context, opts *MarkdownOptions) (string, error)
|
||||
|
||||
type MarkdownOptions struct {
|
||||
Selector string // Selector CSS opcional para convertir solo una parte
|
||||
IncludeImages bool // Incluir imágenes en el output
|
||||
IncludeLinks bool // Incluir enlaces
|
||||
BaseURL string // URL base para enlaces relativos
|
||||
}
|
||||
```
|
||||
|
||||
### Estrategia
|
||||
|
||||
1. Obtener HTML con `GetHTML()`
|
||||
2. Parsear HTML usando `golang.org/x/net/html`
|
||||
3. Convertir nodos recursivamente a markdown
|
||||
4. Alternativamente, ejecutar JS en el navegador con biblioteca turndown
|
||||
|
||||
### Librerías potenciales
|
||||
|
||||
- `github.com/JohannesKaufmann/html-to-markdown` - Conversor Go nativo
|
||||
- O ejecutar `turndown.js` vía `Evaluate()` para mayor fidelidad
|
||||
|
||||
## Casos de uso
|
||||
|
||||
- Extraer contenido de artículos de blog
|
||||
- Scraping de documentación
|
||||
- Generar datasets para LLMs
|
||||
- Archivar contenido web
|
||||
|
||||
## Referencias
|
||||
|
||||
- Turndown: https://github.com/mixmark-io/turndown
|
||||
- html-to-markdown Go: https://github.com/JohannesKaufmann/html-to-markdown
|
||||
@@ -0,0 +1,88 @@
|
||||
# Issue #002: Recuperación de Accessibility Tree
|
||||
|
||||
**Tipo**: Enhancement
|
||||
**Prioridad**: Alta
|
||||
**Estado**: Pendiente
|
||||
|
||||
## Descripción
|
||||
|
||||
Implementar método para obtener el árbol de accesibilidad (Accessibility Tree) de la página usando Chrome DevTools Protocol.
|
||||
|
||||
## Funcionalidad deseada
|
||||
|
||||
- Obtener accessibility tree completo vía CDP
|
||||
- Listar roles ARIA de elementos (button, link, heading, etc.)
|
||||
- Obtener nombres accesibles de elementos
|
||||
- Extraer propiedades de accesibilidad
|
||||
- Útil para que LLMs entiendan estructura semántica de página
|
||||
- Formato JSON estructurado y fácil de parsear
|
||||
- Opción para filtrar por tipos de nodos
|
||||
|
||||
## Implementación técnica
|
||||
|
||||
### Archivo sugerido
|
||||
`pkg/browser/accessibility.go`
|
||||
|
||||
### CDP Domain
|
||||
`Accessibility.getFullAXTree` - https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/
|
||||
|
||||
### API propuesta
|
||||
|
||||
```go
|
||||
// GetAccessibilityTree obtiene el árbol de accesibilidad de la página
|
||||
func (b *Browser) GetAccessibilityTree(ctx context.Context, opts *AccessibilityOptions) (*AXTree, error)
|
||||
|
||||
type AccessibilityOptions struct {
|
||||
Depth int // Profundidad máxima del árbol (0 = ilimitado)
|
||||
FilterRoles []string // Roles a incluir (ej: ["button", "link", "heading"])
|
||||
}
|
||||
|
||||
type AXTree struct {
|
||||
Nodes []AXNode `json:"nodes"`
|
||||
}
|
||||
|
||||
type AXNode struct {
|
||||
NodeID string `json:"nodeId"`
|
||||
Role string `json:"role"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Properties map[string]string `json:"properties,omitempty"`
|
||||
Children []string `json:"children,omitempty"` // IDs de hijos
|
||||
}
|
||||
```
|
||||
|
||||
### Comandos CDP necesarios
|
||||
|
||||
```go
|
||||
// 1. Habilitar dominio Accessibility
|
||||
{"method": "Accessibility.enable"}
|
||||
|
||||
// 2. Obtener árbol completo
|
||||
{"method": "Accessibility.getFullAXTree", "params": {}}
|
||||
|
||||
// O para un nodo específico:
|
||||
{"method": "Accessibility.getPartialAXTree", "params": {"nodeId": ...}}
|
||||
```
|
||||
|
||||
## Casos de uso
|
||||
|
||||
- LLMs pueden entender mejor la estructura de la página
|
||||
- Identificar elementos interactuables automáticamente
|
||||
- Testing de accesibilidad
|
||||
- Generar selectores semánticos
|
||||
- Scraping inteligente basado en roles ARIA
|
||||
|
||||
## Ventajas sobre DOM normal
|
||||
|
||||
- Información semántica rica
|
||||
- Roles ARIA explícitos
|
||||
- Nombres accesibles computados
|
||||
- Estructura más simple que DOM HTML
|
||||
- Ideal para navegación por agentes autónomos
|
||||
|
||||
## Referencias
|
||||
|
||||
- CDP Accessibility Domain: https://chromedevtools.github.io/devtools-protocol/tot/Accessibility/
|
||||
- WAI-ARIA Roles: https://www.w3.org/TR/wai-aria-1.2/#role_definitions
|
||||
- Chrome AX Tree Inspector: chrome://accessibility
|
||||
@@ -0,0 +1,191 @@
|
||||
# Issue #003: Administración avanzada de cookies del perfil
|
||||
|
||||
**Tipo**: Enhancement
|
||||
**Prioridad**: Media
|
||||
**Estado**: Pendiente
|
||||
|
||||
## Descripción
|
||||
|
||||
Mejorar las capacidades de gestión de cookies persistentes en perfiles de navegador, permitiendo importar/exportar y gestionar cookies antes y después del lanzamiento del navegador.
|
||||
|
||||
## Funcionalidad deseada
|
||||
|
||||
### Gestión de cookies en runtime (ya implementado parcialmente)
|
||||
- ✅ `GetCookies()` - Obtener cookies de URLs específicas
|
||||
- ✅ `SetCookie()` - Establecer cookies individuales
|
||||
- ✅ `ClearCookies()` - Limpiar todas las cookies
|
||||
|
||||
### Nuevas funcionalidades necesarias
|
||||
|
||||
#### Importar/Exportar
|
||||
- Exportar todas las cookies del perfil a archivo JSON
|
||||
- Importar cookies desde archivo JSON
|
||||
- Formato compatible con extensiones de Chrome (EditThisCookie, etc.)
|
||||
- Soportar formato Netscape (cookies.txt)
|
||||
|
||||
#### Gestión offline de perfiles
|
||||
- Leer cookies del perfil sin lanzar navegador
|
||||
- Modificar cookies del perfil en disco
|
||||
- Copiar cookies entre perfiles
|
||||
- Backup/restore de cookies
|
||||
|
||||
#### Filtrado y búsqueda
|
||||
- Listar todas las cookies del perfil actual
|
||||
- Filtrar cookies por dominio
|
||||
- Filtrar cookies por nombre
|
||||
- Buscar cookies por patrón
|
||||
|
||||
#### Configuración previa al lanzamiento
|
||||
- Establecer cookies iniciales antes de lanzar navegador
|
||||
- Cargar cookies desde archivo al inicio
|
||||
- Configurar cookies de sesión específicas
|
||||
|
||||
## Implementación técnica
|
||||
|
||||
### Archivos sugeridos
|
||||
- `pkg/browser/profile_cookies.go` - Gestión avanzada
|
||||
- `pkg/browser/cookie_import_export.go` - I/O de archivos
|
||||
|
||||
### API propuesta
|
||||
|
||||
```go
|
||||
// === Gestión en runtime ===
|
||||
|
||||
// GetAllCookies obtiene todas las cookies del navegador actual
|
||||
func (b *Browser) GetAllCookies(ctx context.Context) ([]*Cookie, error)
|
||||
|
||||
// FilterCookies obtiene cookies que coinciden con filtros
|
||||
func (b *Browser) FilterCookies(ctx context.Context, filter CookieFilter) ([]*Cookie, error)
|
||||
|
||||
type CookieFilter struct {
|
||||
Domain string // Filtrar por dominio (ej: ".example.com")
|
||||
Name string // Filtrar por nombre exacto
|
||||
Pattern string // Regex para nombre o valor
|
||||
}
|
||||
|
||||
// === Import/Export ===
|
||||
|
||||
// ExportCookies exporta cookies a archivo JSON
|
||||
func (b *Browser) ExportCookies(ctx context.Context, filepath string, format CookieFormat) error
|
||||
|
||||
// ImportCookies importa cookies desde archivo
|
||||
func (b *Browser) ImportCookies(ctx context.Context, filepath string, format CookieFormat) error
|
||||
|
||||
type CookieFormat string
|
||||
const (
|
||||
CookieFormatJSON CookieFormat = "json" // JSON estándar
|
||||
CookieFormatNetscape CookieFormat = "netscape" // cookies.txt
|
||||
CookieFormatChrome CookieFormat = "chrome" // Formato EditThisCookie
|
||||
)
|
||||
|
||||
// === Gestión offline de perfiles ===
|
||||
|
||||
// Profile representa un perfil de navegador
|
||||
type Profile struct {
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
// ListProfiles lista todos los perfiles disponibles
|
||||
func ListProfiles() ([]Profile, error)
|
||||
|
||||
// GetProfileCookies lee cookies de un perfil sin lanzar navegador
|
||||
func GetProfileCookies(profilePath string) ([]*Cookie, error)
|
||||
|
||||
// SetProfileCookies escribe cookies en un perfil sin lanzar navegador
|
||||
func SetProfileCookies(profilePath string, cookies []*Cookie) error
|
||||
|
||||
// CopyProfileCookies copia cookies entre perfiles
|
||||
func CopyProfileCookies(srcProfile, dstProfile string) error
|
||||
|
||||
// === Configuración inicial ===
|
||||
|
||||
// LaunchWithCookies lanza navegador con cookies precargadas
|
||||
func LaunchWithCookies(ctx context.Context, config *Config, cookiesFile string) (*Browser, error)
|
||||
|
||||
// Config.InitialCookies - campo para establecer cookies al inicio
|
||||
type Config struct {
|
||||
// ... campos existentes ...
|
||||
InitialCookies []*Cookie // Cookies a establecer al lanzar
|
||||
CookiesFile string // Archivo de cookies a cargar
|
||||
}
|
||||
```
|
||||
|
||||
### Formato JSON de cookies
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "session_id",
|
||||
"value": "abc123",
|
||||
"domain": ".example.com",
|
||||
"path": "/",
|
||||
"expires": 1735689600,
|
||||
"httpOnly": true,
|
||||
"secure": true,
|
||||
"sameSite": "Lax"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Ubicación de cookies en perfil Chrome
|
||||
|
||||
```
|
||||
~/.navegator/profiles/<nombre>/
|
||||
├── Cookies # Base de datos SQLite con cookies
|
||||
├── Cookies-journal # Journal de transacciones
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Casos de uso
|
||||
|
||||
### Caso 1: Migrar sesión entre perfiles
|
||||
```go
|
||||
// Exportar cookies del perfil A
|
||||
browserA.ExportCookies(ctx, "session.json", CookieFormatJSON)
|
||||
|
||||
// Importar en perfil B
|
||||
browserB.ImportCookies(ctx, "session.json", CookieFormatJSON)
|
||||
```
|
||||
|
||||
### Caso 2: Backup de sesión autenticada
|
||||
```go
|
||||
// Guardar estado de sesión actual
|
||||
b.ExportCookies(ctx, "backup_session.json", CookieFormatJSON)
|
||||
|
||||
// Restaurar más tarde
|
||||
b2.ImportCookies(ctx, "backup_session.json", CookieFormatJSON)
|
||||
```
|
||||
|
||||
### Caso 3: Lanzar con sesión precargada
|
||||
```go
|
||||
config := browser.DefaultConfig()
|
||||
config.CookiesFile = "authenticated_session.json"
|
||||
b, _ := browser.Launch(ctx, config)
|
||||
// Ya está autenticado al iniciar
|
||||
```
|
||||
|
||||
### Caso 4: Sincronizar cookies entre máquinas
|
||||
```go
|
||||
// Máquina A - exportar
|
||||
GetProfileCookies("~/.navegator/profiles/main").Export("cookies.json")
|
||||
|
||||
// Máquina B - importar
|
||||
SetProfileCookies("~/.navegator/profiles/main", LoadCookies("cookies.json"))
|
||||
```
|
||||
|
||||
## Consideraciones de seguridad
|
||||
|
||||
⚠️ **Importante**: Las cookies pueden contener tokens de sesión y datos sensibles
|
||||
|
||||
- Advertir al usuario sobre seguridad de archivos exportados
|
||||
- Opción para encriptar archivos de cookies
|
||||
- No guardar cookies de sesión por defecto
|
||||
- Limpiar cookies sensibles en exports
|
||||
|
||||
## Referencias
|
||||
|
||||
- CDP Network.getCookies: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-getCookies
|
||||
- CDP Storage.getCookies: https://chromedevtools.github.io/devtools-protocol/tot/Storage/#method-getCookies
|
||||
- Chrome Cookies DB: SQLite format
|
||||
- Netscape cookies.txt: http://fileformats.archiveteam.org/wiki/Netscape_cookies.txt
|
||||
@@ -0,0 +1,288 @@
|
||||
# Issue #004: Administración de extensiones de Chrome
|
||||
|
||||
**Tipo**: Enhancement
|
||||
**Prioridad**: Media
|
||||
**Estado**: Pendiente
|
||||
|
||||
## Descripción
|
||||
|
||||
Implementar sistema completo para cargar, gestionar y configurar extensiones de Chrome en perfiles de navegador.
|
||||
|
||||
## Funcionalidad deseada
|
||||
|
||||
### Carga de extensiones
|
||||
- Cargar extensiones desde archivos `.crx` (empaquetadas)
|
||||
- Cargar extensiones desempaquetadas (carpetas)
|
||||
- Cargar múltiples extensiones simultáneamente
|
||||
- Especificar extensiones en configuración de perfil
|
||||
|
||||
### Gestión de extensiones
|
||||
- Listar extensiones instaladas en perfil
|
||||
- Habilitar/deshabilitar extensiones
|
||||
- Desinstalar extensiones
|
||||
- Actualizar extensiones
|
||||
- Obtener información de extensión (nombre, versión, ID)
|
||||
|
||||
### Extensiones predefinidas
|
||||
- Configuraciones para extensiones populares
|
||||
- uBlock Origin - bloqueador de ads
|
||||
- Tampermonkey - userscripts
|
||||
- Cookie editors
|
||||
- Proxy switchers
|
||||
- User-agent switchers
|
||||
|
||||
### Configuración de extensiones
|
||||
- Establecer configuración de extensión desde código
|
||||
- Importar/exportar configuraciones
|
||||
- Templates de configuración para casos comunes
|
||||
|
||||
## Implementación técnica
|
||||
|
||||
### Archivo sugerido
|
||||
`pkg/browser/extensions.go`
|
||||
|
||||
### Flags de Chrome necesarias
|
||||
|
||||
```go
|
||||
// Cargar extensión específica
|
||||
"--load-extension=/path/to/extension"
|
||||
|
||||
// Cargar múltiples extensiones
|
||||
"--load-extension=/path/ext1,/path/ext2,/path/ext3"
|
||||
|
||||
// Deshabilitar todas excepto las especificadas
|
||||
"--disable-extensions-except=/path/ext1,/path/ext2"
|
||||
|
||||
// Desempaquetar y cargar .crx
|
||||
"--load-extension=/path/to/extension.crx"
|
||||
```
|
||||
|
||||
### API propuesta
|
||||
|
||||
```go
|
||||
// === Configuración de extensiones ===
|
||||
|
||||
type ExtensionConfig struct {
|
||||
Path string // Ruta a extensión (carpeta o .crx)
|
||||
ID string // ID de extensión (opcional)
|
||||
Enabled bool // Habilitada por defecto
|
||||
Settings map[string]string // Configuración específica
|
||||
}
|
||||
|
||||
// Config.Extensions - campo para extensiones
|
||||
type Config struct {
|
||||
// ... campos existentes ...
|
||||
Extensions []*ExtensionConfig // Extensiones a cargar
|
||||
DisableOtherExts bool // Deshabilitar extensiones no especificadas
|
||||
}
|
||||
|
||||
// === Gestión en runtime ===
|
||||
|
||||
// Extension representa una extensión instalada
|
||||
type Extension struct {
|
||||
ID string
|
||||
Name string
|
||||
Version string
|
||||
Path string
|
||||
Enabled bool
|
||||
Description string
|
||||
}
|
||||
|
||||
// ListExtensions lista extensiones instaladas en el navegador actual
|
||||
func (b *Browser) ListExtensions(ctx context.Context) ([]*Extension, error)
|
||||
|
||||
// LoadExtension carga una extensión en runtime
|
||||
func (b *Browser) LoadExtension(ctx context.Context, path string) (*Extension, error)
|
||||
|
||||
// EnableExtension habilita una extensión
|
||||
func (b *Browser) EnableExtension(ctx context.Context, extensionID string) error
|
||||
|
||||
// DisableExtension deshabilita una extensión
|
||||
func (b *Browser) DisableExtension(ctx context.Context, extensionID string) error
|
||||
|
||||
// RemoveExtension desinstala una extensión
|
||||
func (b *Browser) RemoveExtension(ctx context.Context, extensionID string) error
|
||||
|
||||
// GetExtensionSettings obtiene configuración de una extensión
|
||||
func (b *Browser) GetExtensionSettings(ctx context.Context, extensionID string) (map[string]interface{}, error)
|
||||
|
||||
// SetExtensionSettings establece configuración de extensión
|
||||
func (b *Browser) SetExtensionSettings(ctx context.Context, extensionID string, settings map[string]interface{}) error
|
||||
|
||||
// === Extensiones predefinidas ===
|
||||
|
||||
// PresetExtensions contiene configuraciones de extensiones populares
|
||||
var PresetExtensions = map[string]*ExtensionConfig{
|
||||
"ublock-origin": {
|
||||
Path: "~/.navegator/extensions/ublock-origin",
|
||||
ID: "cjpalhdlnbpafiamejdnhcphjbkeiagm",
|
||||
},
|
||||
"tampermonkey": {
|
||||
Path: "~/.navegator/extensions/tampermonkey",
|
||||
ID: "dhdgffkkebhmkfjojejmpbldmpobfkfo",
|
||||
},
|
||||
}
|
||||
|
||||
// LoadPresetExtension carga una extensión predefinida
|
||||
func LoadPresetExtension(name string) (*ExtensionConfig, error)
|
||||
```
|
||||
|
||||
### Estructura de directorio de extensiones
|
||||
|
||||
```
|
||||
~/.navegator/
|
||||
├── profiles/
|
||||
│ └── <nombre>/
|
||||
│ └── Extensions/ # Extensiones instaladas del perfil
|
||||
│ └── <extension-id>/
|
||||
│ └── <version>/
|
||||
└── extensions/ # Extensiones compartidas
|
||||
├── ublock-origin/
|
||||
│ ├── manifest.json
|
||||
│ └── ...
|
||||
└── tampermonkey/
|
||||
├── manifest.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Casos de uso
|
||||
|
||||
### Caso 1: Lanzar con uBlock Origin
|
||||
```go
|
||||
config := browser.DefaultConfig()
|
||||
config.Extensions = []*browser.ExtensionConfig{
|
||||
{Path: "/path/to/ublock-origin"},
|
||||
}
|
||||
b, _ := browser.Launch(ctx, config)
|
||||
```
|
||||
|
||||
### Caso 2: Cargar extensión en runtime
|
||||
```go
|
||||
ext, _ := b.LoadExtension(ctx, "/path/to/extension")
|
||||
log.Printf("Cargada: %s v%s\n", ext.Name, ext.Version)
|
||||
```
|
||||
|
||||
### Caso 3: Usar extensión predefinida
|
||||
```go
|
||||
config := browser.DefaultConfig()
|
||||
ublock, _ := browser.LoadPresetExtension("ublock-origin")
|
||||
config.Extensions = []*browser.ExtensionConfig{ublock}
|
||||
b, _ := browser.Launch(ctx, config)
|
||||
```
|
||||
|
||||
### Caso 4: Gestionar extensiones existentes
|
||||
```go
|
||||
// Listar todas
|
||||
exts, _ := b.ListExtensions(ctx)
|
||||
for _, ext := range exts {
|
||||
log.Printf("%s: %s\n", ext.Name, ext.Enabled)
|
||||
}
|
||||
|
||||
// Deshabilitar extensión específica
|
||||
b.DisableExtension(ctx, "extension-id-here")
|
||||
```
|
||||
|
||||
### Caso 5: Configurar extensión
|
||||
```go
|
||||
// Configurar uBlock Origin con listas personalizadas
|
||||
b.SetExtensionSettings(ctx, "cjpalhdlnbpafiamejdnhcphjbkeiagm", map[string]interface{}{
|
||||
"customFilterLists": []string{
|
||||
"https://example.com/my-filters.txt",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Extensiones útiles para automatización
|
||||
|
||||
### Stealth y anti-detección
|
||||
- **Buster**: Solver de CAPTCHAs
|
||||
- **User-Agent Switcher**: Cambiar user agent
|
||||
- **Canvas Fingerprint Defender**: Anti-fingerprinting
|
||||
|
||||
### Scraping
|
||||
- **uBlock Origin**: Bloquear ads y trackers
|
||||
- **Cookie Editor**: Gestión avanzada de cookies
|
||||
- **Header Editor**: Modificar headers HTTP
|
||||
|
||||
### Automatización
|
||||
- **Tampermonkey**: Ejecutar userscripts personalizados
|
||||
- **Violentmonkey**: Alternativa a Tampermonkey
|
||||
|
||||
### Desarrollo
|
||||
- **React DevTools**: Inspeccionar componentes React
|
||||
- **Vue.js DevTools**: Inspeccionar aplicaciones Vue
|
||||
- **Redux DevTools**: Debugging de estado Redux
|
||||
|
||||
## Obtener extensiones
|
||||
|
||||
### Chrome Web Store
|
||||
```bash
|
||||
# URL de extensión en Chrome Web Store
|
||||
https://chrome.google.com/webstore/detail/<extension-id>
|
||||
|
||||
# Descargar .crx con herramientas
|
||||
# https://github.com/Rob--W/crxviewer
|
||||
```
|
||||
|
||||
### Desarrollo local
|
||||
```bash
|
||||
# Crear extensión simple
|
||||
mkdir my-extension
|
||||
cd my-extension
|
||||
cat > manifest.json <<EOF
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "My Extension",
|
||||
"version": "1.0",
|
||||
"description": "Custom extension"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
## CDP para gestión de extensiones
|
||||
|
||||
CDP no tiene soporte directo robusto para extensiones, pero podemos:
|
||||
|
||||
1. **Launch flags**: Usar `--load-extension` al inicio
|
||||
2. **Service workers**: Comunicarse con background scripts de extensión vía `chrome.runtime`
|
||||
3. **Extension pages**: Navegar a `chrome-extension://<id>/page.html`
|
||||
4. **Local storage**: Acceder a storage de extensión si es accesible
|
||||
|
||||
```go
|
||||
// Ejecutar código en contexto de extensión
|
||||
script := fmt.Sprintf(`
|
||||
chrome.runtime.sendMessage('%s', {action: 'configure'}, response => {
|
||||
return response;
|
||||
});
|
||||
`, extensionID)
|
||||
```
|
||||
|
||||
## Consideraciones especiales
|
||||
|
||||
### Manifest V3 vs V2
|
||||
- Chrome está migrando a Manifest V3
|
||||
- Algunas extensiones V2 dejarán de funcionar
|
||||
- Verificar compatibilidad al cargar extensiones
|
||||
|
||||
### Permisos
|
||||
- Extensiones pueden requerir permisos específicos
|
||||
- Algunas operaciones requieren interacción manual la primera vez
|
||||
- Considerar pre-configurar permisos en perfil
|
||||
|
||||
### Actualizaciones
|
||||
- Extensiones de Chrome Web Store se actualizan automáticamente
|
||||
- Extensiones locales no se actualizan
|
||||
- Implementar sistema de actualización manual si es necesario
|
||||
|
||||
### Headless mode
|
||||
- Algunas extensiones no funcionan en modo headless
|
||||
- Extensiones con UI pueden requerir modo visible
|
||||
- Probar compatibilidad con `--headless=new`
|
||||
|
||||
## Referencias
|
||||
|
||||
- Chrome Extensions: https://developer.chrome.com/docs/extensions/
|
||||
- Load unpacked extensions: https://developer.chrome.com/docs/extensions/mv3/getstarted/
|
||||
- Chrome Extension IDs: https://robwu.nl/crxviewer/
|
||||
- Manifest V3: https://developer.chrome.com/docs/extensions/mv3/intro/
|
||||
- Extension CLI flags: https://peter.sh/experiments/chromium-command-line-switches/
|
||||
@@ -0,0 +1,167 @@
|
||||
# Issue #005: Eliminar timeouts innecesarios del código
|
||||
|
||||
**Tipo**: Improvement
|
||||
**Prioridad**: Alta
|
||||
**Estado**: Pendiente
|
||||
|
||||
## Descripción
|
||||
|
||||
Eliminar todos los `time.Sleep()` y timeouts hardcodeados innecesarios del código, reemplazándolos con esperas basadas en eventos CDP cuando sea posible.
|
||||
|
||||
## Problema actual
|
||||
|
||||
El código tiene múltiples `time.Sleep()` arbitrarios:
|
||||
- `time.Sleep(2 * time.Second)` en examples/basic.go
|
||||
- `time.Sleep(3 * time.Second)` en cmd/list_blog.go
|
||||
- Timeouts hardcodeados en navegación
|
||||
|
||||
Estos timeouts son problemáticos porque:
|
||||
- No se adaptan a velocidad real de carga
|
||||
- Desperdicían tiempo en conexiones rápidas
|
||||
- Fallan en conexiones lentas
|
||||
- Hacen el código menos robusto
|
||||
|
||||
## Estrategia de reemplazo
|
||||
|
||||
### 1. Eventos CDP de carga de página
|
||||
|
||||
En lugar de:
|
||||
```go
|
||||
b.Navigate(ctx, url, nil)
|
||||
time.Sleep(3 * time.Second)
|
||||
```
|
||||
|
||||
Usar eventos CDP:
|
||||
```go
|
||||
opts := browser.DefaultNavigateOptions()
|
||||
opts.WaitUntil = "networkidle" // o "load" o "domcontentloaded"
|
||||
b.Navigate(ctx, url, opts)
|
||||
// No sleep necesario, Navigate espera el evento
|
||||
```
|
||||
|
||||
### 2. Esperar por selectores
|
||||
|
||||
En lugar de:
|
||||
```go
|
||||
time.Sleep(2 * time.Second)
|
||||
html, _ := b.GetHTML(ctx, ".content")
|
||||
```
|
||||
|
||||
Usar:
|
||||
```go
|
||||
b.WaitForSelector(ctx, ".content", 30*time.Second)
|
||||
html, _ := b.GetHTML(ctx, ".content")
|
||||
```
|
||||
|
||||
### 3. Esperar por condiciones JavaScript
|
||||
|
||||
En lugar de:
|
||||
```go
|
||||
time.Sleep(1 * time.Second)
|
||||
result, _ := b.Evaluate(ctx, "window.dataReady")
|
||||
```
|
||||
|
||||
Usar:
|
||||
```go
|
||||
b.WaitForFunction(ctx, "window.dataReady === true", 100*time.Millisecond)
|
||||
result, _ := b.Evaluate(ctx, "window.data")
|
||||
```
|
||||
|
||||
### 4. Eventos de red
|
||||
|
||||
Esperar que network esté idle:
|
||||
```go
|
||||
// Implementar WaitForNetworkIdle()
|
||||
b.WaitForNetworkIdle(ctx, 500*time.Millisecond, 30*time.Second)
|
||||
```
|
||||
|
||||
## Eventos CDP útiles
|
||||
|
||||
### Page domain
|
||||
- `Page.loadEventFired` - Página cargada completamente
|
||||
- `Page.domContentEventFired` - DOM listo
|
||||
- `Page.frameStoppedLoading` - Frame dejó de cargar
|
||||
|
||||
### Network domain
|
||||
- `Network.requestWillBeSent` - Request iniciado
|
||||
- `Network.responseReceived` - Response recibida
|
||||
- `Network.loadingFinished` - Recurso terminó de cargar
|
||||
- `Network.loadingFailed` - Recurso falló
|
||||
|
||||
## Métodos a implementar
|
||||
|
||||
```go
|
||||
// WaitForEvent espera un evento CDP específico
|
||||
func (b *Browser) WaitForEvent(ctx context.Context, eventName string, timeout time.Duration) error
|
||||
|
||||
// WaitForNetworkIdle espera que no haya requests de red por X tiempo
|
||||
func (b *Browser) WaitForNetworkIdle(ctx context.Context, idleTime, timeout time.Duration) error
|
||||
|
||||
// WaitForFunction espera que una función JS retorne true
|
||||
func (b *Browser) WaitForFunction(ctx context.Context, fn string, checkInterval time.Duration) error
|
||||
|
||||
// WaitForNavigation espera que navegación complete
|
||||
func (b *Browser) WaitForNavigation(ctx context.Context, timeout time.Duration) error
|
||||
```
|
||||
|
||||
## Archivos a revisar y actualizar
|
||||
|
||||
- [x] `examples/basic.go` - Eliminar time.Sleep
|
||||
- [x] `examples/advanced.go` - Reemplazar con esperas basadas en eventos
|
||||
- [x] `cmd/list_blog.go` - Usar WaitForSelector
|
||||
- [ ] `pkg/browser/navigation.go` - Mejorar Navigate() para esperar eventos
|
||||
- [ ] `pkg/browser/browser.go` - Agregar métodos de espera
|
||||
|
||||
## Implementación en Navigate()
|
||||
|
||||
```go
|
||||
func (b *Browser) Navigate(ctx context.Context, url string, opts *NavigateOptions) error {
|
||||
if opts == nil {
|
||||
opts = DefaultNavigateOptions()
|
||||
}
|
||||
|
||||
// Registrar listener ANTES de navegar
|
||||
loadedChan := make(chan struct{})
|
||||
b.client.On("Page.loadEventFired", func() {
|
||||
close(loadedChan)
|
||||
})
|
||||
|
||||
// Enviar comando de navegación
|
||||
_, err := b.client.SendCommand(ctx, "Page.navigate", map[string]interface{}{
|
||||
"url": url,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Esperar evento según opts.WaitUntil
|
||||
select {
|
||||
case <-loadedChan:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Beneficios
|
||||
|
||||
✅ **Más rápido**: No espera más de lo necesario
|
||||
✅ **Más robusto**: Falla con timeout claro, no con misterioso "elemento no encontrado"
|
||||
✅ **Más confiable**: Se adapta a velocidad real de página
|
||||
✅ **Mejor UX**: Feedback claro de qué se está esperando
|
||||
|
||||
## Testing
|
||||
|
||||
Probar con:
|
||||
- Conexiones rápidas (localhost)
|
||||
- Conexiones lentas (throttling)
|
||||
- Páginas con mucho JavaScript
|
||||
- Páginas con assets pesados
|
||||
- SPAs (React, Vue) que cargan async
|
||||
|
||||
## Referencias
|
||||
|
||||
- CDP Page events: https://chromedevtools.github.io/devtools-protocol/tot/Page/#event-loadEventFired
|
||||
- CDP Network events: https://chromedevtools.github.io/devtools-protocol/tot/Network/
|
||||
- Puppeteer waitFor: https://pptr.dev/guides/page-interactions#waiting
|
||||
Reference in New Issue
Block a user