Skip to content

Latest commit

 

History

History
183 lines (117 loc) · 8.9 KB

File metadata and controls

183 lines (117 loc) · 8.9 KB

Mochitests

We use mochitests to do integration testing. Mochitests are part of Firefox and allow us to test the debugger literally as you would use it (as a devtools panel).

Getting Started

Requirements

  • mercurial ( brew install mercurial )
  • autoconf213 ( brew install autoconf@2.13 && brew unlink autoconf )

Setup Firefox

  • Inside the main debugger.html folder run:
./bin/prepare-mochitests-dev

This command will either clone mozilla-central (the firefox repo) or update it. It also sets up a symlink for the tests so that changes in src/test/mochitest are reflected in the new firefox directory.

Running the tests

  • yarn copy-assets-watch copies new bundles into the firefox directory
  • yarn mochi runs the tests in a second process

Mochi

mochi passes its params along to mochitest, so you can include --jsdebugger and test globs

  • yarn mochi -- --jsdebugger opens a browser toolbox
  • yarn mochi browser_dbg-editor-highlight runs just one test

Writing Tests

Here are a few tips for writing mochitests:

  • There are lots of great helper methods in head
  • Try to write async user actions that involve a user action like clicking a button or typing a key press followed by a redux action to listen for. For example, the user step in action involvesthe user clicking the step in button followed by the "stepIn" action firing.
  • The dbg object has several helpful properties (actions, selectors, getState, store, toolbox, win)

Testing the DOM

You can find common elements in the debugger with the findElement function, which use shared selectors. You can also find any element with the findElementWithSelector function.

Evaluating in the debuggee

If you want to evaluate a function in the debuggee context you can use the invokeInTab function. Under the hood it is using ContentTask.spawn.

ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
  content.wrappedJSObject.foo();
});

The above calls the function foo that exists in the page itself. You can also access the DOM this way: content.document.querySelector, if you want to click a button or do other things. You can even you use assertions inside this callback to check DOM state.

Debugging Tips

The first step for debugging an integration test is establishing a clear sequence of events: where did the test break and what were the events that preceded it.

The mochitest logs provide some context:

  1. The actions that fired
  2. Assertion Passes/Failures
  3. Events that the test waited for: dispatches, state changes

NOTE: it might be nice to run the tests in headless mode: yarn mochih browser_dbg-editor-highlight

In depth walk through

The next step is to add additional logging in the test and debugger code with info calls. We recommend prefixing your logs and formatting them so they are easy to scan e.g.:

  • info(">> Add breakpoint ${line} -> ${condition}\n")
  • info(">> Current breakpoints ${breakpoints.map(bp => bp.location.line).join(", ")}\n")
  • info(">> Symbols for source ${source.url} ${JSON.stringify(symbols)}\n")

At some point, it can be nice to pause the test and debug it. We are working on a debugger after all :) Mochitest, makes it easy to pause the test at debugger statements with the --jsdebugger flag. You can run the test like this yarn mochid browser_dbg-editor-highlight.

Debugging Intermittents

Intermittents are when a test succeeds most the time (95%) of the time, but not all the time. There are several easy traps that result in intermittents:

  • browser inconsistencies sometimes the server is not as consistent as you would like. For instance, reloading can sometimes cause sources to load out of order. Also stepping too quickly can cause the debugger to enter a bad state. A memorable example of this type of inconsistency came when debugging stepping behavior. It turns out that 1% of the time the browser toolbox will step into an unexpected location. The solution is too loosen our expections :)
  • missed actions sometimes action "B" can fire before action "A" is done. This is a race condition that can be hard to track down. When you suspect this might happen, it is a good practice to start listening for "B" before you fire action "A". Here's an example where this happened with reloading.
  • state changes One common way tests start failing occurs when the redux actions introduces a new asynchronous operation. A good way to safe guard your tests is to wait on state to have certain values. An example, of a test that we recently fixed was pretty printing. The test initially waited for the "select source" action to fire, which was occasionally racey. Switching the test to wait for the formatted source to exist simplified the test tremendously.

Appendix

Mochitest CLI

The mochitest cli has a lot of advanced options that are worth learning about. Here is a quick intro in how it can be used

cd firefox
./mach mochitest devtools/client/debugger/new # runs all the debugger tests
./mach mochitest browser_dbg-editor-highlight # runs one test
./mach mochitest --jsdebugger browser_dbg-editor-highlight # runs one test with the browser toolbox open

Visit the mochitest MDN page to learn more about mochitests and more advanced arguments. A few tips:

For Windows Developers

The detailed instructions for setting up your environment to build Firefox for Windows can be found here. You need to install the latest MozBuild package. You can open a unix-flavor shell by starting:

C:\mozilla-build\start-shell.bat

In the shell, navigate to the debugger.html project folder, and follow the Getting Started instructions as mentioned.

Watching for Changes

The mochitest are running against the compiled debugger bundle inside the Firefox checkout. This means that you need to update the bundle whenever you make code changes. prepare-mochitests-dev does this for you initially, but you can manually update it with:

yarn copy-assets

That will build the debugger and copy over all the relevant files into firefox, including mochitests. If you want it to only symlink the mochitests directory, pass --symlink-mochitests (which is what prepare-mochitests-dev does).

It's annoying to have to manually update the bundle every single time though. If you want to automatically update the bundle in Firefox whenever you make a change, run this:

yarn copy-assets-watch

Now you can make code changes the the bundle will be automatically built for you inside firefox, and you can simply run mochitests and edit code as much as you like.

Adding New Tests

If you add new tests, make sure to list them in the browser.ini file. You will see the other tests there. Add a new entry with the same format as the others. You can also add new JS or HTML files by listing in under support-files.

API

In addition to the standard mochtest API, we provide the following functions to help write tests. All of these expect a dbg context which is returned from initDebugger which should be called at the beginning of the test. An example skeleton test looks like this:

add_task(function* () {
  const dbg = yield initDebugger("doc_simple.html", "code_simple.js");
  // do some stuff
  ok(state.foo, "Foo is OK");
});

The Debugger Mochitest API Documentation can be found here.