browser_net_statistics-content.js (8689B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { 7 FILTER_TAGS, 8 } = require("resource://devtools/client/netmonitor/src/constants.js"); 9 const { 10 Filters, 11 } = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js"); 12 const { 13 getSizeWithDecimals, 14 } = require("resource://devtools/client/netmonitor/src/utils/format-utils.js"); 15 const { 16 responseIsFresh, 17 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 18 /** 19 * Tests that the content rendered in the Statistics Panel is displayed correctly. 20 */ 21 add_task(async function () { 22 const { monitor } = await initNetMonitor(STATISTICS_URL, { requestCount: 1 }); 23 info("Starting test... "); 24 25 const panel = monitor.panelWin; 26 const { document, store, windowRequire, connector } = panel; 27 const Actions = windowRequire("devtools/client/netmonitor/src/actions/index"); 28 const { getDisplayedRequests } = windowRequire( 29 "devtools/client/netmonitor/src/selectors/index" 30 ); 31 32 ok( 33 document.querySelector(".monitor-panel"), 34 "The current main panel is correct." 35 ); 36 37 info("Displaying statistics panel"); 38 store.dispatch(Actions.openStatistics(connector, true)); 39 40 ok( 41 document.querySelector(".statistics-panel"), 42 "The current main panel is correct." 43 ); 44 45 await waitForAllNetworkUpdateEvents(); 46 47 info("Waiting for chart to display"); 48 await waitUntil(() => { 49 return ( 50 document.querySelectorAll(".pie-chart-container:not([placeholder=true])") 51 .length == 2 && 52 document.querySelectorAll( 53 ".table-chart-container:not([placeholder=true])" 54 ).length == 2 55 ); 56 }); 57 58 // Wait a bit for the charts and tables to fully render 59 // This is needed as, unfortunately just waiting rendered elements won't work, 60 // as they may be rendered but not yet with all the needed information. 61 await wait(2000); 62 63 const allRequests = getDisplayedRequests(store.getState()); 64 // Assert the primed cache data 65 const expectedPrimedCacheData = generateExpectedData(allRequests, false); 66 await assertChartContent( 67 document, 68 "Primed Cache", 69 "primed-cache-chart", 70 expectedPrimedCacheData 71 ); 72 73 // Assert the empty cache data 74 const expectedEmptyCacheData = generateExpectedData(allRequests, true); 75 await assertChartContent( 76 document, 77 "Empty Cache", 78 "empty-cache-chart", 79 expectedEmptyCacheData 80 ); 81 82 await teardown(monitor); 83 }); 84 85 // The expected data is generated based of the list of displayed requests 86 function generateExpectedData(requests, isForEmptyCache) { 87 const expectedData = { slices: [] }; 88 const requestsGroupedAsTypes = {}; 89 // Group the request based on the types 90 requests.forEach(request => { 91 // If non of the filter types are matched, default to "others" 92 let dataType = 8; 93 for (const type of FILTER_TAGS) { 94 if (Filters[type](request)) { 95 dataType = type; 96 } 97 if (Filters.xhr(request)) { 98 // Verify XHR last, to categorize other mime types in their own blobs. 99 // "xhr" 100 dataType = "xhr"; 101 } 102 } 103 if (!requestsGroupedAsTypes[dataType]) { 104 requestsGroupedAsTypes[dataType] = []; 105 } 106 requestsGroupedAsTypes[dataType].push(request); 107 }); 108 109 let totalRequests = 0, 110 totalSize = 0, 111 totalTransferredSize = 0, 112 totalRequestsFromCache = 0; 113 for (const type in requestsGroupedAsTypes) { 114 const requestsForType = requestsGroupedAsTypes[type]; 115 if (!requestsForType.length) { 116 return expectedData; 117 } 118 119 const size = accumulate(requestsForType, "contentSize"); 120 const transferred = accumulate( 121 requestsForType, 122 "transferredSize", 123 request => isForEmptyCache || !responseIsFresh(request) 124 ); 125 126 const data = { 127 pieSize: "", 128 pieName: type, 129 count: String(requestsForType.length), 130 size: `${getSizeWithDecimals(size / 1000)} kB`, 131 type, 132 transferred: `${getSizeWithDecimals(transferred / 1000)} kB`, 133 // temp size data which would be used to calculate the pieSize 134 // Should be deleted after. 135 _size: size, 136 }; 137 138 totalRequests += requestsForType.length; 139 totalSize += size; 140 totalTransferredSize += transferred; 141 for (const request of requestsForType) { 142 const isCached = request.fromCache || request.status === "304"; 143 totalRequestsFromCache = isCached 144 ? totalRequestsFromCache + 1 145 : totalRequestsFromCache; 146 } 147 expectedData.slices.push(data); 148 } 149 150 expectedData.totals = { 151 cachedResponses: isForEmptyCache ? 0 : totalRequestsFromCache, 152 requests: totalRequests, 153 size: `${getSizeWithDecimals(totalSize / 1000)} kB`, 154 transferred: `${getSizeWithDecimals(totalTransferredSize / 1000)} kB`, 155 }; 156 157 // Calculate the expected % size of the pies in the chart for each type 158 // This can only be done after we have calculated the totalSize 159 for (const slice of expectedData.slices) { 160 const pieSize = (slice._size * 100) / totalSize; 161 // No need to apply precision to the pie size if its 0 or 100% 162 slice.pieSize = 163 pieSize == 0 || pieSize == 100 ? pieSize : pieSize.toPrecision(4); 164 delete slice._size; 165 } 166 return expectedData; 167 } 168 169 async function assertChartContent( 170 doc, 171 name, 172 chartClassName, 173 expectedChartData 174 ) { 175 info(`Assert the displayed data for the ${name} chart`); 176 const chartRootEl = doc.querySelector(`.${chartClassName}`); 177 for (const slice of expectedChartData.slices) { 178 info(`Assert the ${slice.type} pie slice`); 179 const el = chartRootEl.querySelector( 180 `svg.pie-chart-container a#${slice.type}-slice` 181 ); 182 // Slices may not be displayed, if the % are too low 183 if (el) { 184 isSimilar( 185 Math.ceil(el.getAttribute("aria-label").match(/[0-9\.]+/g)[0]), 186 Math.ceil(slice.pieSize), 187 1, 188 `The size of the '${slice.type}' pie slice is correct` 189 ); 190 const pieChartLabel = el.querySelector(".pie-chart-label"); 191 if (pieChartLabel) { 192 is( 193 pieChartLabel.textContent, 194 slice.pieName, 195 `The pie name for the '${slice.type}' pie slice is correct.` 196 ); 197 } 198 } 199 200 info(`Assert the '${slice.type} table content`); 201 const tableRow = chartRootEl.querySelector( 202 `div.table-chart-container tr[data-statistic-name="${slice.type}"]` 203 ); 204 is( 205 tableRow.querySelector("td[name='count']").innerText, 206 slice.count, 207 `The no of ${slice.type} requests is correct` 208 ); 209 is( 210 tableRow.querySelector("td[name='label']").innerText, 211 slice.type, 212 `The type label of ${slice.type} requests is correct` 213 ); 214 is( 215 tableRow.querySelector("td[name='size']").innerText, 216 `${slice.size}`, 217 `The size of the ${slice.type} requests correct` 218 ); 219 is( 220 tableRow.querySelector("td[name='transferredSize']").innerText, 221 `${slice.transferred}`, 222 `The transferred size of the ${slice.type} requests is correct` 223 ); 224 } 225 226 info("Assert the chart totals"); 227 const totalsRootEl = chartRootEl.querySelector( 228 `.table-chart-container div.table-chart-totals` 229 ); 230 is( 231 totalsRootEl.querySelector("span[name='cached']").innerText, 232 `Cached responses: ${expectedChartData.totals.cachedResponses}`, 233 `The total no of cached responses is correct` 234 ); 235 is( 236 totalsRootEl.querySelector("span[name='count']").innerText, 237 `Total requests: ${expectedChartData.totals.requests}`, 238 `The total no of requests is correct` 239 ); 240 is( 241 totalsRootEl.querySelector("span[name='size']").innerText, 242 `Size: ${expectedChartData.totals.size}`, 243 `The total size of requests is correct` 244 ); 245 is( 246 totalsRootEl.querySelector("span[name='transferredSize']").innerText, 247 `Transferred Size: ${expectedChartData.totals.transferred}`, 248 `The total transferred size is correct` 249 ); 250 } 251 252 /** 253 * Sums together a specified field from a list of requests based 254 * on the condition. 255 * 256 * @param {Array} requests 257 * @param {string} field 258 * @param {Function} condition 259 * @returns {number} 260 */ 261 function accumulate(requests, field, condition = () => true) { 262 return requests.reduce((acc, request) => { 263 if (condition(request)) { 264 return acc + request[field]; 265 } 266 return acc; 267 }, 0); 268 } 269 /** 270 * Assert that two values are equal or different by up to a specific margin 271 * 272 * @param {number} actual 273 * @param {number} expected 274 * @param {number} margin 275 * @param {string} comment 276 */ 277 function isSimilar(actual, expected, margin, comment) { 278 Assert.greaterOrEqual(actual, expected - margin, comment); 279 Assert.lessOrEqual(actual, expected + margin, comment); 280 }