Técnicas de depuración que se usan en el navegador
(alan.norbauer.com)- Si ya manejas las herramientas de desarrollo del navegador a un nivel intermedio o superior, puedes ampliar los breakpoints condicionales más allá de simples condiciones de pausa y convertirlos en herramientas de registro, trazado, cambio de estado y medición de tiempo
- Si pones APIs de consola como
console.log,console.count,console.traceyconsole.timeen un breakpoint o en el Watch Pane, puedes registrar el flujo y el estado sin detener la ejecución - Si combinas la cantidad de argumentos de una función, el tiempo, valores calculados de CSS, el número de ejecuciones, muestreo aleatorio y booleanos globales, puedes controlar con mucha más precisión el momento en que se detiene
monitor,debug,copy,$0,getEventListenersymonitorEventsde Chrome permiten manejar rápidamente desde la consola llamadas a funciones, el DOM, eventos y operaciones con el portapapeles- En pantallas donde el DOM cambia constantemente por JavaScript o por eventos del usuario, pausar JS, usar
setTimeout(debugger), snapshots del DOM y monitoreo de foco es especialmente útil para la depuración de HTML/CSS
Ampliar los breakpoints condicionales como herramienta de depuración
-
En un breakpoint condicional no solo puedes poner condiciones verdadero/falso, sino también expresiones de JavaScript con efectos secundarios
-
Si aprovechas esto, puedes usar un breakpoint normal como si fuera una herramienta de registro, trazado, cambio de estado o medición de tiempo
-
Logpoints y tracepoints
- Si llamas a
console.logdesde un breakpoint condicional, funciona como un logpoint que deja valores en la consola sin detener la ejecución - Si usas
console.count, también puedes contar cuántas veces se ejecutó esa línea - Según la actualización de mayo de 2020, los principales navegadores ya soportan logpoints/tracepoints de forma nativa
- Si llamas a
-
Uso de APIs de consola en el Watch Pane
- También puedes poner expresiones como
console.logen el Watch Pane - Cada vez que la aplicación se detiene en el depurador, puedes tomar un snapshot de
localStorageconconsole.table(localStorage) - Si usas juntos un breakpoint por cambios en el DOM y expresiones del Watch, puedes guardar snapshots en un arreglo después de cambios en el subárbol del DOM
(window.doms = window.doms || []).push(document.documentElement.outerHTML)- No es posible hacer que un breakpoint por cambios en el DOM no detenga la ejecución
- También puedes poner expresiones como
-
Seguimiento del call stack
- Si tienes una función que muestra un spinner de carga y otra que lo oculta, y quieres encontrar el problema cuando solo queda la llamada a mostrarlo, puedes poner
console.traceen un breakpoint condicional - Si haces clic en el caller del último stack trace de la función que muestra el spinner, puedes ir a ese punto de llamada
- Si tienes una función que muestra un spinner de carga y otra que lo oculta, y quieres encontrar el problema cuando solo queda la llamada a mostrarlo, puedes poner
-
Cambiar el comportamiento del programa en tiempo de ejecución
- Si sobrescribes argumentos de función dentro de la expresión del breakpoint condicional, puedes cambiar al vuelo el comportamiento del programa dentro del navegador
- Por ejemplo, puedes cambiar el valor de
iden la funcióngetPerson - Una expresión como
id=1, que evalúa a verdadero, hace que el depurador se detenga; si no quieres que se detenga, puedes agregar, false
-
Medición rápida de rendimiento
- Como el tiempo de evaluación del breakpoint condicional se mezcla con la medición, no es adecuado para profiling de rendimiento preciso
- Si necesitas una medición rápida, puedes poner
console.time('label')en la condición del punto inicial yconsole.timeEnd('label')en la condición del punto final para dejar el tiempo de ejecución en la consola
Hacer más precisas las condiciones de pausa
-
Aprovechar la cantidad de argumentos de una función
- Si quieres detenerte solo cuando la función actual sea llamada con 3 argumentos, puedes usar esta condición
arguments.callee.length === 3- Es útil en funciones sobrecargadas con argumentos opcionales
- Si quieres detenerte solo cuando el número de argumentos definidos y el número de argumentos realmente pasados no coincidan, puedes usar esta condición
(arguments.callee.length) != arguments.length- Esto ayuda a encontrar bugs en el punto donde se llama la función
-
Condiciones basadas en tiempo
- Si no quieres detenerte antes de que hayan pasado 5 segundos desde que cargó la página, puedes usar esta condición
performance.now() > 5000- Es adecuado cuando solo te interesa el breakpoint después de la carga inicial de la página
- Si quieres que no se detenga durante los 5 segundos posteriores a configurar el breakpoint y luego sí se detenga, puedes guardar el tiempo base en una variable global
window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000- Desde la consola puedes reiniciar el contador con
window.baseline = Date.now()
-
Condiciones basadas en estado de CSS
- Puedes detenerte usando como condición un valor calculado de CSS
- Por ejemplo, si quieres detenerte solo cuando el color de fondo de
document.bodysea rojo, puedes usar esto
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)" -
Conteo de ejecuciones y muestreo
- Puedes incrementar un contador cada vez que se ejecuta una línea para detenerte una de cada dos veces
window.counter = (window.counter || 0) + 1, window.counter % 2 === 0- Si quieres hacer muestreo aleatorio para detenerte solo 1 de cada 10 veces, puedes usar esta condición
Math.random() < 0.1 -
Nunca detenerse en una ubicación específica
- En Chrome, si haces clic derecho en el gutter y eliges “Never Pause Here”, se crea un breakpoint condicional que siempre da
false, así que nunca se detiene en esa línea - Puede usarse para excluir líneas específicas de breakpoints XHR o para ignorar ciertas excepciones
- En Chrome, si haces clic derecho en el gutter y eliges “Never Pause Here”, se crea un breakpoint condicional que siempre da
-
Asignación automática de ID de instancia
- Si pones un breakpoint condicional en el constructor de una clase, puedes guardar cada instancia en un arreglo
(window.instances = window.instances || []).push(this)- El ID único de una instancia específica se puede verificar con el índice en el arreglo
window.instances.indexOf(instance) -
Toggle global de breakpoints
- Puedes controlar uno o más breakpoints condicionales con un booleano global
- Es posible cambiarlo directamente desde la consola, desde otro breakpoint o con un temporizador
window.enableBreakpoints = true setTimeout(() => (window.enableBreakpoints = true), 5000)
Seguimiento de llamadas de funciones y clases
-
Monitorear llamadas a métodos de clase
- Si usas el método de línea de comandos
monitorde Chrome, puedes rastrear fácilmente llamadas a métodos de clase - Si quieres ver llamadas a métodos de todas las instancias de la clase
Dog, puedes aplicarmonitora todas las propiedades del prototype
var p = Dog.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- En la consola se mostrará información de la llamada como
function bark called with arguments: 2 - Si en vez de solo registrar quieres que se detenga cuando se llame, puedes usar
debugen lugar demonitor
- Si usas el método de línea de comandos
-
Rastrear una clase desde una instancia específica
- Si no conoces el nombre de la clase pero tienes una instancia, puedes monitorearla igual a través del prototype del constructor
var p = instance.constructor.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- Es útil cuando quieres crear una función que opere sobre una instancia arbitraria sin quedar fija a una clase específica
-
Depurar justo antes de llamar una función desde la consola
- Antes de llamar desde la consola la función que quieres depurar, ejecuta
debuggery luego haz Step into en la siguiente llamada para entrar en la implementación de la función
debugger; fn(1);- Es útil cuando te da flojera buscar dónde está definida la función o cuando
fnestá enlazada dinámicamente y no conoces la ubicación del código fuente - En Chrome también puedes llamar
debug(fn)para que se detenga dentro defncada vez que sea invocada
- Antes de llamar desde la consola la función que quieres depurar, ejecuta
Depurar cambios de URL y lecturas de propiedades
-
Detenerse antes del routing en una SPA
- Si quieres detenerte justo antes de que una aplicación de una sola página cambie la URL, puedes conectar una función con
debuggerahistory.pushState,history.replaceState,window.onhashchangeywindow.onpopstate
const dbg = () => { debugger; }; history.pushState = dbg; history.replaceState = dbg; window.onhashchange = dbg; window.onpopstate = dbg;- Esta versión de
dbgno incluye una implementación que detenga sin romper la navegación - Si se llama directamente a
window.location.replaceowindow.location.assign, la página se descarga justo después de la asignación, así que este método no lo cubre - En Chrome puedes poner
debugsobre esos métodos relacionados para ver la fuente del redirect y el estado en ese momento
debug(window.location.replace); debug(window.location.assign); - Si quieres detenerte justo antes de que una aplicación de una sola página cambie la URL, puedes conectar una función con
-
Encontrar el punto donde se lee una propiedad
- Si quieres saber el momento exacto en que se lee una propiedad específica de un objeto, puedes poner
debuggerdentro del getter
{ get configOption() { debugger; return true; } }- Es útil para ver qué código usa una opción de configuración y de qué manera
- Puedes aplicarlo modificando el código fuente original o usando un breakpoint condicional
- Si quieres saber el momento exacto en que se lee una propiedad específica de un objeto, puedes poner
Copiar datos desde la consola
- Con la API de consola
copy()puedes copiar información del navegador al portapapeles sin que se recorten cadenas - Puedes usarla para copiar el snapshot actual del DOM, metadatos de recursos como imágenes, blobs JSON grandes o dumps de localStorage
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)
Depuración de HTML/CSS
-
Inspeccionar el DOM con la ejecución de JS detenida
- En el inspector del DOM, en Chrome/Windows puedes presionar
ctrl+\para detener la ejecución de JS en cualquier momento - Incluso si JavaScript cambia el DOM o eventos como
mouseoverlo modifican, puedes inspeccionar de forma estable el snapshot actual del DOM
- En el inspector del DOM, en Chrome/Windows puedes presionar
-
Inspeccionar elementos que desaparecen
- Si tienes un elemento condicional del DOM que desaparece al mover el mouse, puedes inspeccionarlo poniendo un temporizador de 5 segundos en la consola para ejecutar el depurador
setTimeout(function() { debugger; }, 5000);- Si dejas visible la UI durante 5 segundos y luego se detiene la ejecución de JS, el elemento no desaparecerá, así que puedes pasar a las herramientas de desarrollo e inspeccionarlo
- Mientras la ejecución de JS esté detenida, puedes inspeccionar elementos, editar CSS y ejecutar comandos en la consola de JS
- Esto es efectivo para inspeccionar DOM que depende de la posición del cursor o del foco
-
Registrar snapshots del DOM
- Si quieres copiar el DOM actual, puedes usar este comando
copy(document.documentElement.outerHTML);- Si quieres guardar la cadena del DOM en un arreglo cada segundo, puedes usar
setIntervalasí
doms = []; setInterval(() => { const domStr = document.documentElement.outerHTML; doms.push(domStr); }, 1000);- También puedes seguir imprimiéndolo en la consola en lugar de guardarlo en un arreglo
-
Monitorear el elemento enfocado
- Puedes comparar periódicamente
document.activeElementpara imprimir en la consola los cambios de foco
(function () { let last = document.activeElement; setInterval(() => { if (document.activeElement !== last) { last = document.activeElement; console.log("Focus changed to: ", last); } }, 100); })(); - Puedes comparar periódicamente
-
Encontrar elementos en negrita
- Puedes encontrar elementos cuyo
fontWeightcalculado sea"bold"o"700"en todos los elementos
const isBold = (e) => { let w = window.getComputedStyle(e).fontWeight; return w === "bold" || w === "700"; }; Array.from(document.querySelectorAll("*")).filter(isBold);- Si quieres buscar solo entre los descendientes del elemento actualmente seleccionado en el inspector, puedes usar
$0como base
Array.from($0.querySelectorAll("*")).filter(isBold); - Puedes encontrar elementos cuyo
-
Manejar el elemento seleccionado y los eventos
$0en la consola es una referencia automática al elemento actualmente seleccionado en el Element Inspector- En Chrome y Edge, puedes acceder a elementos inspeccionados anteriormente como
$1, y al elemento anterior a ese como$2 - En Chrome, puedes revisar los event listeners del elemento actualmente seleccionado
getEventListeners($0)- Para depurar todos los eventos del elemento seleccionado, puedes usar
monitorEvents($0) - Si solo quieres ver grupos específicos de eventos, puedes pasar un arreglo
monitorEvents($0, ["control", "key"])
1 comentarios
Opiniones de Hacker News
En las últimas décadas, las herramientas de depuración integradas en los navegadores han avanzado muchísimo.
Como alguien que lleva mucho tiempo usando JS, estoy profundamente agradecido con quienes hicieron que depurar código dentro del navegador fuera tan intuitivo.
Cuando paso a otras áreas de desarrollo, como backend u otros lenguajes, extraño este ecosistema de herramientas de depuración que los navegadores modernos ofrecen de serie.
He depurado mucho durante años en Python, Java y C++, y nunca sentí que faltaran herramientas.
Claro, sí he visto a bastante gente que no conoce las opciones disponibles en esta área.
Antes que ese infierno, prefiero la consola.
setTimeout(function() { debugger; }, 5000);es ingenioso.Al final, la única forma de vencer esa pila recursiva de tortugas donde el depurador de Chrome se depura a sí mismo es la sentencia
debugger.El sam.pl del infame gusano Sammy de Myspace también usaba trampas de depuración para impedir que los visitantes escarbaran en la página HTML ofuscada.
Recorre el loop principal insertando molestas sentencias
debugger;por todos lados y las ejecuta a 30 FPS / intervalos de 32 ms, volviendo inútiles las DevTools.Porque no hay forma de “ignorar” la sentencia
debugger.cmd-\.La página se detiene en la línea de JS que se estaba ejecutando en ese momento, y puedes inspeccionar el elemento problemático como siempre.
debugger.La mayoría de los casos legítimos de uso de la sentencia
debuggerpueden reemplazarse por un punto de interrupción simple si estás trabajando en tu propio código.Se nota la ausencia de
queryObjects.Es una API bastante loca que devuelve la lista de todos los objetos creados con un constructor específico.
Por ejemplo, con
queryObjects(Function)puedes obtener la lista de todas las funciones en el heap.Incluso devuelve funciones “private” que están dentro de algún módulo.
Es una función realmente demencial, y probablemente sea exclusiva de Chromium.
Nunca logro usar bien las variables vigiladas.
Las reglas de alcance y actualización son demasiado impredecibles; parece que solo se pueden vigilar variables globales, pero ni siquiera eso funciona como esperaba, así que al probar termino inundando todo de logs con valores.
Llevo años pensando que habría que poner en la consola una UI al estilo Data.gui [1] para poder ver y probar variables y valores de configuración.
En este CodePen [2] se puede ver funcionando.
En teoría, el navegador tiene muchas funciones de depuración excelentes, pero en la práctica no parecen funcionar de forma confiable.
Ni siquiera logro que todos los breakpoints se activen de manera estable.
Cuando el código está sin procesar está bien, pero apenas se empaqueta en un bundle, aunque no esté minificado, muchas funciones del depurador parecen romperse.
Como no soy ingeniero frontend, quizá esté haciendo algo mal.
Es muy probable que sean la causa de la frustración, y todavía no entiendo por qué vienen activados por defecto cuando la experiencia de depuración activa con ellos habilitados es tan mala.
Sería bueno tener una forma de acceder a las variables locales de un IIFE sin detener la ejecución del código dentro del scope del IIFE
¿Habrá alguna manera de convencer al debugger para que haga eso?
https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
Si estás completamente fuera del IIFE, diría que es imposible
Es posible que la variable que buscas no exista en memoria antes o después de la ejecución
JavaScript no tiene variables estáticas como otros lenguajes, así que las variables internas se descartan cada vez que se invoca el IIFE
JavaScript tiene un modelo de un solo hilo bastante fuerte, salvo que uses cosas como web workers, así que si no estás intentando crear una condición de carrera directamente con llamadas
async/await, es difícil que tenga sentido siquiera el concepto de que esa variable siga en memoria fuera del scope del IIFESi abusas de
vare inicializas la variable fuera del scope del IIFE, sí puedes ver el valor creado por la última invocación del IIFERequiere algunos ajustes, pero rinde mucho para el esfuerzo que demanda
https://github.com/kristopolous/_inject
Puedes moverte por casi cualquier contexto de función en un momento arbitrario y ver qué ocurrió
Funciona abusando de los contadores de referencia para impedir que el contexto se destruya
Cuando hacía mucho JS del lado del cliente, era realmente útil
La versión killer app de esto, bien hecha, sería abrir un REPL en cualquier contexto
Incluso en su estado actual requiere bastante pericia para usarla bien
https://firefox-source-docs.mozilla.org/devtools-user/debugg...
Edición: acabo de darme cuenta de que es literalmente lo primero que trata el artículo enlazado
[[Scopes]]En Firefox parece que no hay forma
Para completar, se puede recomendar Werkzeug
Lo uso para desarrollo backend con Django y es tremendamente útil
Te permite usar una shell “PDB” dentro del navegador, en cualquier momento y lugar donde ocurra una excepción
1/0donde quieras que aparezca Werkzeug en el navegadorEs muy simple e intuitiva
django-extension/runserver_plus?Un truco que uso siempre es buscar scripts cargados por cadenas de la UI para depurar
Ve al panel Network, empieza a registrar solicitudes de red, abre la barra lateral izquierda y busca el código o la cadena de UI que quieras encontrar
Normalmente aparece dentro de algún chunk raro de JS empaquetado, y al hacer clic en el resultado se abre la solicitud de red de ese archivo
Haz clic derecho en cualquier parte de ese archivo y elige algo como “Open in Sources” para ir al debugger
Ahora puedes poner una sentencia
debugger, y en el proceso probablemente también se carguen los source mapsTras trabajar mucho tiempo como programador de Python/Elixir, he usado mucho
pdb.set_trace()eIEx.pry()Hace poco heredé un backend NodeJS bastante desordenado y, sin herramientas de depuración decentes, me estoy arrancando el poco pelo que me queda
Volví a depurar con
console.log, pero me siento como un cavernícolaNo puedo creer que un ecosistema tan popular no tenga un REPL de depuración decente; ¿alguien puede orientarme?
Usa
node --inspect-brky puedes conectarte con VS Code o Chrome DevToolsLa parte complicada es si hay herramientas de build, como transformación de TypeScript, delante del comando
nodejsSi simplemente ejecutas
node, es bastante fácilEl REPL de depuración es la propia consola de JavaScript
Si lo agregas como opción de depuración, puedes poner breakpoints y ejecutar paso a paso dentro de funciones
Como la mayor parte está encapsulada en objetos, JavaScript es bastante agradable de depurar
--inspecto--inspect-brkEn VSCode también puedes usar el comando “open JavaScript debug terminal”
Ese comando abre una terminal en la que cualquier comando
nodese inicia automáticamente con el debugger adjuntohttps://pypi.org/project/snoop/
Es un decorador para trazas profundas
No he visto algo así en el ecosistema de Node JS
En la sección “Debugging Property Reads”, me pregunto cómo se usa un breakpoint condicional para cambiar
{configOption: true}por{get configOption() { debugger; return true; }}Puedes sobrescribir el valor con un getter desde la consola, y si tienes permisos de escritura también puedes cambiarlo directamente en el código fuente
Esto parece algo que definitivamente hay que aprender, pero me pregunto si hay libros o materiales de aprendizaje dedicados a esto.
¿O no queda otra que profundizar sin parar en desarrollo web y frontend?
Puede que, cuando estés en pleno trabajo, no te den ganas de hacer clic en un enlace para leer las notas de lanzamiento, pero con solo darles una mirada de 5 minutos cada vez que sale una nueva versión, ya vale la pena.
También hay videos de pocos minutos, fáciles de digerir, que sirven para obtener un panorama rápido.
Aunque el objetivo es presentar nuevas funciones, según mi experiencia también ayuda a entender con frecuencia las limitaciones actuales de las herramientas.