browser_net_simple-request-details.js (10261B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Tests if requests render correct information in the details UI. 8 */ 9 10 add_task(async function () { 11 const { 12 L10N, 13 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); 14 15 const { monitor } = await initNetMonitor(SIMPLE_SJS, { 16 requestCount: 1, 17 }); 18 info("Starting test... "); 19 20 const { document, store, windowRequire } = monitor.panelWin; 21 const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); 22 const { PANELS } = windowRequire("devtools/client/netmonitor/src/constants"); 23 const { getSelectedRequest, getSortedRequests } = windowRequire( 24 "devtools/client/netmonitor/src/selectors/index" 25 ); 26 27 store.dispatch(Actions.batchEnable(false)); 28 29 const wait = waitForNetworkEvents(monitor, 1); 30 await reloadBrowser(); 31 await wait; 32 33 is( 34 getSelectedRequest(store.getState()), 35 undefined, 36 "There shouldn't be any selected item in the requests menu." 37 ); 38 is( 39 store.getState().requests.requests.length, 40 1, 41 "The requests menu should not be empty after the first request." 42 ); 43 is( 44 !!document.querySelector(".network-details-bar"), 45 false, 46 "The network details panel should still be hidden after first request." 47 ); 48 49 const waitForHeaders = waitForDOM(document, ".headers-overview"); 50 51 store.dispatch(Actions.toggleNetworkDetails()); 52 53 isnot( 54 getSelectedRequest(store.getState()), 55 undefined, 56 "There should be a selected item in the requests menu." 57 ); 58 is( 59 getSelectedIndex(store.getState()), 60 0, 61 "The first item should be selected in the requests menu." 62 ); 63 is( 64 !!document.querySelector(".network-details-bar"), 65 true, 66 "The network details panel should not be hidden after toggle button was pressed." 67 ); 68 69 await waitForHeaders; 70 71 await testHeadersTab(); 72 await testCookiesTab(); 73 await testParamsTab(); 74 await testResponseTab(); 75 await testTimingsTab(); 76 await closePanelOnEsc(); 77 return teardown(monitor); 78 79 function getSelectedIndex(state) { 80 if (!state.requests.selectedId) { 81 return -1; 82 } 83 return getSortedRequests(state).findIndex( 84 r => r.id === state.requests.selectedId 85 ); 86 } 87 88 async function testHeadersTab() { 89 const tabEl = document.querySelectorAll( 90 ".network-details-bar .tabs-menu a" 91 )[0]; 92 const tabpanel = document.querySelector("#headers-panel"); 93 94 is( 95 tabEl.getAttribute("aria-selected"), 96 "true", 97 "The headers tab in the network details pane should be selected." 98 ); 99 // Request URL 100 is( 101 tabpanel.querySelector(".url-preview .url").innerText, 102 SIMPLE_SJS, 103 "The url summary value is incorrect." 104 ); 105 106 // Request method 107 is( 108 tabpanel.querySelectorAll(".treeLabel")[0].innerText, 109 "GET", 110 "The method summary value is incorrect." 111 ); 112 // Status code 113 is( 114 tabpanel.querySelector(".requests-list-status-code").innerText, 115 "200", 116 "The status summary code is incorrect." 117 ); 118 is( 119 tabpanel.querySelector(".status").childNodes[1].textContent, 120 "Och Aye", 121 "The status summary value is incorrect." 122 ); 123 // Version 124 is( 125 tabpanel.querySelectorAll(".tabpanel-summary-value")[1].innerText, 126 "HTTP/1.1", 127 "The HTTP version is incorrect." 128 ); 129 130 await waitForRequestData(store, ["requestHeaders", "responseHeaders"]); 131 132 is( 133 tabpanel.querySelectorAll(".accordion-item").length, 134 2, 135 "There should be 2 header scopes displayed in this tabpanel." 136 ); 137 138 is( 139 tabpanel.querySelectorAll(".accordion .treeLabelCell").length, 140 24, 141 "There should be 24 header values displayed in this tabpanel." 142 ); 143 144 const headersTable = tabpanel.querySelector(".accordion"); 145 const responseScope = headersTable.querySelectorAll( 146 "tr[id^='/responseHeaders']" 147 ); 148 const requestScope = headersTable.querySelectorAll( 149 "tr[id^='/requestHeaders']" 150 ); 151 152 const headerLabels = headersTable.querySelectorAll( 153 ".accordion-item .accordion-header-label" 154 ); 155 156 ok( 157 headerLabels[0].innerHTML.match( 158 new RegExp(L10N.getStr("responseHeaders") + " \\([0-9]+ .+\\)") 159 ), 160 "The response headers scope doesn't have the correct title." 161 ); 162 163 ok( 164 headerLabels[1].innerHTML.includes(L10N.getStr("requestHeaders") + " ("), 165 "The request headers scope doesn't have the correct title." 166 ); 167 168 const responseHeaders = [ 169 { 170 name: "cache-control", 171 value: "no-cache, no-store, must-revalidate", 172 pos: "first", 173 index: 1, 174 }, 175 { 176 name: "connection", 177 value: "close", 178 pos: "second", 179 index: 2, 180 }, 181 { 182 name: "content-length", 183 value: "12", 184 pos: "third", 185 index: 3, 186 }, 187 { 188 name: "content-type", 189 value: "text/plain; charset=utf-8", 190 pos: "fourth", 191 index: 4, 192 }, 193 { 194 name: "foo-bar", 195 value: "baz", 196 pos: "seventh", 197 index: 7, 198 }, 199 ]; 200 responseHeaders.forEach(header => { 201 is( 202 responseScope[header.index - 1].querySelector(".treeLabel").innerHTML, 203 header.name, 204 `The ${header.pos} response header name was incorrect.` 205 ); 206 is( 207 responseScope[header.index - 1].querySelector(".objectBox").innerHTML, 208 `${header.value}`, 209 `The ${header.pos} response header value was incorrect.` 210 ); 211 }); 212 213 const requestHeaders = [ 214 { 215 name: "Cache-Control", 216 value: "no-cache", 217 pos: "fourth", 218 index: 4, 219 }, 220 { 221 name: "Connection", 222 value: "keep-alive", 223 pos: "fifth", 224 index: 5, 225 }, 226 { 227 name: "Host", 228 value: "example.com", 229 pos: "seventh", 230 index: 7, 231 }, 232 { 233 name: "Pragma", 234 value: "no-cache", 235 pos: "eighth", 236 index: 8, 237 }, 238 ]; 239 requestHeaders.forEach(header => { 240 is( 241 requestScope[header.index - 1].querySelector(".treeLabel").innerHTML, 242 header.name, 243 `The ${header.pos} request header name was incorrect.` 244 ); 245 is( 246 requestScope[header.index - 1].querySelector(".objectBox").innerHTML, 247 `${header.value}`, 248 `The ${header.pos} request header value was incorrect.` 249 ); 250 }); 251 } 252 253 async function testCookiesTab() { 254 const tabpanel = await selectTab(PANELS.COOKIES, 1); 255 256 const cookieAccordion = tabpanel.querySelector(".accordion"); 257 258 is( 259 cookieAccordion.querySelectorAll(".accordion-item").length, 260 2, 261 "There should be 2 cookie scopes displayed in this tabpanel." 262 ); 263 // 2 Cookies in response - 1 httpOnly and 1 value for each cookie - total 6 264 265 const resCookiesTable = cookieAccordion.querySelector( 266 "li[id='responseCookies'] .accordion-content .treeTable" 267 ); 268 is( 269 resCookiesTable.querySelectorAll("tr.treeRow").length, 270 6, 271 "There should be 6 rows displayed in response cookies table" 272 ); 273 274 const reqCookiesTable = cookieAccordion.querySelector( 275 "li[id='requestCookies'] .accordion-content .treeTable" 276 ); 277 is( 278 reqCookiesTable.querySelectorAll("tr.treeRow").length, 279 2, 280 "There should be 2 cookie values displayed in request cookies table." 281 ); 282 } 283 284 async function testParamsTab() { 285 const tabpanel = await selectTab(PANELS.REQUEST, 2); 286 287 is( 288 tabpanel.querySelectorAll(".panel-container").length, 289 0, 290 "There should be no param scopes displayed in this tabpanel." 291 ); 292 is( 293 tabpanel.querySelectorAll(".empty-notice").length, 294 1, 295 "The empty notice should be displayed in this tabpanel." 296 ); 297 } 298 299 async function testResponseTab() { 300 const tabpanel = await selectTab(PANELS.RESPONSE, 3); 301 await waitForDOM(document, "#response-panel .source-editor-mount"); 302 303 is( 304 tabpanel.querySelectorAll( 305 "#response-panel .raw-data-toggle-input .devtools-checkbox-toggle" 306 ).length, 307 0, 308 "The raw data toggle should not be shown in this tabpanel." 309 ); 310 is( 311 tabpanel.querySelectorAll(".source-editor-mount").length, 312 1, 313 "The response payload should be shown initially." 314 ); 315 } 316 317 async function testTimingsTab() { 318 const tabpanel = await selectTab(PANELS.TIMINGS, 4); 319 320 const displayFormat = new RegExp(/[0-9]+ ms$/); 321 const propsToVerify = [ 322 "blocked", 323 "dns", 324 "connect", 325 "ssl", 326 "send", 327 "wait", 328 "receive", 329 ]; 330 331 // To ensure that test case for a new property is written, otherwise this 332 // test will fail 333 is( 334 tabpanel.querySelectorAll(".tabpanel-summary-container").length, 335 propsToVerify.length, 336 `There should be exactly ${propsToVerify.length} values 337 displayed in this tabpanel` 338 ); 339 340 propsToVerify.forEach(propName => { 341 ok( 342 tabpanel 343 .querySelector( 344 `#timings-summary-${propName} 345 .requests-list-timings-total` 346 ) 347 .innerHTML.match(displayFormat), 348 `The ${propName} timing info does not appear to be correct.` 349 ); 350 }); 351 } 352 353 async function selectTab(tabName, pos) { 354 const tabEl = document.querySelectorAll( 355 ".network-details-bar .tabs-menu a" 356 )[pos]; 357 358 const onPanelOpen = waitForDOM(document, `#${tabName}-panel`); 359 clickOnSidebarTab( 360 document, 361 tabEl.id.substring(0, tabEl.id.indexOf("-tab")) 362 ); 363 await onPanelOpen; 364 365 is( 366 tabEl.getAttribute("aria-selected"), 367 "true", 368 `The ${tabName} tab in the network details pane should be selected.` 369 ); 370 371 return document.querySelector(".network-details-bar .tab-panel"); 372 } 373 374 // This test will timeout on failure 375 async function closePanelOnEsc() { 376 EventUtils.sendKey("ESCAPE", window); 377 378 await waitUntil(() => { 379 return document.querySelector(".network-details-bar") == null; 380 }); 381 382 is( 383 document.querySelectorAll(".network-details-bar").length, 384 0, 385 "Network details panel should close on ESC key" 386 ); 387 } 388 });