Bug 1346902 - Update browser toolbox integration test for new debugger. r=jlast
MozReview-Commit-ID: 8k8bIqSBKGf
--- a/devtools/client/debugger/new/test/mochitest/head.js
+++ b/devtools/client/debugger/new/test/mochitest/head.js
@@ -170,17 +170,18 @@ function waitForSources(dbg, ...sources)
}
info("Waiting on sources: " + sources.join(", "));
const { selectors: { getSources }, store } = dbg;
return Promise.all(
sources.map(url => {
function sourceExists(state) {
return getSources(state).some(s => {
- return s.get("url").includes(url);
+ let u = s.get("url");
+ return u && u.includes(url);
});
}
if (!sourceExists(store.getState())) {
return waitForState(dbg, sourceExists);
}
})
);
@@ -207,18 +208,19 @@ function assertPausedLocation(dbg, sourc
is(getSelectedSource(getState()).get("id"), source.id);
// Check the pause location
const location = getPause(getState()).getIn(["frame", "location"]);
is(location.get("sourceId"), source.id);
is(location.get("line"), line);
// Check the debug line
+ let cm = dbg.win.document.querySelector(".CodeMirror").CodeMirror;
ok(
- dbg.win.cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
+ cm.lineInfo(line - 1).wrapClass.includes("debug-line"),
"Line is highlighted as paused"
);
}
/**
* Assert that the debugger is highlighting the correct location.
*
* @memberof mochitest/asserts
@@ -351,17 +353,20 @@ function findSource(dbg, url) {
if (typeof url !== "string") {
// Support passing in a source object itelf all APIs that use this
// function support both styles
const source = url;
return source;
}
const sources = dbg.selectors.getSources(dbg.getState());
- const source = sources.find(s => s.get("url").includes(url));
+ const source = sources.find(s => {
+ let u = s.get("url");
+ return u && u.includes(url);
+ });
if (!source) {
throw new Error("Unable to find source: " + url);
}
return source.toJS();
}
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -18,16 +18,17 @@ support-files =
shared-head.js
shared-redux-head.js
helper_disable_cache.js
doc_theme.css
doc_viewsource.html
browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
browser_toolbox_options_enable_serviceworkers_testing.html
serviceworker.js
+ test_browser_toolbox_debugger.js
[browser_browser_toolbox.js]
[browser_browser_toolbox_debugger.js]
[browser_devtools_api.js]
[browser_devtools_api_destroy.js]
[browser_dynamic_tool_enabling.js]
[browser_ignore_toolbox_network_requests.js]
[browser_keybindings_01.js]
--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -1,16 +1,25 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
+// This test asserts that the new debugger works from the browser toolbox process
+// Its pass a big piece of Javascript string to the browser toolbox process via
+// MOZ_TOOLBOX_TEST_SCRIPT env variable. It does that as test resources fetched from
+// chrome://mochitests/ package isn't available from browser toolbox process.
+
// On debug test runner, it takes about 50s to run the test.
requestLongerTimeout(4);
const { setInterval, clearInterval } = require("sdk/timers");
+const { fetch } = require("devtools/shared/DevToolsUtils");
+
+const debuggerHeadURL = CHROME_URL_ROOT + "../../debugger/new/test/mochitest/head.js";
+const testScriptURL = CHROME_URL_ROOT + "test_browser_toolbox_debugger.js";
add_task(function* runTest() {
yield new Promise(done => {
let options = {"set": [
["devtools.debugger.prompt-connection", false],
["devtools.debugger.remote-enabled", true],
["devtools.chrome.enabled", true],
// Test-only pref to allow passing `testScript` argument to the browser
@@ -36,83 +45,74 @@ add_task(function* runTest() {
// Execute the function every second in order to trigger the breakpoint
let interval = setInterval(s.plop, 1000);
// Be careful, this JS function is going to be executed in the browser toolbox,
// which lives in another process. So do not try to use any scope variable!
let env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
- let testScript = function () {
- const { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
- dump("Opening the browser toolbox and debugger panel\n");
- let window, document;
- let testUrl = "http://mozilla.org/browser-toolbox-test.js";
- Task.spawn(function* () {
- dump("Waiting for debugger load\n");
- let panel = yield toolbox.selectTool("jsdebugger");
- let window = panel.panelWin;
- let document = window.document;
-
- yield window.once(window.EVENTS.SOURCE_SHOWN);
-
- dump("Loaded, selecting the test script to debug\n");
- let item = document.querySelector(`.dbg-source-item[tooltiptext="${testUrl}"]`);
- let onSourceShown = window.once(window.EVENTS.SOURCE_SHOWN);
- item.click();
- yield onSourceShown;
-
- dump("Selected, setting a breakpoint\n");
- let { Sources, editor } = window.DebuggerView;
- let onBreak = window.once(window.EVENTS.FETCHED_SCOPES);
- editor.emit("gutterClick", 1);
- yield onBreak;
-
- dump("Paused, asserting breakpoint position\n");
- let url = Sources.selectedItem.attachment.source.url;
- if (url != testUrl) {
- throw new Error("Breaking on unexpected script: " + url);
+ // First inject a very minimal head, with simplest assertion methods
+ // and very common globals
+ let testHead = (function () {
+ const info = msg => dump(msg + "\n");
+ const is = (a, b, description) => {
+ let msg = "'" + JSON.stringify(a) + "' is equal to '" + JSON.stringify(b) + "'";
+ if (description) {
+ msg += " - " + description;
}
- let cursor = editor.getCursor();
- if (cursor.line != 1) {
- throw new Error("Breaking on unexpected line: " + cursor.line);
+ if (a !== b) {
+ msg = "FAILURE: " + msg;
+ dump(msg + "\n");
+ throw new Error(msg);
+ } else {
+ msg = "SUCCESS: " + msg;
+ dump(msg + "\n");
+ }
+ };
+ const ok = (a, description) => {
+ let msg = "'" + JSON.stringify(a) + "' is true";
+ if (description) {
+ msg += " - " + description;
}
-
- dump("Now, stepping over\n");
- let stepOver = window.document.querySelector("#step-over");
- let onFetchedScopes = window.once(window.EVENTS.FETCHED_SCOPES);
- stepOver.click();
- yield onFetchedScopes;
-
- dump("Stepped, asserting step position\n");
- url = Sources.selectedItem.attachment.source.url;
- if (url != testUrl) {
- throw new Error("Stepping on unexpected script: " + url);
+ if (!a) {
+ msg = "FAILURE: " + msg;
+ dump(msg + "\n");
+ throw new Error(msg);
+ } else {
+ msg = "SUCCESS: " + msg;
+ dump(msg + "\n");
}
- cursor = editor.getCursor();
- if (cursor.line != 2) {
- throw new Error("Stepping on unexpected line: " + cursor.line);
- }
+ };
+ const registerCleanupFunction = () => {};
- dump("Resume script execution\n");
- let resume = window.document.querySelector("#resume");
- let onResume = toolbox.target.once("thread-resumed");
- resume.click();
- yield onResume;
+ const Cu = Components.utils;
+ const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+ }).toSource().replace(/^\(function \(\) \{|\}\)$/g, "");
+ // Stringify testHead's function and remove `(function {` prefix and `})` suffix
+ // to ensure inner symbols gets exposed to next pieces of code
- dump("Close the browser toolbox\n");
- toolbox.destroy();
+ // Then inject new debugger head file
+ let { content } = yield fetch(debuggerHeadURL);
+ let debuggerHead = content;
+ // We remove its import of shared-head, which isn't available in browser toolbox process
+ // And isn't needed thanks to testHead's symbols
+ debuggerHead = debuggerHead.replace(/Services.scriptloader.loadSubScript[^\)]*\);/, "");
- }).catch(error => {
- dump("Error while running code in the browser toolbox process:\n");
- dump(error + "\n");
- dump("stack:\n" + error.stack + "\n");
- });
- };
- env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
+ // Finally, fetch the debugger test script that is going to be execute in the browser
+ // toolbox process
+ let testScript = (yield fetch(testScriptURL)).content;
+ let source =
+ "try {" + testHead + debuggerHead + testScript + "} catch (e) {" +
+ " dump('Exception: '+ e + ' at ' + e.fileName + ':' + " +
+ " e.lineNumber + '\\nStack: ' + e.stack + '\\n');" +
+ "}";
+ env.set("MOZ_TOOLBOX_TEST_SCRIPT", source);
registerCleanupFunction(() => {
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
});
let { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
// Use two promises, one for each BrowserToolboxProcess.init callback
// arguments, to ensure that we wait for toolbox run and close events.
let closePromise;
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/test_browser_toolbox_debugger.js
@@ -0,0 +1,48 @@
+/* global toolbox */
+
+info(`START: ${new Error().lineNumber}`);
+
+let testUrl = "http://mozilla.org/browser-toolbox-test.js";
+
+Task.spawn(function* () {
+ Services.prefs.clearUserPref("devtools.debugger.tabs")
+ Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
+
+ info("Waiting for debugger load");
+ yield toolbox.selectTool("jsdebugger");
+ let dbg = createDebuggerContext(toolbox);
+ let window = dbg.win;
+ let document = window.document;
+
+ yield waitForSources(dbg, testUrl);
+// yield waitForSourceCount(dbg, 6);
+
+ info("Loaded, selecting the test script to debug");
+ // First expand the domain
+ let domain = [...document.querySelectorAll(".tree-node")].find(node => {
+ return node.textContent == "mozilla.org";
+ });
+ let arrow = domain.querySelector(".arrow");
+ arrow.click();
+ let script = [...document.querySelectorAll(".tree-node")].find(node => {
+ return node.textContent.includes("browser-toolbox-test.js");
+ });
+ script = script.querySelector(".node");
+ script.click();
+
+ let onPaused = waitForPaused(dbg);
+ yield addBreakpoint(dbg, "browser-toolbox-test.js", 2);
+
+ yield onPaused;
+
+ assertPausedLocation(dbg, "browser-toolbox-test.js", 2);
+
+ yield stepIn(dbg);
+
+ assertPausedLocation(dbg, "browser-toolbox-test.js", 3);
+
+ yield resume(dbg);
+
+ info("Close the browser toolbox");
+ toolbox.destroy();
+});
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -152,26 +152,26 @@ function bindToolboxHandlers() {
// Once the debugger panel opens listen for thread pause / resume.
gToolbox.getPanelWhenReady("jsdebugger").then(panel => {
setupThreadListeners(panel);
});
#endif
}
function setupThreadListeners(panel) {
- updateBadgeText(panel._controller.activeThread.state == "paused");
+ updateBadgeText(panel._selectors().getPause(panel._getState()));
let onPaused = updateBadgeText.bind(null, true);
let onResumed = updateBadgeText.bind(null, false);
- panel.target.on("thread-paused", onPaused);
- panel.target.on("thread-resumed", onResumed);
+ gToolbox.target.on("thread-paused", onPaused);
+ gToolbox.target.on("thread-resumed", onResumed);
panel.once("destroyed", () => {
- panel.off("thread-paused", onPaused);
- panel.off("thread-resumed", onResumed);
+ gToolbox.target.off("thread-paused", onPaused);
+ gToolbox.target.off("thread-resumed", onResumed);
});
}
function updateBadgeText(paused) {
let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport);
dockSupport.badgeText = paused ? "▐▐ " : " ▶";
}