summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
blob: bcbb4bef2aeb052eb70e7736f5f9684c0d55efe3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* 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, 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, 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");
  Assert.greater(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);
}