Skip to content

Commit fe9a88f

Browse files
authored
Add mochitest infrastructure (firefox-devtools#630)
1 parent 90ea89d commit fe9a88f

19 files changed

+531
-4
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ public/js/test/cypress/**
55
public/js/test/integration/**
66
public/js/test/unit-sources/**
77
public/js/lib/**
8+
public/js/test/mochitest/head.js

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ npm-debug.log
66
.idea/
77
.vscode/
88
cypress/screenshots
9+
firefox

bin/download-firefox-artifact

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
ROOT=`dirname $0`
4+
FIREFOX_PATH="$ROOT/../firefox"
5+
6+
if [ -d "$FIREFOX_PATH" ]; then
7+
# If we already have Firefox locally, just update it
8+
cd "$FIREFOX_PATH";
9+
hg pull
10+
else
11+
hg clone https://hg.mozilla.org/mozilla-central/ "$FIREFOX_PATH"
12+
cd "$FIREFOX_PATH"
13+
14+
# Make an artifcat build so it builds much faster
15+
echo "
16+
ac_add_options --enable-artifact-builds
17+
mk_add_options MOZ_OBJDIR=./objdir-frontend
18+
" > .mozconfig
19+
fi

bin/make-firefox-bundle

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ if [ -z "$1" ]; then
55
exit 1
66
fi
77

8+
if [ "$2" == "--symlink-mochitests" ]; then
9+
SYMLINK_MOCHITESTS=1
10+
fi
11+
12+
ROOT=`dirname $0`
813
DEBUGGER_PATH="$1/devtools/client/debugger/new"
914
REV=`git log -1 --pretty=oneline`
1015

@@ -13,10 +18,27 @@ if [ ! -d "$DEBUGGER_PATH" ]; then
1318
exit 2
1419
fi
1520

21+
(
22+
cd "$DEBUGGER_PATH";
23+
if ! git log --oneline . | head -n 1 |
24+
grep "UPDATE_BUNDLE" > /dev/null; then
25+
echo "\033[31mWARNING\033[0m: local changes detected on mozilla-central";
26+
fi
27+
);
1628

1729
TARGET=firefox-panel webpack
1830
echo "// Generated from: $REV\n" | cat - public/build/bundle.js > "$DEBUGGER_PATH/bundle.js"
1931
cp public/build/pretty-print-worker.js "$DEBUGGER_PATH"
2032
cp public/build/source-map-worker.js "$DEBUGGER_PATH"
2133
cp public/build/styles.css "$DEBUGGER_PATH"
2234
cp public/images/* "$DEBUGGER_PATH/images"
35+
36+
rm -r "$DEBUGGER_PATH/test/mochitest"
37+
if [ -n "$SYMLINK_MOCHITESTS" ]; then
38+
ln -s `pwd -P`"/$ROOT/../public/js/test/mochitest/" "$DEBUGGER_PATH/test/mochitest"
39+
else
40+
rsync -avz public/js/test/mochitest/ "$DEBUGGER_PATH/test/mochitest"
41+
fi
42+
43+
# Make sure a rebuild uses the new tests
44+
touch "$DEBUGGER_PATH/test/mochitest/browser.ini"

bin/prepare-mochitests-dev

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
3+
ROOT=`dirname $0`
4+
FIREFOX_PATH="$ROOT/../firefox"
5+
6+
# If we don't have a local copy of firefox, download it
7+
if [ ! -d firefox ]; then
8+
"$ROOT/download-firefox-artifact"
9+
fi
10+
11+
# Update the debugger files, build firefox, and run all the mochitests
12+
"$ROOT/make-firefox-bundle" "$FIREFOX_PATH" --symlink-mochitests
13+
cd "$FIREFOX_PATH"
14+
./mach build

bin/run-mochitests-docker

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
3+
TARGET=firefox-panel ./node_modules/.bin/webpack
4+
5+
docker run -it \
6+
-v `pwd`/public/build/bundle.js:/firefox/devtools/client/debugger/new/bundle.js \
7+
-v `pwd`/public/build/pretty-print-worker.js:/firefox/devtools/client/debugger/new/pretty-print-worker.js \
8+
-v `pwd`/public/build/source-map-worker.js:/firefox/devtools/client/debugger/new/source-map-worker.js \
9+
-v `pwd`/public/build/styles.css:/firefox/devtools/client/debugger/new/styles.css \
10+
-v `pwd`/public/images:/firefox/devtools/client/debugger/new/images \
11+
-v `pwd`/public/js/test/mochitest:/firefox/devtools/client/debugger/new/test/mochitest \
12+
-v "/tmp/.X11-unix:/tmp/.X11-unix:rw" \
13+
-e "DISPLAY=unix$DISPLAY" \
14+
--ipc host \
15+
jlongster/mochitest-runner \
16+
/bin/bash -c "export SHELL=/bin/bash; cd firefox && touch devtools/client/debugger/new/test/mochitest/browser.ini && ./mach mochitest --subsuite devtools devtools/client/debugger/new/test/mochitest/"

circle.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
machine:
22
node:
33
version: 6.3
4+
services:
5+
- docker
46

57
checkout:
68
post:
@@ -9,6 +11,7 @@ test:
911
override:
1012
- mkdir -p $CIRCLE_TEST_REPORTS/mocha
1113
- node public/js/test/node-unit-tests.js --ci
14+
- ./bin/run-mochitests-docker
1215
- npm run firefox-unit-test
1316
- node_modules/.bin/cypress ci b07646ab-ddfa-442b-b63f-aebf00452de8
1417
- node_modules/.bin/cypress ci b07646ab-ddfa-442b-b63f-aebf00452de8
@@ -32,3 +35,4 @@ dependencies:
3235
- ./bin/install-chrome
3336
- ./bin/install-firefox
3437
- node_modules/.bin/cypress install
38+
- docker pull jlongster/mochitest-runner

docs/mochitests.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
We use [mochitests](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Mochitest) 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). While we are developing the debugger locally in a tab, it's important that we test it as a devtools panel.
2+
3+
Mochitests require a local checkout of the Firefox source code. This is because they are used to test a lot of Firefox, and you would usually run them inside Firefox. We are developing the debugger outside of Firefox, but still want to test it as a devtools panel, so we've figured out a way to use them. It may not be elegant, but it allows us to ensure a high quality Firefox debugger.
4+
5+
Mochitests live in `public/js/test/mochitest`.
6+
7+
## Getting Started
8+
9+
If you haven't set up the mochitest environment yet, just run this:
10+
11+
```
12+
./bin/prepare-mochitests-dev
13+
```
14+
15+
This will download a local copy of Firefox (or update it if it already exists), set up an [artifact build](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Artifact_builds) (just think of a super fast Firefox build) and set up the environment. If you are doing this the first time, it may take a while (10-15 minutes). Most of that is downloading Firefox, later updates will be much quicker.
16+
17+
Now, you can run the mochitests like this:
18+
19+
```
20+
cd firefox
21+
./mach mochitest --subsuite devtools devtools/client/debugger/new/test/mochitest/
22+
```
23+
24+
This works because we've symlinked the local mochitests into where the debugger lives in Firefox. Any changes to the tests in `public/js/test/mochitest` will be reflected and you can re-run the tests.
25+
26+
Visit the [mochitest](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Mochitest) MDN page to learn more about mochitests and more advanced arguments. A few tips:
27+
28+
* Passing `--jsdebugger` will open a JavaScript debugger and allow you to debug the tests (sometimes can be fickle)
29+
* Add `{ "logging": { "actions": true } }` to your local config file to see verbose logs of all the redux actions
30+
31+
## Watching for Changes
32+
33+
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:
34+
35+
```
36+
./bin/make-firefox-bundle firefox
37+
```
38+
39+
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).
40+
41+
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:
42+
43+
```
44+
npm run mochitests-watch
45+
```
46+
47+
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.
48+
49+
## Adding New Tests
50+
51+
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`.
52+
53+
## API
54+
55+
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:
56+
57+
```js
58+
const TAB_URL = EXAMPLE_URL + "doc_simple.html";
59+
60+
add_task(function* () {
61+
const dbg = yield initDebugger(TAB_URL, "code_simple.js");
62+
// do some stuff
63+
ok(state.foo, "Foo is OK");
64+
});
65+
```
66+
67+
Any of the below APIs that takes a `url` will match it as a substring, meaning that `foo.js` will match a source with the URL `http://example.com/foo.js`.
68+
69+
* `findSource(dbg, url)` - Returns a source that matches the URL
70+
* `selectSource(dbg, url)` - Selects the source
71+
* `stepOver(dbg)` - Steps over
72+
* `stepIn(dbg)` - Steps in
73+
* `stepOut(dbg)` - Steps out
74+
* `resume(dbg)` - Resumes
75+
* `addBreakpoint(dbg, sourceId, line, col?)` - Add a breakpoint to a source at line/col
76+
* `waitForPaused(dbg)` - Waits for the debugger to be fully paused
77+
* `waitForState(dbg, predicate)` - Waits for `predicate(state)` to be true. `state` is the redux app state
78+
* `waitForThreadEvents(dbg, eventName)` - Waits for specific thread events
79+
* `waitForDispatch(dbg, type)` - Wait for a specific action type to be dispatch. If an async action, will wait for it to be done.
80+
81+
## Writing Tests
82+
83+
Here are a few tips for writing mochitests:
84+
85+
* Only write mochitests for testing the interaction of multiple components on the page and to make sure that the protocol is working.
86+
* By default, use the above builtin functions to drive the interaction and only dig into the DOM when you specifically want to test a component. For example, most tests should use the `addBreakpoint` command to add breakpoints, but certain tests may specifically want to test the editor gutter and left-click on that DOM element to add a breakpoint.
87+
* The `dbg` object has the following properties:
88+
** `actions` - Redux actions (already bound to the store)
89+
** `selectors` - State selectors
90+
** `getState` - Function to get current state
91+
** `store` - Redux store
92+
** `toolbox` - Devtools toolbox
93+
** `win` - The current debugger window
94+
* You can assert DOM structure like `is(dbg.win.querySelectorAll("#foo").length, 1, "...")`
95+
* If you need to access the content page, use `ContentTask.spawn`:
96+
97+
```js
98+
ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
99+
content.wrappedJSObject.foo();
100+
});
101+
```
102+
103+
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.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"firefox": "node bin/firefox-driver --start",
2626
"cypress": "./node_modules/.bin/cypress run --port 2021",
2727
"cypress-intermittents": "for i in {1..100}; do time npm run cypress; done | tee cypress-run.log",
28+
"mochitests-watch": "MOCHITESTS=true TARGET=firefox-panel webpack --watch",
2829
"prepublish": "webpack"
2930
},
3031
"dependencies": {

public/js/main.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ if (connTarget) {
9292
renderRoot(App);
9393
});
9494
} else if (isFirefoxPanel()) {
95+
const selectors = require("./selectors");
9596
// The toolbox already provides the tab to debug.
9697
function bootstrap({ threadClient, tabTarget }) {
9798
firefox.setThreadClient(threadClient);
@@ -100,7 +101,19 @@ if (connTarget) {
100101
renderRoot(App);
101102
}
102103

103-
module.exports = { bootstrap, store, actions, selectors };
104+
module.exports = {
105+
bootstrap,
106+
store: store,
107+
actions: actions,
108+
selectors: selectors,
109+
110+
// Remove these once we update the API on m-c
111+
setThreadClient: firefox.setThreadClient,
112+
setTabTarget: firefox.setTabTarget,
113+
initPage: firefox.initPage,
114+
renderApp: () => renderRoot(App),
115+
getActions: () => actions
116+
};
104117
} else {
105118
renderRoot(Tabs);
106119
connectClients().then(tabs => {

0 commit comments

Comments
 (0)