summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/har/test
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/netmonitor/src/har/test')
-rw-r--r--devtools/client/netmonitor/src/har/test/browser-harautomation.ini16
-rw-r--r--devtools/client/netmonitor/src/har/test/browser.ini29
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_harautomation_simple.js35
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js220
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_import.js149
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_import_no-mime.js78
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_multipage.js153
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js51
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js43
-rw-r--r--devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js69
-rw-r--r--devtools/client/netmonitor/src/har/test/head.js45
-rw-r--r--devtools/client/netmonitor/src/har/test/html_har_import-test-page.html51
-rw-r--r--devtools/client/netmonitor/src/har/test/html_har_multipage_iframe.html24
-rw-r--r--devtools/client/netmonitor/src/har/test/html_har_multipage_page.html30
-rw-r--r--devtools/client/netmonitor/src/har/test/html_har_post-data-test-page.html55
-rw-r--r--devtools/client/netmonitor/src/har/test/sjs_cache-test-server.sjs14
-rw-r--r--devtools/client/netmonitor/src/har/test/sjs_cookies-test-server.sjs10
17 files changed, 1072 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/har/test/browser-harautomation.ini b/devtools/client/netmonitor/src/har/test/browser-harautomation.ini
new file mode 100644
index 0000000000..61ee56be44
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser-harautomation.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+prefs=
+ # This preference needs to be set before starting Firefox, so we use a
+ # dedicated browser.ini
+ devtools.netmonitor.har.enableAutoExportToFile=true
+
+support-files =
+ head.js
+ !/devtools/client/netmonitor/test/head.js
+ !/devtools/client/netmonitor/test/html_simple-test-page.html
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+[browser_harautomation_simple.js]
diff --git a/devtools/client/netmonitor/src/har/test/browser.ini b/devtools/client/netmonitor/src/har/test/browser.ini
new file mode 100644
index 0000000000..c45b2b0358
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser.ini
@@ -0,0 +1,29 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+skip-if = http3 # Bug 1829298
+support-files =
+ head.js
+ html_har_import-test-page.html
+ html_har_multipage_iframe.html
+ html_har_multipage_page.html
+ html_har_post-data-test-page.html
+ sjs_cache-test-server.sjs
+ sjs_cookies-test-server.sjs
+ !/devtools/client/netmonitor/test/head.js
+ !/devtools/client/netmonitor/test/html_simple-test-page.html
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+[browser_net_har_copy_all_as_har.js]
+skip-if =
+ !debug && os == "mac" #Bug 1622925
+ !debug && os == "linux" #Bug 1622925
+ win10_2004 # Bug 1723573
+ win11_2009 # Bug 1797751
+[browser_net_har_import.js]
+[browser_net_har_import_no-mime.js]
+[browser_net_har_multipage.js]
+[browser_net_har_post_data.js]
+[browser_net_har_post_data_on_get.js]
+[browser_net_har_throttle_upload.js]
diff --git a/devtools/client/netmonitor/src/har/test/browser_harautomation_simple.js b/devtools/client/netmonitor/src/har/test/browser_harautomation_simple.js
new file mode 100644
index 0000000000..9da746d28f
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_harautomation_simple.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const HAR_FILENAME = "test_filename.har";
+
+// We expect the HAR file to be created on reload in profD/har/logs
+const HAR_PATH = ["har", "logs", HAR_FILENAME];
+
+/**
+ * Smoke test for automated HAR export.
+ * Note that the `enableAutoExportToFile` is set from browser-harautomation.ini
+ * because the preference needs to be set before starting the browser.
+ */
+add_task(async function () {
+ // Set a simple test filename for the exported HAR.
+ await pushPref("devtools.netmonitor.har.defaultFileName", "test_filename");
+
+ const tab = await addTab(SIMPLE_URL);
+ const toolbox = await gDevTools.showToolboxForTab(tab, {
+ toolId: "inspector",
+ });
+
+ await reloadBrowser();
+
+ info("Wait until the HAR file is created in the profile directory");
+ await waitUntil(() => FileUtils.getFile("ProfD", HAR_PATH).exists());
+
+ const harFile = FileUtils.getFile("ProfD", HAR_PATH);
+ ok(harFile.exists(), "HAR file was automatically created");
+
+ await toolbox.destroy();
+ await removeTab(tab);
+});
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js b/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
new file mode 100644
index 0000000000..ab4302883c
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
@@ -0,0 +1,220 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Basic tests for exporting Network panel content into HAR format.
+ */
+
+const EXPECTED_REQUEST_HEADER_COUNT = 9;
+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(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, "Network Monitor test page", "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.
+ ok(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, 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, toolbox }) {
+ info("Navigate to an empty page");
+ const topDocumentURL =
+ "https://example.org/document-builder.sjs?html=empty-document";
+ const iframeURL =
+ "https://example.org/document-builder.sjs?html=" +
+ 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");
+ ok(entry.time > 0, "Check the total time");
+ is(entry.request.method, "GET", "Check the method");
+ is(entry.request.url, 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({
+ tab,
+ 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);
+}
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_import.js b/devtools/client/netmonitor/src/har/test/browser_net_har_import.js
new file mode 100644
index 0000000000..879966e653
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_import.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for importing HAR data.
+ */
+add_task(async () => {
+ const { tab, monitor } = await initNetMonitor(
+ HAR_EXAMPLE_URL + "html_har_import-test-page.html",
+ { requestCount: 1 }
+ );
+
+ info("Starting test... ");
+
+ const { actions, store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+ const { HarImporter } = windowRequire(
+ "devtools/client/netmonitor/src/har/har-importer"
+ );
+
+ store.dispatch(Actions.batchEnable(false));
+
+ // Execute one POST request on the page and wait till its done.
+ const wait = waitForNetworkEvents(monitor, 3);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await content.wrappedJSObject.executeTest();
+ });
+ await wait;
+
+ // Copy HAR into the clipboard
+ const json1 = await copyAllAsHARWithContextMenu(monitor, { asString: true });
+
+ // Import HAR string
+ const importer = new HarImporter(actions);
+ importer.import(json1);
+
+ // Copy HAR into the clipboard again
+ const json2 = await copyAllAsHARWithContextMenu(monitor, { asString: true });
+
+ // Compare exported HAR data
+ const har1 = JSON.parse(json1);
+ const har2 = JSON.parse(json2);
+
+ // Explicit tests
+ is(har2.log.entries.length, 3, "There must be expected number of requests");
+ ok(
+ har2.log.pages[0].title.endsWith("Network Monitor Test Page"),
+ "There must be some page title"
+ );
+ ok(
+ !!har2.log.entries[0].request.headers.length,
+ "There must be some request headers"
+ );
+ ok(
+ !!har2.log.entries[0].response.headers.length,
+ "There must be some response headers"
+ );
+ is(
+ har2.log.entries[1].response.cookies.length,
+ 3,
+ "There must be expected number of cookies"
+ );
+ is(
+ har2.log.entries[1]._securityState,
+ "insecure",
+ "There must be expected security state"
+ );
+ is(har2.log.entries[2].response.status, 304, "There must be expected status");
+
+ // Complex test comparing exported & imported HARs.
+ ok(compare(har1.log, har2.log, ["log"]), "Exported HAR must be the same");
+
+ // Clean up
+ return teardown(monitor);
+});
+
+/**
+ * Check equality of HAR files.
+ */
+function compare(obj1, obj2, path) {
+ const keys1 = Object.getOwnPropertyNames(obj1).sort();
+ const keys2 = Object.getOwnPropertyNames(obj2).sort();
+
+ const name = path.join("/");
+
+ is(
+ keys1.length,
+ keys2.length,
+ "There must be the same number of keys for: " + name
+ );
+ if (keys1.length != keys2.length) {
+ return false;
+ }
+
+ is(keys1.join(), keys2.join(), "There must be the same keys for: " + name);
+ if (keys1.join() != keys2.join()) {
+ return false;
+ }
+
+ // Page IDs are generated and don't have to be the same after import.
+ const ignore = [
+ "log/entries/0/pageref",
+ "log/entries/1/pageref",
+ "log/entries/2/pageref",
+ "log/pages/0/id",
+ "log/pages/1/id",
+ "log/pages/2/id",
+ ];
+
+ let result = true;
+ for (let i = 0; i < keys1.length; i++) {
+ const key = keys1[i];
+ const prop1 = obj1[key];
+ const prop2 = obj2[key];
+
+ if (prop1 instanceof Array) {
+ if (!(prop2 instanceof Array)) {
+ ok(false, "Arrays are not the same " + name);
+ result = false;
+ break;
+ }
+ if (!compare(prop1, prop2, path.concat(key))) {
+ result = false;
+ break;
+ }
+ } else if (prop1 instanceof Object) {
+ if (!(prop2 instanceof Object)) {
+ ok(false, "Objects are not the same in: " + name);
+ result = false;
+ break;
+ }
+ if (!compare(prop1, prop2, path.concat(key))) {
+ result = false;
+ break;
+ }
+ } else if (prop1 !== prop2) {
+ const propName = name + "/" + key;
+ if (!ignore.includes(propName)) {
+ is(prop1, prop2, "Values are not the same: " + propName);
+ result = false;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_import_no-mime.js b/devtools/client/netmonitor/src/har/test/browser_net_har_import_no-mime.js
new file mode 100644
index 0000000000..91c5160217
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_import_no-mime.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests importing HAR with missing `response.content.mimeType` does not crash the netmonitor.
+ */
+add_task(async () => {
+ const { monitor } = await initNetMonitor(SIMPLE_URL, {
+ requestCount: 1,
+ });
+
+ info("Starting test... ");
+
+ const { document, actions, store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+ const { HarImporter } = windowRequire(
+ "devtools/client/netmonitor/src/har/har-importer"
+ );
+
+ store.dispatch(Actions.batchEnable(false));
+
+ // Invalid HAR json which should contain `entries[0].response.content.mimeType`
+ const invalidHarJSON = {
+ log: {
+ version: "1.2",
+ pages: [
+ {
+ title: "bla",
+ },
+ ],
+ entries: [
+ {
+ request: {
+ method: "POST",
+ url: "https://bla.com",
+ httpVersion: "",
+ headers: [],
+ cookies: [],
+ queryString: [],
+ },
+ response: {
+ content: {
+ size: 1231,
+ text: '{"requests":[{"uri":"https://bla.com"}]}',
+ },
+ headers: [],
+ },
+ timings: {},
+ cache: {},
+ },
+ ],
+ },
+ };
+
+ // Import invalid Har file
+ const importer = new HarImporter(actions);
+ importer.import(JSON.stringify(invalidHarJSON));
+
+ const waitForResponsePanelOpen = waitUntil(() =>
+ document.querySelector("#response-panel")
+ );
+
+ // Open the response details panel
+ EventUtils.sendMouseEvent(
+ { type: "mousedown" },
+ document.querySelector(".request-list-item")
+ );
+ clickOnSidebarTab(document, "response");
+
+ await waitForResponsePanelOpen;
+ ok(true, "The response panel opened");
+
+ // Clean up
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_multipage.js b/devtools/client/netmonitor/src/har/test/browser_net_har_multipage.js
new file mode 100644
index 0000000000..38936f73fe
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_multipage.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+const MULTIPAGE_IFRAME_URL = HAR_EXAMPLE_URL + "html_har_multipage_iframe.html";
+const MULTIPAGE_PAGE_URL = HAR_EXAMPLE_URL + "html_har_multipage_page.html";
+
+/**
+ * Tests HAR export with navigations and multipage support
+ */
+add_task(async function () {
+ await testHARWithNavigation({ enableMultipage: false, filter: false });
+ await testHARWithNavigation({ enableMultipage: true, filter: false });
+ await testHARWithNavigation({ enableMultipage: false, filter: true });
+ await testHARWithNavigation({ enableMultipage: true, filter: true });
+});
+
+async function testHARWithNavigation({ enableMultipage, filter }) {
+ await pushPref("devtools.netmonitor.persistlog", true);
+ await pushPref("devtools.netmonitor.har.multiple-pages", enableMultipage);
+
+ const { tab, monitor } = await initNetMonitor(MULTIPAGE_PAGE_URL + "?page1", {
+ requestCount: 1,
+ });
+
+ info("Starting test... ");
+
+ const { store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+ store.dispatch(Actions.batchEnable(false));
+
+ info("Perform 3 additional requests");
+ let onNetworkEvents = waitForNetworkEvents(monitor, 3);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.sendRequests(3);
+ });
+ await onNetworkEvents;
+
+ info("Navigate to a second page where we will not perform any extra request");
+ onNetworkEvents = waitForNetworkEvents(monitor, 1);
+ await navigateTo(MULTIPAGE_PAGE_URL + "?page2");
+ await onNetworkEvents;
+
+ info("Navigate to a third page where we will not perform any extra request");
+ onNetworkEvents = waitForNetworkEvents(monitor, 1);
+ await navigateTo(MULTIPAGE_PAGE_URL + "?page3");
+ await onNetworkEvents;
+
+ info("Perform 2 additional requests");
+ onNetworkEvents = waitForNetworkEvents(monitor, 2);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.sendRequests(2);
+ });
+ await onNetworkEvents;
+
+ info("Create an iframe which will perform 2 additional requests");
+ onNetworkEvents = waitForNetworkEvents(monitor, 2);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [MULTIPAGE_IFRAME_URL],
+ async function (url) {
+ const iframe = content.document.createElement("iframe");
+ const onLoad = new Promise(resolve =>
+ iframe.addEventListener("load", resolve, { once: true })
+ );
+ content.content.document.body.appendChild(iframe);
+ iframe.setAttribute("src", url);
+ await onLoad;
+ }
+ );
+ await onNetworkEvents;
+
+ if (filter) {
+ info("Start filtering requests");
+ store.dispatch(Actions.setRequestFilterText("?request"));
+ }
+
+ info("Trigger Copy All As HAR from the context menu");
+ const har = await copyAllAsHARWithContextMenu(monitor);
+
+ // Check out the HAR log.
+ isnot(har.log, null, "The HAR log must exist");
+
+ if (enableMultipage) {
+ is(har.log.pages.length, 3, "There must be three pages");
+ } else {
+ is(har.log.pages.length, 1, "There must be one page");
+ }
+
+ if (!filter) {
+ // Expect 9 requests:
+ // - 3 requests performed with sendRequests on the first page
+ // - 1 navigation request to the second page
+ // - 1 navigation request to the third page
+ // - 2 requests performed with sendRequests on the third page
+ // - 1 request to load an iframe on the third page
+ // - 1 request from the iframe on the third page
+ is(har.log.entries.length, 9, "There must be 9 requests");
+ } else {
+ // Same but we only expect the fetch requests
+ is(har.log.entries.length, 6, "There must be 6 requests");
+ }
+
+ if (enableMultipage) {
+ // With multipage enabled, check that the page entries are valid and that
+ // requests are referencing the expected page id.
+ assertPageDetails(har.log.pages[0], "page_0", "HAR Multipage test page");
+ assertPageRequests(har.log.entries, 0, 2, har.log.pages[0].id);
+
+ assertPageDetails(har.log.pages[1], "page_1", "HAR Multipage test page");
+ if (filter) {
+ // When filtering, we don't expect any request to match page_1
+ } else {
+ assertPageRequests(har.log.entries, 3, 3, har.log.pages[1].id);
+ }
+
+ assertPageDetails(har.log.pages[2], "page_2", "HAR Multipage test page");
+ if (filter) {
+ assertPageRequests(har.log.entries, 3, 5, har.log.pages[2].id);
+ } else {
+ assertPageRequests(har.log.entries, 4, 8, har.log.pages[2].id);
+ }
+ } else {
+ is(har.log.pages[0].id, "page_1");
+ // Without multipage, all requests are associated with the only page entry.
+ for (const entry of har.log.entries) {
+ is(entry.pageref, "page_1");
+ }
+ }
+
+ // Clean up
+ return teardown(monitor);
+}
+
+function assertPageDetails(page, expectedId, expectedTitle) {
+ is(page.id, expectedId, "Page has the expected id");
+ is(page.title, expectedTitle, "Page has the expected title");
+}
+
+function assertPageRequests(entries, startIndex, endIndex, expectedPageId) {
+ for (let i = startIndex; i < endIndex + 1; i++) {
+ const entry = entries[i];
+ is(
+ entry.pageref,
+ expectedPageId,
+ `Entry ${i} is attached to page id: ${expectedPageId}`
+ );
+ }
+}
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
new file mode 100644
index 0000000000..0640364a39
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for exporting POST data into HAR format.
+ */
+add_task(async function () {
+ const { tab, monitor } = await initNetMonitor(
+ HAR_EXAMPLE_URL + "html_har_post-data-test-page.html",
+ { requestCount: 1 }
+ );
+
+ info("Starting test... ");
+
+ const { store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+ store.dispatch(Actions.batchEnable(false));
+
+ // Execute one POST request on the page and wait till its done.
+ const wait = waitForNetworkEvents(monitor, 1);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.executeTest();
+ });
+ await wait;
+
+ // Copy HAR into the clipboard (asynchronous).
+ const har = await copyAllAsHARWithContextMenu(monitor);
+
+ // Check out the HAR log.
+ isnot(har.log, null, "The HAR log must exist");
+ is(har.log.pages.length, 1, "There must be one page");
+ is(har.log.entries.length, 1, "There must be one request");
+
+ const entry = har.log.entries[0];
+ is(
+ entry.request.postData.mimeType,
+ "application/json",
+ "Check post data content type"
+ );
+ is(
+ entry.request.postData.text,
+ "{'first': 'John', 'last': 'Doe'}",
+ "Check post data payload"
+ );
+
+ // Clean up
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js
new file mode 100644
index 0000000000..206fc43da6
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data_on_get.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests for exporting POST data into HAR format.
+ */
+add_task(async function () {
+ const { tab, monitor } = await initNetMonitor(
+ HAR_EXAMPLE_URL + "html_har_post-data-test-page.html",
+ { requestCount: 1 }
+ );
+
+ info("Starting test... ");
+
+ const { store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+ store.dispatch(Actions.batchEnable(false));
+
+ // Execute one GET request on the page and wait till its done.
+ const wait = waitForNetworkEvents(monitor, 1);
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ content.wrappedJSObject.executeTest3();
+ });
+ await wait;
+
+ // Copy HAR into the clipboard (asynchronous).
+ const har = await copyAllAsHARWithContextMenu(monitor);
+
+ // Check out the HAR log.
+ isnot(har.log, null, "The HAR log must exist");
+ is(har.log.pages.length, 1, "There must be one page");
+ is(har.log.entries.length, 1, "There must be one request");
+
+ const entry = har.log.entries[0];
+
+ is(entry.request.postData, undefined, "Check post data is not present");
+
+ // Clean up
+ return teardown(monitor);
+});
diff --git a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
new file mode 100644
index 0000000000..24f7d482ca
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test timing of upload when throttling.
+
+"use strict";
+
+add_task(async function () {
+ await throttleUploadTest(true);
+ await throttleUploadTest(false);
+});
+
+async function throttleUploadTest(actuallyThrottle) {
+ const { tab, monitor } = await initNetMonitor(
+ HAR_EXAMPLE_URL + "html_har_post-data-test-page.html",
+ { requestCount: 1 }
+ );
+
+ info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
+
+ const { connector, store, windowRequire } = monitor.panelWin;
+ const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+ store.dispatch(Actions.batchEnable(false));
+
+ const size = 4096;
+ const uploadSize = actuallyThrottle ? size / 3 : 0;
+
+ const throttleProfile = {
+ latency: 0,
+ download: 200000,
+ upload: uploadSize,
+ };
+
+ info("sending throttle request");
+ await connector.updateNetworkThrottling(true, throttleProfile);
+
+ // Execute one POST request on the page and wait till its done.
+ const wait = waitForNetworkEvents(monitor, 1);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ size }],
+ async function (args) {
+ content.wrappedJSObject.executeTest2(args.size);
+ }
+ );
+ await wait;
+
+ // Copy HAR into the clipboard (asynchronous).
+ const har = await copyAllAsHARWithContextMenu(monitor);
+
+ // Check out the HAR log.
+ isnot(har.log, null, "The HAR log must exist");
+ is(har.log.pages.length, 1, "There must be one page");
+ is(har.log.entries.length, 1, "There must be one request");
+
+ const entry = har.log.entries[0];
+ is(entry.request.postData.text, "x".repeat(size), "Check post data payload");
+
+ const wasTwoSeconds = entry.timings.send >= 2000;
+ if (actuallyThrottle) {
+ ok(wasTwoSeconds, "upload should have taken more than 2 seconds");
+ } else {
+ ok(!wasTwoSeconds, "upload should not have taken more than 2 seconds");
+ }
+
+ // Clean up
+ await teardown(monitor);
+}
diff --git a/devtools/client/netmonitor/src/har/test/head.js b/devtools/client/netmonitor/src/har/test/head.js
new file mode 100644
index 0000000000..b41ea580fd
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/head.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+/* import-globals-from ../../../test/head.js */
+
+// Load the NetMonitor head.js file to share its API.
+var netMonitorHead =
+ "chrome://mochitests/content/browser/devtools/client/netmonitor/test/head.js";
+Services.scriptloader.loadSubScript(netMonitorHead, this);
+
+// Directory with HAR related test files.
+const HAR_EXAMPLE_URL =
+ "http://example.com/browser/devtools/client/netmonitor/src/har/test/";
+
+/**
+ * Trigger a "copy all as har" from the context menu of the requests list.
+
+ * @param {Object} monitor
+ * The network monitor object
+ */
+async function copyAllAsHARWithContextMenu(monitor, { asString = false } = {}) {
+ const { HarMenuUtils } = monitor.panelWin.windowRequire(
+ "devtools/client/netmonitor/src/har/har-menu-utils"
+ );
+
+ info("Open the context menu on the first visible request.");
+ const firstRequest =
+ monitor.panelWin.document.querySelectorAll(".request-list-item")[0];
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, firstRequest);
+ EventUtils.sendMouseEvent({ type: "contextmenu" }, firstRequest);
+
+ info("Trigger Copy All As HAR from the context menu");
+ const onHarCopyDone = HarMenuUtils.once("copy-all-as-har-done");
+ await selectContextMenuItem(monitor, "request-list-context-copy-all-as-har");
+ const jsonString = await onHarCopyDone;
+
+ if (asString) {
+ return jsonString;
+ }
+ return JSON.parse(jsonString);
+}
diff --git a/devtools/client/netmonitor/src/har/test/html_har_import-test-page.html b/devtools/client/netmonitor/src/har/test/html_har_import-test-page.html
new file mode 100644
index 0000000000..04d5ec33ba
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/html_har_import-test-page.html
@@ -0,0 +1,51 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor Test Page</title>
+ </head>
+
+ <body>
+ <p>HAR import test</p>
+
+ <script type="text/javascript">
+ /* exported executeTest, executeTest2, executeTest3 */
+ "use strict";
+
+ function post(address, data) {
+ return new Promise(resolve => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("POST", address, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.onload = resolve;
+ xhr.send(data);
+ });
+ }
+
+ function get(address) {
+ return new Promise(resolve => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", address);
+ xhr.onload = resolve;
+ xhr.send();
+ });
+ }
+
+ async function executeTest() {
+ const url = "html_har_import-test-page.html";
+ const data = "{'first': 'John', 'last': 'Doe'}";
+ await post(url, data);
+ await get("sjs_cookies-test-server.sjs");
+ await get("sjs_cache-test-server.sjs");
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/src/har/test/html_har_multipage_iframe.html b/devtools/client/netmonitor/src/har/test/html_har_multipage_iframe.html
new file mode 100644
index 0000000000..4e0fc96344
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/html_har_multipage_iframe.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor HAR Multipage test iframe</title>
+ </head>
+
+ <body>
+ <p>HAR Multipage test iframe</p>
+
+ <script type="text/javascript">
+ "use strict";
+ fetch("?request-from-iframe");
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/src/har/test/html_har_multipage_page.html b/devtools/client/netmonitor/src/har/test/html_har_multipage_page.html
new file mode 100644
index 0000000000..d36fbca52b
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/html_har_multipage_page.html
@@ -0,0 +1,30 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>HAR Multipage test page</title>
+ </head>
+
+ <body>
+ <p>HAR Multipage test page</p>
+
+ <script type="text/javascript">
+ /* exported sendRequests */
+ "use strict";
+
+ async function sendRequests(requestsCount) {
+ for (let i = 0; i < requestsCount; i++) {
+ fetch("?request" + i);
+ }
+ }
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/src/har/test/html_har_post-data-test-page.html b/devtools/client/netmonitor/src/har/test/html_har_post-data-test-page.html
new file mode 100644
index 0000000000..5e42c6139d
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/html_har_post-data-test-page.html
@@ -0,0 +1,55 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Monitor Test Page</title>
+ </head>
+
+ <body>
+ <p>HAR POST data test</p>
+
+ <script type="text/javascript">
+ /* exported executeTest, executeTest2, executeTest3 */
+ "use strict";
+
+ function post(address, data) {
+ const xhr = new XMLHttpRequest();
+ xhr.open("POST", address, true);
+ xhr.setRequestHeader("Content-Type", "application/json");
+ xhr.send(data);
+ }
+
+ function get(address) {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", address);
+ xhr.send();
+ }
+
+ function executeTest() {
+ const url = "html_har_post-data-test-page.html";
+ const data = "{'first': 'John', 'last': 'Doe'}";
+ post(url, data);
+ }
+
+ function executeTest2(size) {
+ const url = "html_har_post-data-test-page.html";
+ const data = "x".repeat(size);
+ post(url, data);
+ }
+
+ function executeTest3(size) {
+ const url = "html_har_post-data-test-page.html";
+ get(url);
+ }
+
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/client/netmonitor/src/har/test/sjs_cache-test-server.sjs b/devtools/client/netmonitor/src/har/test/sjs_cache-test-server.sjs
new file mode 100644
index 0000000000..66081fe1bb
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/sjs_cache-test-server.sjs
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 304, "Not Modified");
+ response.setHeader(
+ "Cache-Control",
+ "no-transform,public,max-age=300,s-maxage=900"
+ );
+ response.setHeader("Expires", "Thu, 01 Dec 2100 20:00:00 GMT");
+ response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+ response.write("Hello from cache!");
+}
diff --git a/devtools/client/netmonitor/src/har/test/sjs_cookies-test-server.sjs b/devtools/client/netmonitor/src/har/test/sjs_cookies-test-server.sjs
new file mode 100644
index 0000000000..a86a0f13cd
--- /dev/null
+++ b/devtools/client/netmonitor/src/har/test/sjs_cookies-test-server.sjs
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Set-Cookie", "tom=cool; Max-Age=10; HttpOnly", true);
+ response.setHeader("Set-Cookie", "bob=true; Max-Age=10; HttpOnly", true);
+ response.setHeader("Set-Cookie", "foo=bar; Max-Age=10; HttpOnly", true);
+ response.write("Hello world!");
+}