Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
/**
* Basic tests for exporting Network panel content into HAR format.
*/
const EXPECTED_REQUEST_HEADER_COUNT = 13;
const EXPECTED_RESPONSE_HEADER_COUNT = 6;
add_task(async function () {
// Disable tcp fast open, because it is setting a response header indicator
// (bug 1352274). TCP Fast Open is not present on all platforms therefore the
// number of response headers will vary depending on the platform.
await pushPref("network.tcp.tcp_fastopen_enable", false);
const { tab, monitor, toolbox } = await initNetMonitor(HTTPS_SIMPLE_URL, {
requestCount: 1,
});
info("Starting test... ");
await testSimpleReload({ tab, monitor, toolbox });
await testResponseBodyLimits({ tab, monitor, toolbox });
await testManyReloads({ tab, monitor, toolbox });
await testClearedRequests({ tab, monitor, toolbox });
// Do not use teardown(monitor) as testClearedRequests register broken requests
// which never complete and would block on waitForAllNetworkUpdateEvents
await closeTabAndToolbox();
});
async function testSimpleReload({ tab, monitor, toolbox }) {
info("Test with a simple page reload");
const har = await reloadAndCopyAllAsHar({ tab, monitor, toolbox });
// Check out HAR log
isnot(har.log, null, "The HAR log must exist");
is(har.log.creator.name, "Firefox", "The creator field must be set");
is(har.log.browser.name, "Firefox", "The browser field must be set");
is(har.log.pages.length, 1, "There must be one page");
is(har.log.entries.length, 1, "There must be one request");
const page = har.log.pages[0];
is(page.title, HTTPS_SIMPLE_URL, "There must be some page title");
ok("onContentLoad" in page.pageTimings, "There must be onContentLoad time");
ok("onLoad" in page.pageTimings, "There must be onLoad time");
const entry = har.log.entries[0];
assertNavigationRequestEntry(entry);
info("We get the response content and timings when doing a simple reload");
isnot(entry.response.content.text, undefined, "Check response body");
is(entry.response.content.text.length, 465, "Response body is complete");
isnot(entry.timings, undefined, "Check timings");
}
async function testResponseBodyLimits({ tab, monitor, toolbox }) {
info("Test response body limit (non zero).");
await pushPref("devtools.netmonitor.responseBodyLimit", 10);
let har = await reloadAndCopyAllAsHar({ tab, monitor, toolbox });
let entry = har.log.entries[0];
is(entry.response.content.text.length, 10, "Response body must be truncated");
info("Test response body limit (zero).");
await pushPref("devtools.netmonitor.responseBodyLimit", 0);
har = await reloadAndCopyAllAsHar({ tab, monitor, toolbox });
entry = har.log.entries[0];
is(
entry.response.content.text.length,
465,
"Response body must not be truncated"
);
}
async function testManyReloads({ tab, monitor, toolbox }) {
const har = await reloadAndCopyAllAsHar({
tab,
monitor,
toolbox,
reloadTwice: true,
});
// In most cases, we will have two requests, but sometimes,
// the first one might be missing as we couldn't fetch any lazy data for it.
Assert.greaterOrEqual(
har.log.entries.length,
1,
"There must be at least one request"
);
info(
"Assert the first navigation request which has been cancelled by the second reload"
);
// Requests may come out of order, so try to find the bogus cancelled request
let entry = har.log.entries.find(e => e.response.status == 0);
if (entry) {
ok(entry, "Found the cancelled request");
is(entry.request.method, "GET", "Method is set");
is(entry.request.url, HTTPS_SIMPLE_URL, "URL is set");
// We always get the following headers:
// "Host", "User-agent", "Accept", "Accept-Language", "Accept-Encoding", "Connection"
// but are missing the three last headers:
// "Upgrade-Insecure-Requests", "Pragma", "Cache-Control"
is(entry.request.headers.length, 6, "But headers are partialy populated");
is(entry.response.status, 0, "And status is set to 0");
}
entry = har.log.entries.find(e => e.response.status != 0);
assertNavigationRequestEntry(entry);
}
async function testClearedRequests({ tab, monitor }) {
info("Navigate to an empty page");
const topDocumentURL =
const iframeURL =
encodeURIComponent(
`iframe<script>fetch("/document-builder.sjs?html=iframe-request")</script>`
);
await waitForAllNetworkUpdateEvents();
await navigateTo(topDocumentURL);
info("Create an iframe doing a request and remove the iframe.");
info(
"Doing this, should notify a network request that is destroyed on the server side"
);
const onNetworkEvents = waitForNetworkEvents(monitor, 2);
await SpecialPowers.spawn(
tab.linkedBrowser,
[iframeURL],
async function (_iframeURL) {
const iframe = content.document.createElement("iframe");
iframe.setAttribute("src", _iframeURL);
content.document.body.appendChild(iframe);
}
);
// Wait for the two request to be processed (iframe doc + fetch requests)
// before removing the iframe so that the netmonitor is able to fetch
// all lazy data without throwing
await onNetworkEvents;
await waitForAllNetworkUpdateEvents();
info("Remove the iframe so that lazy request data are freed");
await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
content.document.querySelector("iframe").remove();
});
// HAR will try to re-fetch lazy data and may throw on the iframe fetch request.
// This subtest is meants to verify we aren't throwing here and HAR export
// works fine, even if some requests can't be fetched.
const har = await copyAllAsHARWithContextMenu(monitor);
is(har.log.entries.length, 2, "There must be two requests");
is(
har.log.entries[0].request.url,
topDocumentURL,
"First request is for the top level document"
);
is(
har.log.entries[1].request.url,
iframeURL,
"Second request is for the iframe"
);
info(
"The fetch request doesn't appear in HAR export, because its lazy data is freed and we completely ignore the request."
);
}
function assertNavigationRequestEntry(entry) {
info("Assert that the entry relates to the navigation request");
Assert.greater(entry.time, 0, "Check the total time");
is(entry.request.method, "GET", "Check the method");
is(entry.request.url, HTTPS_SIMPLE_URL, "Check the URL");
is(
entry.request.headers.length,
EXPECTED_REQUEST_HEADER_COUNT,
"Check number of request headers"
);
is(entry.response.status, 200, "Check response status");
is(entry.response.statusText, "OK", "Check response status text");
is(
entry.response.headers.length,
EXPECTED_RESPONSE_HEADER_COUNT,
"Check number of response headers"
);
is(
entry.response.content.mimeType,
"text/html",
"Check response content type"
);
}
/**
* Reload the page and copy all as HAR.
*/
async function reloadAndCopyAllAsHar({
monitor,
toolbox,
reloadTwice = false,
}) {
const { store, windowRequire } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
store.dispatch(Actions.batchEnable(false));
const onNetworkEvent = waitForNetworkEvents(monitor, 1);
const { onDomCompleteResource } =
await waitForNextTopLevelDomCompleteResource(toolbox.commands);
if (reloadTwice) {
reloadBrowser();
}
await reloadBrowser();
info("Waiting for network events");
await onNetworkEvent;
info("Waiting for DOCUMENT_EVENT dom-complete resource");
await onDomCompleteResource;
return copyAllAsHARWithContextMenu(monitor);
}