5 puntos por GN⁺ 2023-11-12 | 1 comentarios | Compartir por WhatsApp
  • 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.trace y console.time en 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, getEventListeners y monitorEvents de 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.log desde 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
  • Uso de APIs de consola en el Watch Pane

    • También puedes poner expresiones como console.log en el Watch Pane
    • Cada vez que la aplicación se detiene en el depurador, puedes tomar un snapshot de localStorage con console.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
  • 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.trace en 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
  • 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 id en la función getPerson
    • 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 y console.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.body sea 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
  • 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 monitor de 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 aplicar monitor a 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 debug en lugar de monitor
  • 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 debugger y 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 fn está 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 de fn cada vez que sea invocada

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 debugger a history.pushState, history.replaceState, window.onhashchange y window.onpopstate
    const dbg = () => {
      debugger;
    };
    history.pushState = dbg;
    history.replaceState = dbg;
    window.onhashchange = dbg;
    window.onpopstate = dbg;
    
    • Esta versión de dbg no incluye una implementación que detenga sin romper la navegación
    • Si se llama directamente a window.location.replace o window.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 debug sobre esos métodos relacionados para ver la fuente del redirect y el estado en ese momento
    debug(window.location.replace);
    debug(window.location.assign);
    
  • 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 debugger dentro 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

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 mouseover lo modifican, puedes inspeccionar de forma estable el snapshot actual del DOM
  • 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 setInterval así
    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.activeElement para 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);
    })();
    
  • Encontrar elementos en negrita

    • Puedes encontrar elementos cuyo fontWeight calculado 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 $0 como base
    Array.from($0.querySelectorAll("*")).filter(isBold);
    
  • Manejar el elemento seleccionado y los eventos

    • $0 en 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

 
GN⁺ 2023-11-12
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.

    • También quiero aplaudir a quienes crearon la extensión Firebug, que impulsó en serio las herramientas para desarrolladores del navegador.
    • Me da curiosidad saber qué les parece tan especial de la experiencia de depuración en JS.
      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.
    • No es perfecto, pero es mucho mejor que la experiencia de depurar Node.js con VS Code que promovía un compañero.
      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.

    • Es uno de los bookmarklets que uso seguido: https://darekkay.com/blog/debugging-dynamic-content/
    • Esto pasa cuando un sitio web bloquea el panel lateral Console/DevTools abierto.
      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.
    • Si no quieres esperar 5 segundos, puedes usar el atajo de pausar la ejecución de scripts 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.
    • Ojalá los fabricantes de navegadores dejaran esto como una opción que se active explícitamente en DevTools y, por defecto, ignoraran la sentencia debugger.
      La mayoría de los casos legítimos de uso de la sentencia debugger pueden reemplazarse por un punto de interrupción simple si estás trabajando en tu propio código.
    • Espera, ¿eso es todo? Demasiado simple y demasiado bueno.
  • 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.

    • Sinceramente, sería justificable no querer difundir esto.
      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.

    1. https://github.com/dataarts/dat.gui
    2. https://codepen.io/russellbeattie/full/kGxaqM
    • Sentí la misma frustración.
      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.
    • Conviene probar desactivar los source maps en el depurador.
      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.
    • En el panel lateral de Chrome, las variables minificadas aparecen con el nombre correcto, pero en el watchpoint da un error de que no están definidas, y eso molesta.
  • 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?

    • IIFE significa expresión de función invocada inmediatamente
      https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
    • Si entraste a un breakpoint en una función llamada por el IIFE, simplemente puedes subir por el stack
      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 IIFE
      Si abusas de var e inicializas la variable fuera del scope del IIFE, sí puedes ver el valor creado por la última invocación del IIFE
    • Hace unos 12 años hice una herramienta que hacía eso
      Requiere 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
    • ¿Lo que buscas son logpoints?
      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
    • En Chrome, puedes inspeccionar el closure descrito en https://news.ycombinator.com/item?id=38226743#38231705 desde el panel Watch y luego ver la pseudopropiedad [[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

    • Es fácil: basta con poner 1/0 donde quieras que aparezca Werkzeug en el navegador
    • Quisiera tener esa experiencia en todas partes, incluso con nose.js y el navegador
      Es muy simple e intuitiva
    • ¿Todavía hace falta 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 maps

  • Tras trabajar mucho tiempo como programador de Python/Elixir, he usado mucho pdb.set_trace() e IEx.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ícola
    No puedo creer que un ecosistema tan popular no tenga un REPL de depuración decente; ¿alguien puede orientarme?

    • Sí lo tiene
      Usa node --inspect-brk y puedes conectarte con VS Code o Chrome DevTools
      La parte complicada es si hay herramientas de build, como transformación de TypeScript, delante del comando nodejs
      Si simplemente ejecutas node, es bastante fácil
      El REPL de depuración es la propia consola de JavaScript
    • Con las herramientas de desarrollador de VSCode se puede depurar NodeJS de forma bastante sólida
      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
    • Como dijeron otros, lo que quieres es --inspect o --inspect-brk
      En VSCode también puedes usar el comando “open JavaScript debug terminal”
      Ese comando abre una terminal en la que cualquier comando node se inicia automáticamente con el debugger adjunto
    • Quizá no sea suficiente, pero acabo de conocer python snoop
      https://pypi.org/project/snoop/
      Es un decorador para trazas profundas
    • Lo bueno del REPL de Elixir, parecido al de Erlang, es que permite conectarse a un proceso en ejecución
      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; }}

    • Eso no está bajo el título de breakpoints condicionales
      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?

    • Recomiendo mucho seguir la serie “What’s new in DevTools” del equipo de Chrome.
      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.