Techniques de débogage à utiliser dans le navigateur
(alan.norbauer.com)- Si vous maîtrisez les outils de développement du navigateur à un niveau intermédiaire ou avancé, vous pouvez étendre les points d’arrêt conditionnels au-delà de simples conditions d’arrêt pour en faire des outils de journalisation, de traçage, de modification d’état et de mesure du temps
- En plaçant des API de console comme
console.log,console.count,console.traceouconsole.timedans un point d’arrêt ou dans le Watch Pane, vous pouvez enregistrer le flux d’exécution et l’état sans interrompre l’exécution - En combinant le nombre d’arguments d’une fonction, le temps, les valeurs CSS calculées, le nombre d’exécutions, l’échantillonnage aléatoire et un booléen global, vous pouvez contrôler beaucoup plus finement le moment où l’exécution s’arrête
- Dans Chrome,
monitor,debug,copy,$0,getEventListenersetmonitorEventspermettent de manipuler rapidement depuis la console les appels de fonctions, le DOM, les événements et les opérations de presse-papiers - Sur les écrans où le DOM change en permanence sous l’effet de JS ou d’événements utilisateur, la mise en pause de JS,
setTimeout(debugger), les instantanés DOM et la surveillance du focus sont particulièrement utiles pour le débogage HTML/CSS
Étendre les points d’arrêt conditionnels en outils de débogage
-
Dans un point d’arrêt conditionnel, vous pouvez mettre non seulement une condition vrai/faux, mais aussi une expression JavaScript avec des effets de bord
-
Cela permet d’utiliser les points d’arrêt de base comme des outils de journalisation, de traçage, de modification d’état ou de mesure du temps
-
Logpoints et tracepoints
- Appeler
console.logdans un point d’arrêt conditionnel le fait se comporter comme un logpoint, qui écrit une valeur dans la console sans interrompre l’exécution - Avec
console.count, vous pouvez aussi compter combien de fois la ligne a été exécutée - Depuis la mise à jour de mai 2020, les principaux navigateurs prennent directement en charge les logpoints/tracepoints
- Appeler
-
Utiliser les API de console dans le Watch Pane
- Vous pouvez aussi mettre des expressions comme
console.logdans le Watch Pane - Chaque fois que l’application s’arrête dans le débogueur,
console.table(localStorage)permet de prendre un instantané de localStorage - En combinant un point d’arrêt sur modification du DOM avec une expression Watch, vous pouvez enregistrer dans un tableau des instantanés après modification d’un sous-arbre DOM
(window.doms = window.doms || []).push(document.documentElement.outerHTML)- Les points d’arrêt sur modification du DOM ne peuvent pas être configurés pour ne pas interrompre l’exécution
- Vous pouvez aussi mettre des expressions comme
-
Tracer la pile d’appels
- Pour trouver un problème où une fonction affiche un spinner de chargement et une autre le masque, mais où seul l’appel d’affichage subsiste, vous pouvez mettre
console.tracedans un point d’arrêt conditionnel - En cliquant sur l’appelant dans la dernière stack trace de la fonction d’affichage, vous pouvez accéder à l’emplacement de cet appel
- Pour trouver un problème où une fonction affiche un spinner de chargement et une autre le masque, mais où seul l’appel d’affichage subsiste, vous pouvez mettre
-
Modifier le comportement d’un programme en cours d’exécution
- En écrasant les arguments d’une fonction dans l’expression d’un point d’arrêt conditionnel, vous pouvez modifier à la volée le comportement du programme dans le navigateur
- Par exemple, vous pouvez modifier la valeur
idde la fonctiongetPerson - Une expression évaluée à vrai comme
id=1arrête le débogueur ; pour éviter l’arrêt, vous pouvez ajouter, false
-
Mesure rapide des performances
- Comme le temps d’évaluation du point d’arrêt conditionnel s’y mêle, cela ne convient pas au profilage de performance précis
- Quand vous avez besoin d’une mesure rapide, vous pouvez mettre
console.time('label')dans la condition au point de départ etconsole.timeEnd('label')dans la condition au point d’arrivée afin d’enregistrer le temps d’exécution dans la console
Rendre les conditions d’arrêt plus précises
-
Utiliser le nombre d’arguments d’une fonction
- Pour s’arrêter uniquement lorsque la fonction courante est appelée avec 3 arguments, vous pouvez utiliser la condition suivante
arguments.callee.length === 3- C’est utile dans les fonctions surchargées avec des arguments optionnels
- Pour s’arrêter uniquement lorsque le nombre d’arguments définis et le nombre d’arguments réellement passés diffèrent, vous pouvez utiliser la condition suivante
(arguments.callee.length) != arguments.length- Cela aide à trouver des bugs dans les sites d’appel de la fonction
-
Conditions basées sur le temps
- Pour ne pas s’arrêter avant que 5 secondes se soient écoulées après le chargement de la page, vous pouvez utiliser la condition suivante
performance.now() > 5000- C’est adapté lorsque vous ne vous intéressez au point d’arrêt qu’après le chargement initial de la page
- Pour que l’exécution ne s’arrête pas pendant les 5 secondes suivant la définition du point d’arrêt, puis s’arrête ensuite, vous pouvez stocker l’heure de référence dans une variable globale
window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000- Vous pouvez redémarrer le compteur depuis la console avec
window.baseline = Date.now()
-
Conditions basées sur l’état CSS
- Vous pouvez vous arrêter en prenant comme condition une valeur CSS calculée
- Par exemple, pour s’arrêter uniquement lorsque la couleur d’arrière-plan de
document.bodyest rouge, vous pouvez utiliser la condition suivante
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)" -
Nombre d’exécutions et échantillonnage
- Vous pouvez incrémenter un compteur à chaque exécution d’une ligne pour ne vous arrêter qu’une fois sur deux
window.counter = (window.counter || 0) + 1, window.counter % 2 === 0- Pour ne vous arrêter qu’une fois sur 10 avec un échantillonnage aléatoire, vous pouvez utiliser la condition suivante
Math.random() < 0.1 -
Ne jamais s’arrêter à un emplacement précis
- Dans Chrome, en faisant un clic droit sur la gouttière et en sélectionnant “Never Pause Here”, un point d’arrêt conditionnel toujours à
falseest créé, ce qui empêche l’arrêt sur cette ligne - Cela peut servir à exclure une ligne précise d’un point d’arrêt XHR ou à ignorer une exception spécifique
- Dans Chrome, en faisant un clic droit sur la gouttière et en sélectionnant “Never Pause Here”, un point d’arrêt conditionnel toujours à
-
Attribution automatique d’ID d’instance
- Vous pouvez placer un point d’arrêt conditionnel dans le constructeur d’une classe pour enregistrer chaque instance dans un tableau
(window.instances = window.instances || []).push(this)- L’ID unique d’une instance donnée peut être vérifié via son index dans le tableau
window.instances.indexOf(instance) -
Bascule globale des points d’arrêt
- Vous pouvez contrôler un ou plusieurs points d’arrêt conditionnels avec un booléen global
- Il est possible de le modifier directement depuis la console, depuis un autre point d’arrêt, ou via un timer
window.enableBreakpoints = true setTimeout(() => (window.enableBreakpoints = true), 5000)
Tracer les appels de fonctions et de classes
-
Surveiller les appels de méthodes de classe
- La méthode en ligne de commande
monitorde Chrome permet de tracer facilement les appels de méthodes de classe - Si vous voulez voir les appels de méthodes pour toutes les instances de la classe
Dog, vous pouvez appliquermonitorà toutes les propriétés du prototype
var p = Dog.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- La console affiche des informations d’appel comme
function bark called with arguments: 2 - Si vous voulez arrêter l’exécution lors de l’appel au lieu de seulement journaliser, vous pouvez utiliser
debugà la place demonitor
- La méthode en ligne de commande
-
Tracer une classe depuis une instance donnée
- Si vous ne connaissez pas le nom de la classe mais disposez d’une instance, vous pouvez la surveiller de la même manière via le prototype de son constructeur
var p = instance.constructor.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- C’est utile pour créer une fonction qui agit sur une instance arbitraire sans être liée à une classe précise
-
Déboguer juste avant un appel de fonction depuis la console
- Avant d’appeler depuis la console la fonction à déboguer, exécutez
debugger, puis faites Step into sur l’appel suivant pour entrer dans l’implémentation de la fonction
debugger; fn(1);- C’est utile lorsqu’il est fastidieux de trouver l’emplacement de définition de la fonction, ou lorsque
fnest liée dynamiquement et que son emplacement source est inconnu - Dans Chrome, vous pouvez aussi appeler
debug(fn)pour vous arrêter à l’intérieur de la fonction chaque fois quefnest appelée
- Avant d’appeler depuis la console la fonction à déboguer, exécutez
Débogage des changements d’URL et des lectures de propriétés
-
S’arrêter avant le routage d’une SPA
- Pour s’arrêter juste avant qu’une application monopage modifie l’URL, vous pouvez associer une fonction contenant
debuggeràhistory.pushState,history.replaceState,window.onhashchangeetwindow.onpopstate
const dbg = () => { debugger; }; history.pushState = dbg; history.replaceState = dbg; window.onhashchange = dbg; window.onpopstate = dbg;- Cette version de
dbgn’inclut pas d’implémentation permettant de s’arrêter sans casser la navigation - Si
window.location.replaceouwindow.location.assignest appelé directement, la page est déchargée juste après l’affectation, donc cette méthode ne permet pas de le gérer - Dans Chrome, vous pouvez placer
debugsur les méthodes concernées pour voir la source de la redirection et l’état à ce moment-là
debug(window.location.replace); debug(window.location.assign); - Pour s’arrêter juste avant qu’une application monopage modifie l’URL, vous pouvez associer une fonction contenant
-
Trouver le point de lecture d’une propriété
- Si vous voulez savoir à quel moment une propriété précise d’un objet est lue, vous pouvez mettre
debuggerdans un getter
{ get configOption() { debugger; return true; } }- C’est utile pour vérifier quel code utilise une option de configuration et comment
- Vous pouvez l’appliquer en modifiant la source originale ou en utilisant un point d’arrêt conditionnel
- Si vous voulez savoir à quel moment une propriété précise d’un objet est lue, vous pouvez mettre
Copier des données depuis la console
- L’API de console
copy()permet de copier des informations du navigateur dans le presse-papiers sans troncature des chaînes - Vous pouvez l’utiliser pour copier l’instantané DOM courant, les métadonnées de ressources comme les images, un gros blob JSON ou un dump de localStorage
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)
Débogage HTML/CSS
-
Inspecter le DOM pendant que l’exécution JS est en pause
- Dans l’inspecteur DOM, sous Chrome/Windows, appuyer sur
ctrl+\permet de mettre en pause l’exécution JS à tout moment - Même lorsque JS modifie le DOM ou que des événements comme mouseover modifient le DOM, vous pouvez examiner de façon fiable l’instantané DOM courant
- Dans l’inspecteur DOM, sous Chrome/Windows, appuyer sur
-
Inspecter des éléments qui disparaissent
- Pour inspecter un élément DOM conditionnel qui disparaît quand vous déplacez la souris, vous pouvez lancer le débogueur avec un timer de 5 secondes dans la console
setTimeout(function() { debugger; }, 5000);- Après avoir affiché l’UI pendant 5 secondes, l’exécution JS s’arrête, l’élément ne disparaît donc plus, et vous pouvez passer aux outils de développement pour l’inspecter
- Pendant que l’exécution JS est en pause, vous pouvez inspecter des éléments, modifier le CSS et exécuter des commandes dans la console JS
- C’est efficace pour inspecter un DOM qui dépend de la position du curseur ou du focus
-
Enregistrer des instantanés DOM
- Pour copier le DOM courant, vous pouvez utiliser la commande suivante
copy(document.documentElement.outerHTML);- Pour enregistrer la chaîne DOM dans un tableau chaque seconde, vous pouvez utiliser
setIntervalcomme suit
doms = []; setInterval(() => { const domStr = document.documentElement.outerHTML; doms.push(domStr); }, 1000);- Vous pouvez aussi l’afficher en continu dans la console sans l’enregistrer dans un tableau
-
Surveiller l’élément focalisé
- Vous pouvez comparer périodiquement
document.activeElementet afficher les changements de focus dans la console
(function () { let last = document.activeElement; setInterval(() => { if (document.activeElement !== last) { last = document.activeElement; console.log("Focus changed to: ", last); } }, 100); })(); - Vous pouvez comparer périodiquement
-
Trouver les éléments affichés en gras
- Vous pouvez trouver tous les éléments dont le
fontWeightcalculé est"bold"ou"700"
const isBold = (e) => { let w = window.getComputedStyle(e).fontWeight; return w === "bold" || w === "700"; }; Array.from(document.querySelectorAll("*")).filter(isBold);- Pour ne chercher que dans les descendants de l’élément actuellement sélectionné dans l’inspecteur, vous pouvez effectuer la recherche à partir de
$0
Array.from($0.querySelectorAll("*")).filter(isBold); - Vous pouvez trouver tous les éléments dont le
-
Manipuler l’élément sélectionné et les événements
- Dans la console,
$0est une référence automatique vers l’élément actuellement sélectionné dans l’Element Inspector - Dans Chrome et Edge, vous pouvez accéder aux éléments inspectés précédemment avec
$1, puis$2, etc. - Dans Chrome, vous pouvez vérifier les écouteurs d’événements de l’élément actuellement sélectionné
getEventListeners($0)- Pour déboguer tous les événements de l’élément sélectionné, vous pouvez utiliser
monitorEvents($0) - Si vous voulez ne voir qu’un groupe d’événements spécifique, vous pouvez passer un tableau
monitorEvents($0, ["control", "key"]) - Dans la console,
1 commentaires
Avis sur Hacker News
Au cours des dernières décennies, les outils de débogage intégrés aux navigateurs ont vraiment énormément progressé.
En tant que personne qui utilise JS depuis longtemps, je suis profondément reconnaissant envers celles et ceux qui ont rendu le débogage de code dans le navigateur aussi intuitif.
Quand je passe à d’autres domaines du développement, comme le backend ou d’autres langages, cet écosystème d’outils de débogage que les navigateurs modernes fournissent par défaut me manque.
J’ai beaucoup débogué en Python, Java et C++ pendant des années, et je n’ai jamais eu le sentiment de manquer d’outils.
Bien sûr, j’ai vu pas mal de gens qui ne connaissaient pas les options disponibles dans ce domaine.
Entre cet enfer et la console, je choisis la console.
setTimeout(function() { debugger; }, 5000);est malin.Au final, la seule façon pour le débogueur de Chrome de venir à bout de cette pile récursive de tortues où il se débogue lui-même, c’est l’instruction
debugger.Le célèbre ver Myspace Sammy, sam.pl, utilisait aussi des pièges de débogage pour empêcher les visiteurs de décortiquer la page d’accueil HTML obfusquée.
Il fait tourner la boucle principale en insérant un peu partout des instructions
debugger;agaçantes, exécutées à 30 FPS / toutes les 32 ms, ce qui rend DevTools inutilisable.Parce qu’il n’existe aucun moyen « d’ignorer » l’instruction
debugger.cmd-\.La page s’arrête sur la ligne JS en cours d’exécution, et vous pouvez inspecter l’élément problématique comme d’habitude.
debuggerune option à activer explicitement dans DevTools, et qu’elle soit activée par défaut.La plupart des usages légitimes de l’instruction
debugger, quand on travaille sur son propre code, peuvent être remplacés par un simple point d’arrêt.L’absence de
queryObjectssaute aux yeux.C’est une API assez folle qui renvoie la liste de tous les objets créés avec un constructeur donné.
Par exemple,
queryObjects(Function)permet d’obtenir la liste de toutes les fonctions présentes dans le tas.Elle renvoie même les fonctions « private » à l’intérieur d’un module.
C’est vraiment une fonctionnalité dingue, et probablement réservée à Chromium.
Je n’arrive tout simplement pas à utiliser correctement les variables surveillées.
Les règles de portée et de mise à jour sont trop opaques ; on pourrait croire que seules les variables globales peuvent être surveillées, mais même là ça ne fonctionne pas comme prévu, si bien qu’en test je finis par inonder les logs de valeurs.
Cela fait des années que je pense qu’il faudrait intégrer à la console une UI à la Data.gui [1], pour pouvoir voir les variables et les valeurs de configuration pendant les tests.
On peut la voir fonctionner dans ce CodePen [2].
En théorie, les navigateurs ont beaucoup d’excellentes fonctionnalités de débogage, mais en pratique elles ne semblent pas fonctionner de façon fiable.
Je n’arrive même pas à faire en sorte que tous les points d’arrêt se déclenchent de manière stable.
Quand le code est non transformé, ça va ; mais dès qu’il est bundlé, même sans être minifié, beaucoup de fonctions du débogueur semblent se casser.
Comme je ne suis pas ingénieur frontend, je fais peut-être quelque chose de travers.
C’est probablement la cause de la frustration, et je ne comprends toujours pas pourquoi elles sont activées par défaut alors que l’expérience de débogage actif avec elles est aussi mauvaise.
Ce serait bien d’avoir un moyen d’accéder aux variables locales d’une IIFE sans interrompre l’exécution du code dans le scope de l’IIFE
Existe-t-il un moyen de convaincre le débogueur de faire ça ?
https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
Si vous êtes complètement en dehors de l’IIFE, je pense que c’est impossible
La variable que vous cherchez peut ne pas exister en mémoire avant ou après l’exécution
JavaScript n’a pas de variables statiques comme d’autres langages, donc à chaque appel de l’IIFE, les variables internes sont jetées
JavaScript a un modèle monothread assez strict, sauf si vous utilisez des choses comme les web workers ; donc, à moins de créer volontairement une condition de concurrence avec des appels
async/await, l’idée même que ce genre de variable reste en mémoire hors du scope de l’IIFE tient difficilementSi vous abusez de
varpour initialiser une variable hors du scope de l’IIFE, vous pouvez voir la valeur créée par le dernier appel de l’IIFEIl faudrait quelques ajustements, mais le rapport effort/bénéfice est élevé
https://github.com/kristopolous/_inject
On peut se promener dans presque n’importe quel contexte de fonction à un moment arbitraire et voir ce qui s’est passé
Le principe consiste à détourner les compteurs de références pour empêcher la destruction du contexte
À l’époque où je faisais beaucoup de JS côté client, c’était vraiment utile
La killer app bien faite pour ça serait une version qui ouvre un REPL dans n’importe quel contexte
Même dans son état actuel, il faut pas mal d’expérience pour bien l’utiliser
https://firefox-source-docs.mozilla.org/devtools-user/debugg...
Édit : je viens de me rendre compte que c’est littéralement le premier sujet abordé dans l’article lié
[[Scopes]]Dans Firefox, il ne semble pas y avoir de méthode
Par souci d’exhaustivité, on peut recommander Werkzeug
Je l’utilise pour du développement backend Django et c’est extrêmement utile
Il permet d’avoir un shell « PDB » dans le navigateur, à tout moment et à n’importe quel endroit où une exception se produit
1/0à l’endroit où vous voulez faire apparaître Werkzeug dans le navigateurC’est très simple et intuitif
django-extension/runserver_plus?Une astuce que j’utilise toujours consiste à chercher dans les scripts chargés à partir d’une chaîne de l’UI pour déboguer
Allez dans le panneau Network, lancez l’enregistrement des requêtes réseau, ouvrez la barre latérale gauche, puis cherchez le code ou la chaîne d’interface que vous voulez trouver
En général, on la retrouve dans un étrange fichier de chunk JS bundlé ; en cliquant sur le résultat, la requête réseau de ce fichier s’ouvre
Faites un clic droit n’importe où dans ce fichier et choisissez une option du genre « Open in Sources » pour passer au débogueur
Il ne reste plus qu’à placer une instruction
debugger, et les source maps seront probablement chargées au passageEn tant que programmeur Python/Elixir depuis longtemps, j’ai beaucoup utilisé
pdb.set_trace()etIEx.pry()J’ai récemment hérité d’un backend NodeJS très sale, et je m’arrache les cheveux qu’il me reste faute d’outils de débogage corrects
Je suis revenu au débogage à coups de
console.log, mais j’ai l’impression d’être un homme des cavernesJ’ai du mal à croire qu’un écosystème aussi populaire n’ait pas de vrai REPL de débogage ; quelqu’un peut m’indiquer vers quoi me tourner ?
Utilisez
node --inspect-brk, puis connectez-vous avec VS Code ou Chrome DevToolsLa partie délicate est de savoir si des outils de build, comme une transpilation TypeScript, se trouvent avant la commande
nodejsSi vous lancez simplement
node, c’est assez simpleLe REPL de débogage, c’est la console JavaScript elle-même
En l’ajoutant comme option de débogage, vous pouvez poser des points d’arrêt et avancer pas à pas dans les fonctions
Comme la plupart des choses sont encapsulées dans des objets, JavaScript se débogue plutôt bien
--inspectou--inspect-brkDans VSCode, vous pouvez aussi utiliser la commande « open JavaScript debug terminal »
Elle ouvre un terminal où n’importe quelle commande
nodedémarre automatiquement avec le débogueur attachéhttps://pypi.org/project/snoop/
C’est un décorateur pour du traçage approfondi
Je n’ai pas vu ce type de fonctionnalité dans l’écosystème Node JS
Dans la section « Debugging Property Reads », je me demande comment on utilise un point d’arrêt conditionnel pour transformer
{configOption: true}en{get configOption() { debugger; return true; }}Il suffit de remplacer la valeur par un getter dans la console ; si vous avez les droits d’écriture, vous pouvez aussi la modifier directement dans le code source
C’est quelque chose qu’il faudrait vraiment apprendre, mais je me demande s’il existe des livres dédiés ou des ressources d’apprentissage
Ou bien n’y a-t-il pas d’autre choix que de creuser sans fin le développement web et le frontend ?
Quand on est en plein travail, cliquer sur un lien pour lire des notes de version peut ne pas être très tentant, mais parcourir celles d’une nouvelle version pendant seulement 5 minutes en vaut largement la peine
Il y a aussi des vidéos de quelques minutes, faciles à assimiler, qui permettent d’avoir un aperçu rapide
L’objectif est de présenter les nouvelles fonctionnalités, mais d’après mon expérience, cela aide aussi souvent à comprendre les limites actuelles des outils