- Reproduce the bug in local.
- Fix the bug.
- Test the bug fix in local.
- Deploy to environment for verification.
The below are the few heuristics that I follow to reverse-engineer the root cause for an UI issue. I am assuming that you are thrown into the deep end to fix an issue with neither having access to code base nor any knowledge/memory about the functionality. If not then still the heuristic is helpful.
Ok, here it goes using the above sample project as a bug:
Follow the steps to recreate. (In the above project, the page must display a circle but only a semi-circle is visible.)
Once you had recreated the bug, check if the issue is browser specific. If not browser specific, you can use any browser to debug further. Else, use the browser specifically causing the bug. (In the above project, it is happening in more than one browser so I choose chrome to debug it but it can be any browser. At work, I would have chosen IE.)
This is the important part: Isolate the element that is not behaving as expected a.k.a. the “bug”. (In the above project, the svg element is not behaving as proper.)
Open developer tool (in IE press F12. For other browsers, check their documentation on how to open its developer toolbar).
Now we need to find out the code that is the precursor of the “bug”. That is, the code which executed correctly just before the “bug” happened. (In the above project, it is the onload handler)
After adding the breakpoint via dev tool at line #2, I ran the page again and this time the page stopped at the breakpoint location line #2. From here I need to slowly step through the code to understand where it is breaking. (In the above project, I guess it is breaking at line #9 where the d3 js is creating the circle)
If you find the precursor but still are not able to add breakpoint because the js is dynamically generated, then find the js file that is injecting the dynamic script. This can be found via network tab or the html source that has the list of js files that it loads. Then put a breakpoint just before and after this dynamic creation of script. After the script is created, it will appear in the sources window and you can now go and add additional breakpoints that you need. Sometimes (esp. I noticed this in IE) even if you add a breakpoint in the generated script, the browser may not stop at that line. Check the “Fix” section on how to handle this thornier case.
Enable cache (temporarily while you are testing) so that when you reload the page again, the dynamic scripts are retrieved from cache with your added breakpoint intact.
There are only two techniques you need to test your fix directly in the developer tools/console.
A. While the browser is waiting at the breakpoint line #2, open console in dev tools and paste the below code and click run or press enter. This code effectively redefines the drawCircle() function to the bug-fix version. Now go and run the debugger to completion.
In my case, this fix worked and I am done. For complex cases, you might be doing multiple iterations on the fix before you finally conclude that it works in all cases.
How cool is that!
B. Now, for the corner case when your breakpoint may not be working esp. for dynamically injected scripts, when the browser stops at the breakpoint just after dynamic script injection call, you can redefine the function inside the dynamic script with the same function definition except at the first line inside the definition add the “debugger;” statement.
Then you need to manually invoke the “bootstrap” function of the dynamic script via console. (In the above example, you need to call “drawCircle()” via console)
This will stop the browser at this line and then you can add more breakpoints or continue to step-into/step-over via the debug bar.
Caveat1: Remove “debugger;” statement before you check-in the fix. It should only be used as a debugging aid.
Caveat2: Note that this dynamic injection of a function redefinition will not work if the browser already started executing within the function that you are redefining. So always put the first breakpoint just one call stack below the function that you are going to redefine.
Note: In order not to repeat step B every time the browser dynamically injects the script while refreshing the page, you can enable caching via dev tool.
This has turned into a longer article than I anticipated but I hope it helps someone to effectively debug their frontend apps. The method captured above are browser agnostic and has personally helped me save many person hours in debugging and effectively testing the fixes.