summaryrefslogtreecommitdiffstats
path: root/devtools/client/netmonitor/test/browser_net_resend.js
blob: 9150bfe3bac05189dd7d4bb9f7d95d7492357655 (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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
/* Any copyright is dedicated to the Public Domain.
 *  http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
 * Tests if resending a request works.
 */

add_task(async function () {
  if (
    Services.prefs.getBoolPref(
      "devtools.netmonitor.features.newEditAndResend",
      true
    )
  ) {
    await testResendRequest();
  } else {
    await testOldEditAndResendPanel();
  }
});

// This tests resending a request without editing using
// the resend context menu item. This particularly covering
// the new resend functionality.
async function testResendRequest() {
  const { tab, monitor } = await initNetMonitor(POST_DATA_URL, {
    requestCount: 1,
  });
  info("Starting test... ");

  const { document, store, windowRequire } = monitor.panelWin;

  // Action should be processed synchronously in tests.
  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
  store.dispatch(Actions.batchEnable(false));

  await performRequests(monitor, tab, 2);

  is(
    document.querySelectorAll(".request-list-item").length,
    2,
    "There are currently two requests"
  );

  const firstResend = await resendRequestAndWaitForNewRequest(
    monitor,
    document.querySelectorAll(".request-list-item")[0]
  );

  Assert.notStrictEqual(
    firstResend.originalResource.resourceId,
    firstResend.newResource.resourceId,
    "The resent request is different resource from the first request"
  );

  is(
    firstResend.originalResource.url,
    firstResend.newResource.url,
    "The resent request has the same url and query parameters and the first request"
  );

  // The priority header only appears when the urgency and incremental values
  // are not both default values (u=3 and i=false). In this case the original
  // request has no priority header and the resent request does, hence we subtract one.
  is(
    firstResend.originalResource.requestHeaders.headers.length,
    firstResend.newResource.requestHeaders.headers.length - 1,
    "The no of headers are the same"
  );

  // Because a resent request has a different purpose and principal it will
  // also have a different CoS flag (meaning a different priority header).
  // So we can't compare the original and resent request's priority and skip it.
  firstResend.originalResource.requestHeaders.headers.forEach(
    ({ name, value }) => {
      if (name === "Priority") {
        return;
      }
      const foundHeader = firstResend.newResource.requestHeaders.headers.find(
        header => header.name == name
      );
      is(
        value,
        foundHeader.value,
        `The '${name}' header for the request and the resent request match`
      );
    }
  );

  info("Check that the custom headers and form data are resent correctly");
  const secondResend = await resendRequestAndWaitForNewRequest(
    monitor,
    document.querySelectorAll(".request-list-item")[1]
  );

  Assert.notStrictEqual(
    secondResend.originalResource.resourceId,
    secondResend.newResource.resourceId,
    "The resent request is different resource from the second request"
  );

  const customHeader =
    secondResend.originalResource.requestHeaders.headers.find(
      header => header.name == "custom-header-xxx"
    );

  const customHeaderInResentRequest =
    secondResend.newResource.requestHeaders.headers.find(
      header => header.name == "custom-header-xxx"
    );

  is(
    customHeader.value,
    customHeaderInResentRequest.value,
    "The custom header in the resent request is the same as the second request"
  );

  is(
    customHeaderInResentRequest.value,
    "custom-value-xxx",
    "The custom header in the resent request is correct"
  );

  is(
    secondResend.originalResource.requestPostData.postData.text,
    secondResend.newResource.requestPostData.postData.text,
    "The form data in the resent is the same as the second request"
  );
}

async function resendRequestAndWaitForNewRequest(monitor, originalRequestItem) {
  const { document, store, windowRequire, connector } = monitor.panelWin;
  const { getSelectedRequest, getDisplayedRequests } = windowRequire(
    "devtools/client/netmonitor/src/selectors/index"
  );

  info("Select the request to resend");
  const expectedNoOfRequestsAfterResend =
    getDisplayedRequests(store.getState()).length + 1;

  const waitForHeaders = waitUntil(() =>
    document.querySelector(".headers-overview")
  );
  EventUtils.sendMouseEvent({ type: "mousedown" }, originalRequestItem);
  await waitForHeaders;

  const originalResourceId = getSelectedRequest(store.getState()).id;

  const waitForNewRequest = waitUntil(
    () =>
      getDisplayedRequests(store.getState()).length ==
        expectedNoOfRequestsAfterResend &&
      getSelectedRequest(store.getState()).id !== originalResourceId
  );

  info("Open the context menu and select the resend for the request");
  EventUtils.sendMouseEvent({ type: "contextmenu" }, originalRequestItem);
  await selectContextMenuItem(monitor, "request-list-context-resend-only");
  await waitForNewRequest;

  const newResourceId = getSelectedRequest(store.getState()).id;

  // Make sure we fetch the request headers and post data for the
  // new request so we can assert them.
  await connector.requestData(newResourceId, "requestHeaders");
  await connector.requestData(newResourceId, "requestPostData");

  return {
    originalResource: getRequestById(store.getState(), originalResourceId),
    newResource: getRequestById(store.getState(), newResourceId),
  };
}

// This is a basic test for the old edit and resend panel
// This should be removed soon in Bug 1745416 when we remove
// the old panel functionality.
async function testOldEditAndResendPanel() {
  const ADD_QUERY = "t1=t2";
  const ADD_HEADER = "Test-header: true";
  const ADD_UA_HEADER = "User-Agent: Custom-Agent";
  const ADD_POSTDATA = "&t3=t4";

  const { tab, monitor } = await initNetMonitor(POST_DATA_URL, {
    requestCount: 1,
  });
  info("Starting test... ");

  const { document, store, windowRequire } = monitor.panelWin;
  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
  const { getSelectedRequest, getSortedRequests } = windowRequire(
    "devtools/client/netmonitor/src/selectors/index"
  );

  store.dispatch(Actions.batchEnable(false));

  // Execute requests.
  await performRequests(monitor, tab, 2);

  const origItemId = getSortedRequests(store.getState())[0].id;

  store.dispatch(Actions.selectRequest(origItemId));
  await waitForRequestData(
    store,
    ["requestHeaders", "requestPostData"],
    origItemId
  );

  let origItem = getSortedRequests(store.getState())[0];

  // add a new custom request cloned from selected request

  store.dispatch(Actions.cloneSelectedRequest());
  await testCustomForm(origItem);

  let customItem = getSelectedRequest(store.getState());
  testCustomItem(customItem, origItem);

  // edit the custom request
  await editCustomForm();

  // FIXME: reread the customItem, it's been replaced by a new object (immutable!)
  customItem = getSelectedRequest(store.getState());
  testCustomItemChanged(customItem, origItem);

  // send the new request
  const wait = waitForNetworkEvents(monitor, 1);
  store.dispatch(Actions.sendCustomRequest());
  await wait;

  let sentItem;
  // Testing sent request will require updated requestHeaders and requestPostData,
  // we must wait for both properties get updated before starting test.
  await waitUntil(() => {
    sentItem = getSelectedRequest(store.getState());
    origItem = getSortedRequests(store.getState())[0];
    return (
      sentItem &&
      sentItem.requestHeaders &&
      sentItem.requestPostData &&
      origItem &&
      origItem.requestHeaders &&
      origItem.requestPostData
    );
  });

  await testSentRequest(sentItem, origItem);

  // Ensure the UI shows the new request, selected, and that the detail panel was closed.
  is(
    getSortedRequests(store.getState()).length,
    3,
    "There are 3 requests shown"
  );
  is(
    document
      .querySelector(".request-list-item.selected")
      .getAttribute("data-id"),
    sentItem.id,
    "The sent request is selected"
  );
  is(
    document.querySelector(".network-details-bar"),
    null,
    "The detail panel is hidden"
  );

  await teardown(monitor);

  function testCustomItem(item, orig) {
    is(
      item.method,
      orig.method,
      "item is showing the same method as original request"
    );
    is(item.url, orig.url, "item is showing the same URL as original request");
  }

  function testCustomItemChanged(item, orig) {
    const { url } = item;
    const expectedUrl = orig.url + "&" + ADD_QUERY;

    is(url, expectedUrl, "menu item is updated to reflect url entered in form");
  }

  /*
   * Test that the New Request form was populated correctly
   */
  async function testCustomForm(data) {
    await waitUntil(() => document.querySelector(".custom-request-panel"));
    is(
      document.getElementById("custom-method-value").value,
      data.method,
      "new request form showing correct method"
    );

    is(
      document.getElementById("custom-url-value").value,
      data.url,
      "new request form showing correct url"
    );

    const query = document.getElementById("custom-query-value");
    is(
      query.value,
      "foo=bar\nbaz=42\ntype=urlencoded",
      "new request form showing correct query string"
    );

    const headers = document
      .getElementById("custom-headers-value")
      .value.split("\n");
    for (const { name, value } of data.requestHeaders.headers) {
      ok(
        headers.includes(name + ": " + value),
        "form contains header from request"
      );
    }

    const postData = document.getElementById("custom-postdata-value");
    is(
      postData.value,
      data.requestPostData.postData.text,
      "new request form showing correct post data"
    );
  }

  /*
   * Add some params and headers to the request form
   */
  async function editCustomForm() {
    monitor.panelWin.focus();

    const query = document.getElementById("custom-query-value");
    const queryFocus = once(query, "focus", false);
    // Bug 1195825: Due to some unexplained dark-matter with promise,
    // focus only works if delayed by one tick.
    query.setSelectionRange(query.value.length, query.value.length);
    executeSoon(() => query.focus());
    await queryFocus;

    // add params to url query string field
    typeInNetmonitor(["VK_RETURN"], monitor);
    typeInNetmonitor(ADD_QUERY, monitor);

    const headers = document.getElementById("custom-headers-value");
    const headersFocus = once(headers, "focus", false);
    headers.setSelectionRange(headers.value.length, headers.value.length);
    headers.focus();
    await headersFocus;

    // add a header
    typeInNetmonitor(["VK_RETURN"], monitor);
    typeInNetmonitor(ADD_HEADER, monitor);

    // add a User-Agent header, to check if default headers can be modified
    // (there will be two of them, first gets overwritten by the second)
    typeInNetmonitor(["VK_RETURN"], monitor);
    typeInNetmonitor(ADD_UA_HEADER, monitor);

    const postData = document.getElementById("custom-postdata-value");
    const postFocus = once(postData, "focus", false);
    postData.setSelectionRange(postData.value.length, postData.value.length);
    postData.focus();
    await postFocus;

    // add to POST data once textarea has updated
    await waitUntil(() => postData.textContent !== "");
    typeInNetmonitor(ADD_POSTDATA, monitor);
  }

  /*
   * Make sure newly created event matches expected request
   */
  async function testSentRequest(data, origData) {
    is(data.method, origData.method, "correct method in sent request");
    is(data.url, origData.url + "&" + ADD_QUERY, "correct url in sent request");

    const { headers } = data.requestHeaders;
    const hasHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_HEADER);
    ok(hasHeader, "new header added to sent request");

    const hasUAHeader = headers.some(
      h => `${h.name}: ${h.value}` == ADD_UA_HEADER
    );
    ok(hasUAHeader, "User-Agent header added to sent request");

    is(
      data.requestPostData.postData.text,
      origData.requestPostData.postData.text + ADD_POSTDATA,
      "post data added to sent request"
    );
  }
}