Bug 1761368 - [devtools] Cover nameless eval and content script sources in the SourceTree test. r=bomsy

I'm also improving waitForSourcesInSourceTree to timeout faster and have an helpful error message,
clearly highlight the unexpected or missing sources.

In source tree test, it looks like adding named eval slow things down for the quick open.
So I tuned the related assertion to avoid intermittents.
We were having intermediate updates showing up named eval instead of the source with query string.

Differential Revision: https://phabricator.services.mozilla.com/D142340
This commit is contained in:
Alexandre Poirot 2022-03-31 11:20:54 +00:00
parent 00bc34f43e
commit ca85b5c654
10 changed files with 240 additions and 117 deletions

View File

@ -19,12 +19,12 @@ add_task(async function testBreakableLinesOverReloads() {
);
info("Assert breakable lines of the first html page load");
await assertBreakableLines(dbg, "index.html", 49, [
[28, 29],
[32],
await assertBreakableLines(dbg, "index.html", 53, [
[16, 17],
[21],
[23],
[28],
[34],
[39],
[45],
]);
info("Assert breakable lines of the first original source file, original.js");

View File

@ -30,13 +30,13 @@ add_task(async function testBreakableLinesOverReloads() {
);
info("Assert breakable lines of the first html page load");
await assertBreakablePositions(dbg, "index.html", 49, [
{ line: 28, columns: [6, 14] },
{ line: 29, columns: [] },
{ line: 32, columns: [6, 14] },
await assertBreakablePositions(dbg, "index.html", 53, [
{ line: 16, columns: [6, 14] },
{ line: 17, columns: [] },
{ line: 21, columns: [6, 14] },
{ line: 23, columns: [] },
{ line: 28, columns: [] },
{ line: 34, columns: [] },
{ line: 39, columns: [] },
{ line: 45, columns: [] },
]);
info("Assert breakable lines of the first original source file, original.js");

View File

@ -8,23 +8,23 @@
add_task(async function() {
await pushPref("devtools.chrome.enabled", true);
const extension = await installAndStartExtension();
const extension = await installAndStartContentScriptExtension();
let dbg = await initDebugger("doc-content-script-sources.html");
await clickElement(dbg, "sourceDirectoryLabel", 2);
await selectContentScriptSources(dbg);
let dbg = await initDebugger(
"doc-content-script-sources.html",
"content_script.js"
);
await selectSource(dbg, "content_script.js");
await closeTab(dbg, "content_script.js");
// Destroy the toolbox and repeat the test in a new toolbox
// and ensures that the content script is still listed.
await dbg.toolbox.destroy();
const toolbox = await openToolboxForTab(gBrowser.selectedTab, "jsdebugger");
dbg = createDebuggerContext(toolbox);
await clickElement(dbg, "sourceDirectoryLabel", 2);
await selectContentScriptSources(dbg);
await waitForSources(dbg, "content_script.js");
await selectSource(dbg, "content_script.js");
await addBreakpoint(dbg, "content_script.js", 2);
@ -50,45 +50,3 @@ add_task(async function() {
await extension.unload();
});
async function selectContentScriptSources(dbg) {
await waitForSources(dbg, "content_script.js");
// Select a source.
await selectSource(dbg, "content_script.js");
ok(
findElementWithSelector(dbg, ".sources-list .focused"),
"Source is focused"
);
}
async function installAndStartExtension() {
function contentScript() {
console.log("content script loads");
// This listener prevents the source from being garbage collected
// and be missing from the scripts returned by `dbg.findScripts()`
// in `ThreadActor._discoverSources`.
window.onload = () => {};
}
const extension = ExtensionTestUtils.loadExtension({
manifest: {
content_scripts: [
{
js: ["content_script.js"],
matches: ["https://example.com/*"],
run_at: "document_start",
},
],
},
files: {
"content_script.js": contentScript,
},
});
await extension.startup();
return extension;
}

View File

@ -19,6 +19,21 @@ const testServer = createVersionizedHttpTestServer(
);
const TEST_URL = testServer.urlFor("index.html");
const INTEGRATION_TEST_PAGE_SOURCES = [
"index.html",
"script.js",
"test-functions.js",
"query.js?x=1",
"query.js?x=2",
"bundle.js",
"original.js",
"replaced-bundle.js",
"removed-original.js",
"named-eval.js",
"bootstrap 3b1a221408fdde86aa49",
"bootstrap 6fda1f7ea9ecbc1a2d5b",
];
/**
* This test opens the SourceTree manually via click events on the nested source,
* and then adds a source dynamically and asserts it is visible.
@ -231,6 +246,7 @@ add_task(async function testSourceTreeOnTheIntegrationTestPage() {
TEST_URL,
"index.html",
"script.js",
"test-functions.js",
"query.js?x=1",
"query.js?x=2",
"bundle.js",
@ -240,24 +256,18 @@ add_task(async function testSourceTreeOnTheIntegrationTestPage() {
"named-eval.js"
);
await waitForSourcesInSourceTree(dbg, [
"index.html",
"script.js",
"query.js?x=1",
"query.js?x=2",
"bundle.js",
"original.js",
"replaced-bundle.js",
"removed-original.js",
"named-eval.js",
"bootstrap 6fda1f7ea9ecbc1a2d5b",
"bootstrap 450f2ed5071212525ef7",
]);
await waitForSourcesInSourceTree(dbg, INTEGRATION_TEST_PAGE_SOURCES);
info("Assert the content of the named eval");
await selectSource(dbg, "named-eval.js");
assertTextContentOnLine(dbg, 3, `console.log("named-eval");`);
info("Assert that nameless eval don't show up in the source tree");
invokeInTab("breakInEval");
await waitForPaused(dbg);
await waitForSourcesInSourceTree(dbg, INTEGRATION_TEST_PAGE_SOURCES);
await resume(dbg);
info("Assert the content of sources with query string");
await selectSource(dbg, "query.js?x=1");
const tab = findElement(dbg, "activeTab");
@ -287,8 +297,62 @@ add_task(async function testSourceTreeOnTheIntegrationTestPage() {
pressKey(dbg, "quickOpen");
type(dbg, "query.js?x");
const resultItems = await waitForAllElements(dbg, "resultItems");
ok(resultItems[0].innerText.includes("query.js?x=1"));
// There can be intermediate updates in the results,
// so wait for the final expected value
await waitFor(async () => {
const resultItem = findElement(dbg, "resultItems");
if (!resultItem) {
return false;
}
return resultItem.innerText.includes("query.js?x=1");
}, "Results include the source with the query string");
});
/**
* Verify that Web Extension content scripts appear only when
* devtools.chrome.enabled is set to true and that they get
* automatically re-selected on page reload.
*/
add_task(async function testSourceTreeWithWebExtensionContentScript() {
const extension = await installAndStartContentScriptExtension();
info("Without the chrome preference, the content script doesn't show up");
await pushPref("devtools.chrome.enabled", false);
let dbg = await initDebugger("doc-content-script-sources.html");
// Let some time for unexpected source to appear
await wait(1000);
// Bug 1761975 - While the content script doesn't show up,
// the internals of WebExtension codebase appear in the debugger...
await waitForSourcesInSourceTree(dbg, ["ExtensionContent.jsm"]);
await dbg.toolbox.closeToolbox();
info("With the chrome preference, the content script shows up");
await pushPref("devtools.chrome.enabled", true);
const toolbox = await openToolboxForTab(gBrowser.selectedTab, "jsdebugger");
dbg = createDebuggerContext(toolbox);
await waitForSourcesInSourceTree(dbg, [
"content_script.js",
"ExtensionContent.jsm",
]);
await selectSource(dbg, "content_script.js");
ok(
findElementWithSelector(dbg, ".sources-list .focused"),
"Source is focused"
);
for (let i = 1; i < 3; i++) {
info(
`Reloading tab (${i} time), the content script should always be reselected`
);
gBrowser.reloadTab(gBrowser.selectedTab);
await waitForSelectedSource(dbg, "content_script.js");
ok(
findElementWithSelector(dbg, ".sources-list .focused"),
"Source is focused"
);
}
await dbg.toolbox.closeToolbox();
await extension.unload();
});
/**

View File

@ -3,19 +3,6 @@
<!doctype html>
<html>
<!-- A simple script -->
<script src="script.js"></script>
<!-- Scripts with query strings, which are merged in the SourceTree -->
<script src="query.js?x=1"></script>
<script src="query.js?x=2"></script>
<!-- A bundle/generated source that contains original.js -->
<script src="bundle.js"></script>
<!-- Another bundle/generated source that contains removed-original.js -->
<script src="replaced-bundle.js"></script>
<head>
<meta charset="utf-8"/>
<title>Empty test page 1</title>
@ -23,11 +10,13 @@
<body>
<!-- A simple inline script, that helps to cover breakable lines/columns -->
<!-- Have them define first, so that we avoid shifting lines when adding new scripts later in the html -->
<script>
// Comment
console.log('breakable-line');
</script>
<!-- A second inline script, that helps cover bugs when having two distinct inline scripts -->
<script>
console.log("second in line script");
@ -43,6 +32,21 @@
//# sourceURL=named-eval.js
`);
</script>
<!-- A simple script -->
<script src="script.js"></script>
<!-- Another script containing helper functions trigerred by the tests -->
<script src="test-functions.js"></script>
<!-- Scripts with query strings, which are merged in the SourceTree -->
<script src="query.js?x=1"></script>
<script src="query.js?x=2"></script>
<!-- A bundle/generated source that contains original.js -->
<script src="bundle.js"></script>
<!-- Another bundle/generated source that contains removed-original.js -->
<script src="replaced-bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,14 @@
/**
* This file contains various test functions called by the test script via `await invokeInTab("functionName");`.
*/
function breakInEval() {
eval(`
debugger;
window.evaledFunc = function() {
var foo = 1;
var bar = 2;
return foo + bar;
};
`);
}

View File

@ -3,19 +3,6 @@
<!doctype html>
<html>
<!-- A simple script -->
<script src="script.js"></script>
<!-- Scripts with query strings, which are merged in the SourceTree -->
<script src="query.js?x=1"></script>
<script src="query.js?x=2"></script>
<!-- A bundle/generated source that contains original.js -->
<script src="bundle.js"></script>
<!-- Another bundle/generated source that contains removed-original.js -->
<script src="replaced-bundle.js"></script>
<head>
<meta charset="utf-8"/>
<title>Empty test page 1</title>
@ -23,11 +10,13 @@
<body>
<!-- A simple inline script, that helps to cover breakable lines/columns -->
<!-- Have them define first, so that we avoid shifting lines when adding new scripts later in the html -->
<script>
// Comment
console.log('breakable-line');
</script>
<!-- A second inline script, that helps cover bugs when having two distinct inline scripts -->
<script>
console.log("second in line script");
@ -43,6 +32,21 @@
//# sourceURL=named-eval.js
`);
</script>
<!-- A simple script -->
<script src="script.js"></script>
<!-- Another script containing helper functions trigerred by the tests -->
<script src="test-functions.js"></script>
<!-- Scripts with query strings, which are merged in the SourceTree -->
<script src="query.js?x=1"></script>
<script src="query.js?x=2"></script>
<!-- A bundle/generated source that contains original.js -->
<script src="bundle.js"></script>
<!-- Another bundle/generated source that contains removed-original.js -->
<script src="replaced-bundle.js"></script>
</body>
</html>

View File

@ -0,0 +1,14 @@
/**
* This file contains various test functions called by the test script via `await invokeInTab("functionName");`.
*/
function breakInEval() {
eval(`
debugger;
window.evaledFunc = function() {
var foo = 1;
var bar = 2;
return foo + bar;
};
`);
}

View File

@ -96,3 +96,39 @@ async function runAllIntegrationTests(testFolder, env) {
await task(testServer, testUrl, env);
}
}
/**
* Install a Web Extension which will run a content script against any test page
* served from https://example.com
*
* This content script is meant to be debuggable when devtools.chrome.enabled is true.
*/
async function installAndStartContentScriptExtension() {
function contentScript() {
console.log("content script loads");
// This listener prevents the source from being garbage collected
// and be missing from the scripts returned by `dbg.findScripts()`
// in `ThreadActor._discoverSources`.
window.onload = () => {};
}
const extension = ExtensionTestUtils.loadExtension({
manifest: {
content_scripts: [
{
js: ["content_script.js"],
matches: ["https://example.com/*"],
run_at: "document_start",
},
],
},
files: {
"content_script.js": contentScript,
},
});
await extension.startup();
return extension;
}

View File

@ -2002,7 +2002,6 @@ async function expandSourceTree(dbg) {
);
for (const rootNode of rootNodes) {
await expandAllSourceNodes(dbg, rootNode);
await wait(250);
}
}
@ -2019,21 +2018,51 @@ async function waitForSourcesInSourceTree(
{ noExpand = false } = {}
) {
info(`waiting for ${sources.length} files in the source tree`);
await waitFor(async () => {
if (!noExpand) {
await expandSourceTree(dbg);
}
function getDisplayedSources() {
// Replace some non visible space characters that prevents Array.includes from working correctly
const displayedSources = [...findAllElements(dbg, "sourceTreeFiles")].map(
e => {
return e.textContent.trim().replace(/^[\s\u200b]*/g, "");
return [...findAllElements(dbg, "sourceTreeFiles")].map(e => {
return e.textContent.trim().replace(/^[\s\u200b]*/g, "");
});
}
try {
// Use custom timeout and retry count for waitFor as the test method is slow to resolve
// and default value makes the timeout unecessarily long
await waitFor(
async () => {
if (!noExpand) {
await expandSourceTree(dbg);
}
const displayedSources = getDisplayedSources();
return (
displayedSources.length == sources.length &&
sources.every(source => displayedSources.includes(source))
);
},
null,
100,
50
);
} catch (e) {
// Craft a custom error message to help understand what's wrong with the Source Tree content
const displayedSources = getDisplayedSources();
let msg = "Invalid Source Tree Content.\n";
const missingElements = [];
for (const source of sources) {
const idx = displayedSources.indexOf(source);
if (idx != -1) {
displayedSources.splice(idx, 1);
} else {
missingElements.push(source);
}
);
return (
displayedSources.length == sources.length &&
sources.every(source => displayedSources.includes(source))
);
});
}
if (missingElements.length > 0) {
msg += "Missing elements: " + missingElements.join(", ") + "\n";
}
if (displayedSources.length > 0) {
msg += "Unexpected elements: " + displayedSources.join(", ");
}
throw new Error(msg);
}
}
async function waitForNodeToGainFocus(dbg, index) {