El protocolo por debajo: ver el tráfico que genera cada clic
El monitor de protocolo, cómo se conecta un cliente externo, el punto de entrada de descubrimiento, y qué significa que toda la interfaz sea solo un cliente más.
Todo lo que hace la interfaz de las DevTools —expandir un nodo, poner un punto de parada, grabar un perfil, cambiar una propiedad— se traduce en mensajes de un protocolo que viajan a través de una conexión con el navegador. La interfaz no tiene ningún acceso privilegiado: es un cliente más. Esa arquitectura, que en el nivel de ontología era un hecho conceptual, aquí se vuelve práctica, porque significa que cualquier programa puede hacer exactamente lo mismo.
- Abrir el monitor de protocolo y leer el tráfico que genera una acción.
- Conectar un cliente externo al navegador y consultar sus destinos.
- Enviar comandos a mano y observar el resultado.
- Explicar qué implica que la interfaz sea un cliente sin privilegios.
El monitor de protocolo
Es una vista del cajón inferior que se abre por su nombre desde el menú de comandos y que registra todos los mensajes que la interfaz intercambia con el navegador.
Su valor didáctico es enorme: hacer una acción cualquiera en las DevTools y leer los mensajes que ha generado enseña más sobre el funcionamiento interno que cualquier documentación. Pulsar sobre un nodo del árbol produce una cascada de peticiones de nodos hijos; poner un punto de parada produce un comando concreto; grabar un perfil produce una activación de traza y un torrente de eventos.
Los mensajes tienen dos formas.
Comandos. Petición y respuesta, con un identificador que las empareja. Tienen un método, unos parámetros y un resultado.
Eventos. Del navegador hacia el cliente, sin petición previa. Notifican que algo ha pasado: un nodo se ha modificado, ha llegado una respuesta, se ha alcanzado un punto de parada.
El monitor tiene además un campo para enviar comandos a mano, y ahí es donde deja de ser un visor y pasa a ser una herramienta. Cualquier cosa que el protocolo pueda hacer, y hay mucho más de lo que la interfaz expone, se puede probar escribiendo el método y sus parámetros.
El uso más rentable del monitor no es aprender el protocolo sino descubrir qué comando corresponde a una acción que quieres automatizar. Haz la acción a mano con el monitor abierto, mira qué comando se envió, y ya tienes el método exacto y sus parámetros para tu script. Es infinitamente más rápido que buscar en la documentación del protocolo, que es enorme.
Conectar un cliente externo
El navegador expone el protocolo cuando se arranca con la opción de depuración remota y un puerto. Una vez arrancado así, hay un punto de entrada de descubrimiento sobre HTTP con varias rutas útiles.
La ruta de versión devuelve información del navegador y, sobre todo, la dirección de conexión al destino del navegador entero.
La ruta de lista devuelve todos los destinos inspeccionables —pestañas, workers, extensiones— cada uno con su identificador y su dirección de conexión.
La ruta de creación abre una pestaña nueva y devuelve su destino.
Con la dirección de conexión de un destino, cualquier cliente que hable el protocolo puede conectarse y empezar a enviar comandos.
// Cliente minimo del protocolo, ejecutable en Node contra un Chrome
// arrancado con la opcion de depuracion remota en el puerto 9222.
import WebSocket from 'ws'; // npm install ws
async function conectar(puerto = 9222) {
const lista = await fetch(`http://localhost:${puerto}/json/list`).then(r => r.json());
const pagina = lista.find(t => t.type === 'page');
if (!pagina) throw new Error('No hay ninguna pestana inspeccionable.');
console.log('Conectando a:', pagina.title, '|', pagina.url);
const ws = new WebSocket(pagina.webSocketDebuggerUrl);
await new Promise(r => ws.once('open', r));
let siguienteId = 1;
const pendientes = new Map();
const oyentes = new Map();
ws.on('message', (bruto) => {
const mensaje = JSON.parse(bruto);
if (mensaje.id && pendientes.has(mensaje.id)) {
const { resolver, rechazar } = pendientes.get(mensaje.id);
pendientes.delete(mensaje.id);
mensaje.error ? rechazar(new Error(mensaje.error.message)) : resolver(mensaje.result);
} else if (mensaje.method) {
(oyentes.get(mensaje.method) ?? []).forEach(fn => fn(mensaje.params));
}
});
return {
enviar(metodo, parametros = {}) {
const id = siguienteId++;
return new Promise((resolver, rechazar) => {
pendientes.set(id, { resolver, rechazar });
ws.send(JSON.stringify({ id, method: metodo, params: parametros }));
});
},
al(evento, fn) {
if (!oyentes.has(evento)) oyentes.set(evento, []);
oyentes.get(evento).push(fn);
},
cerrar() { ws.close(); }
};
}
// Uso: navegar, esperar y evaluar codigo en la pagina
const cliente = await conectar();
await cliente.enviar('Page.enable');
await cliente.enviar('Runtime.enable');
await cliente.enviar('Network.enable');
cliente.al('Network.responseReceived', ({ response }) => {
console.log(response.status, response.url.slice(0, 80));
});
await cliente.enviar('Page.navigate', { url: 'https://example.com' });
await new Promise(r => cliente.al('Page.loadEventFired', r));
const { result } = await cliente.enviar('Runtime.evaluate', {
expression: 'document.title + " | " + document.querySelectorAll("*").length + " elementos"',
returnByValue: true
});
console.log('Resultado de la evaluacion:', result.value);
cliente.cerrar();
Ese cliente de sesenta líneas hace lo mismo que hacen las librerías de automatización por debajo. No es un ejercicio teórico: entender que la automatización de navegadores es esto, y no una magia aparte, es lo que permite salirse del camino trillado cuando una librería no expone algo que el protocolo sí ofrece.
Qué implica que la interfaz sea un cliente
Cuatro consecuencias prácticas que se derivan directamente de esta arquitectura.
Todo lo que ves en la interfaz se puede automatizar. No hay funcionalidad que solo exista en el panel: si el panel puede hacerlo, es porque envía comandos, y esos comandos los puedes enviar tú.
Hay mucho más en el protocolo que en la interfaz. El protocolo cubre capacidades que ningún panel expone, y algunas son muy útiles: interceptar y modificar peticiones antes de que salgan, emular condiciones que no están en ningún menú, capturar imágenes con parámetros arbitrarios, controlar la recolección de basura.
Dos clientes pueden competir. Si tienes las DevTools abiertas y además un script conectado, los dos envían comandos y algunos son excluyentes. Es la causa de comportamientos extraños al automatizar con el panel abierto.
El protocolo es una superficie de seguridad. Un navegador arrancado con el puerto de depuración abierto y accesible desde la red permite a cualquiera controlarlo por completo: leer cookies, ejecutar código, capturar la pantalla. Nunca se expone ese puerto fuera de la máquina local.
El puerto de depuración no tiene autenticación por diseño. Un navegador con ese puerto escuchando en una interfaz accesible es equivalente a dar acceso completo a todas las sesiones abiertas en él. En un servidor de integración continua, el contenedor tiene que estar aislado; en una máquina de desarrollo, el puerto solo debe escuchar en la interfaz local.
Cuándo bajar a este nivel
Casi nunca hace falta escribir un cliente propio: las librerías de automatización cubren el noventa y cinco por ciento de los casos con una interfaz mucho más cómoda. Bajar al protocolo compensa en cuatro situaciones.
Cuando necesitas algo que la librería no expone. Todas las librerías serias permiten abrir una sesión directa del protocolo desde su propia interfaz, así que no hay que elegir: se usa la librería para lo normal y el protocolo para lo raro.
Cuando quieres escuchar eventos de bajo nivel. El flujo completo de eventos de red, de depurador o de traza da información que las abstracciones filtran.
Cuando construyes una herramienta. Un visor propio, un recolector de métricas, un sistema de captura.
Cuando algo falla y no entiendes por qué. El monitor de protocolo enseña qué está pasando realmente por debajo de la abstracción que estás usando.
El cambio de mentalidad que produce esta lección es más importante que cualquier comando concreto, y conviene enunciarlo. Mientras uno piensa en las DevTools como una aplicación con acceso especial al navegador, el conjunto de cosas posibles está limitado por lo que sus autores decidieron poner en la interfaz: si no hay un botón, no se puede hacer. En cuanto se entiende que la interfaz es un cliente sin privilegios que habla un protocolo documentado, ese límite desaparece y se sustituye por otro mucho más amplio: se puede hacer todo lo que el protocolo expone, y el protocolo expone bastante más de lo que ningún panel muestra. Eso reencuadra tres clases de problema que la gente da por irresolubles. Uno: las tareas repetitivas de depuración. Si haces la misma secuencia de veinte pasos cada vez que investigas cierto tipo de incidente, esa secuencia es automatizable entera, incluidas las partes que parecen manuales: cargar un estado, aplicar unas condiciones, provocar una interacción, recoger un perfil y extraer tres números de él. Dos: la instrumentación de lo que no se puede instrumentar. Cuando el problema está en código de terceros que no puedes modificar, el protocolo permite interceptar sus peticiones, sustituir su respuesta, poner puntos de parada en su código y observar su ejecución sin tocar una línea. Y tres: las herramientas propias. Muchas de las mejores herramientas internas que existen en equipos grandes son doscientas líneas conectadas a este protocolo que resuelven un problema muy específico de ese equipo: un comparador de perfiles entre ramas, un detector de regresiones visuales, un recolector de todas las peticiones de una batería de pruebas. La barrera de entrada real es solo saber que se puede, y a partir de ahí el trabajo es el de cualquier integración con una API.