Source code
Revision control
Copy as Markdown
Other Tools
/* Any copyright is dedicated to the Public Domain.
"use strict";
const {
FILTER_TAGS,
} = require("resource://devtools/client/netmonitor/src/constants.js");
const {
Filters,
} = require("resource://devtools/client/netmonitor/src/utils/filter-predicates.js");
const {
getSizeWithDecimals,
} = require("resource://devtools/client/netmonitor/src/utils/format-utils.js");
const {
responseIsFresh,
} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js");
/**
* Tests that the content rendered in the Statistics Panel is displayed correctly.
*/
add_task(async function () {
const { monitor } = await initNetMonitor(STATISTICS_URL, { requestCount: 1 });
info("Starting test... ");
const panel = monitor.panelWin;
const { document, store, windowRequire, connector } = panel;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
const { getDisplayedRequests } = windowRequire(
"devtools/client/netmonitor/src/selectors/index"
);
ok(
document.querySelector(".monitor-panel"),
"The current main panel is correct."
);
info("Displaying statistics panel");
store.dispatch(Actions.openStatistics(connector, true));
ok(
document.querySelector(".statistics-panel"),
"The current main panel is correct."
);
await waitForAllNetworkUpdateEvents();
info("Waiting for chart to display");
await waitUntil(() => {
return (
document.querySelectorAll(".pie-chart-container:not([placeholder=true])")
.length == 2 &&
document.querySelectorAll(
".table-chart-container:not([placeholder=true])"
).length == 2
);
});
// Wait a bit for the charts and tables to fully render
// This is needed as, unfortunately just waiting rendered elements won't work,
// as they may be rendered but not yet with all the needed information.
await wait(2000);
const allRequests = getDisplayedRequests(store.getState());
// Assert the primed cache data
const expectedPrimedCacheData = generateExpectedData(allRequests, false);
await assertChartContent(
document,
"Primed Cache",
"primed-cache-chart",
expectedPrimedCacheData
);
// Assert the empty cache data
const expectedEmptyCacheData = generateExpectedData(allRequests, true);
await assertChartContent(
document,
"Empty Cache",
"empty-cache-chart",
expectedEmptyCacheData
);
await teardown(monitor);
});
// The expected data is generated based of the list of displayed requests
function generateExpectedData(requests, isForEmptyCache) {
const expectedData = { slices: [] };
const requestsGroupedAsTypes = {};
// Group the request based on the types
requests.forEach(request => {
// If non of the filter types are matched, default to "others"
let dataType = 8;
for (const type of FILTER_TAGS) {
if (Filters[type](request)) {
dataType = type;
}
if (Filters.xhr(request)) {
// Verify XHR last, to categorize other mime types in their own blobs.
// "xhr"
dataType = "xhr";
}
}
if (!requestsGroupedAsTypes[dataType]) {
requestsGroupedAsTypes[dataType] = [];
}
requestsGroupedAsTypes[dataType].push(request);
});
let totalRequests = 0,
totalSize = 0,
totalTransferredSize = 0,
totalRequestsFromCache = 0;
for (const type in requestsGroupedAsTypes) {
const requestsForType = requestsGroupedAsTypes[type];
if (!requestsForType.length) {
return expectedData;
}
const size = accumulate(requestsForType, "contentSize");
const transferred = accumulate(
requestsForType,
"transferredSize",
request => isForEmptyCache || !responseIsFresh(request)
);
const data = {
pieSize: "",
pieName: type,
count: String(requestsForType.length),
size: `${getSizeWithDecimals(size / 1000)} kB`,
type,
transferred: `${getSizeWithDecimals(transferred / 1000)} kB`,
// temp size data which would be used to calculate the pieSize
// Should be deleted after.
_size: size,
};
totalRequests += requestsForType.length;
totalSize += size;
totalTransferredSize += transferred;
for (const request of requestsForType) {
const isCached = request.fromCache || request.status === "304";
totalRequestsFromCache = isCached
? totalRequestsFromCache + 1
: totalRequestsFromCache;
}
expectedData.slices.push(data);
}
expectedData.totals = {
cachedResponses: isForEmptyCache ? 0 : totalRequestsFromCache,
requests: totalRequests,
size: `${getSizeWithDecimals(totalSize / 1000)} kB`,
transferred: `${getSizeWithDecimals(totalTransferredSize / 1000)} kB`,
};
// Calculate the expected % size of the pies in the chart for each type
// This can only be done after we have calculated the totalSize
for (const slice of expectedData.slices) {
const pieSize = (slice._size * 100) / totalSize;
// No need to apply precision to the pie size if its 0 or 100%
slice.pieSize =
pieSize == 0 || pieSize == 100 ? pieSize : pieSize.toPrecision(4);
delete slice._size;
}
return expectedData;
}
async function assertChartContent(
doc,
name,
chartClassName,
expectedChartData
) {
info(`Assert the displayed data for the ${name} chart`);
const chartRootEl = doc.querySelector(`.${chartClassName}`);
for (const slice of expectedChartData.slices) {
info(`Assert the ${slice.type} pie slice`);
const el = chartRootEl.querySelector(
`svg.pie-chart-container a#${slice.type}-slice`
);
// Slices may not be displayed, if the % are too low
if (el) {
isSimilar(
Math.ceil(el.getAttribute("aria-label").match(/[0-9\.]+/g)[0]),
Math.ceil(slice.pieSize),
1,
`The size of the '${slice.type}' pie slice is correct`
);
const pieChartLabel = el.querySelector(".pie-chart-label");
if (pieChartLabel) {
is(
pieChartLabel.textContent,
slice.pieName,
`The pie name for the '${slice.type}' pie slice is correct.`
);
}
}
info(`Assert the '${slice.type} table content`);
const tableRow = chartRootEl.querySelector(
`div.table-chart-container tr[data-statistic-name="${slice.type}"]`
);
is(
tableRow.querySelector("td[name='count']").innerText,
slice.count,
`The no of ${slice.type} requests is correct`
);
is(
tableRow.querySelector("td[name='label']").innerText,
slice.type,
`The type label of ${slice.type} requests is correct`
);
is(
tableRow.querySelector("td[name='size']").innerText,
`${slice.size}`,
`The size of the ${slice.type} requests correct`
);
is(
tableRow.querySelector("td[name='transferredSize']").innerText,
`${slice.transferred}`,
`The transferred size of the ${slice.type} requests is correct`
);
}
info("Assert the chart totals");
const totalsRootEl = chartRootEl.querySelector(
`.table-chart-container div.table-chart-totals`
);
is(
totalsRootEl.querySelector("span[name='cached']").innerText,
`Cached responses: ${expectedChartData.totals.cachedResponses}`,
`The total no of cached responses is correct`
);
is(
totalsRootEl.querySelector("span[name='count']").innerText,
`Total requests: ${expectedChartData.totals.requests}`,
`The total no of requests is correct`
);
is(
totalsRootEl.querySelector("span[name='size']").innerText,
`Size: ${expectedChartData.totals.size}`,
`The total size of requests is correct`
);
is(
totalsRootEl.querySelector("span[name='transferredSize']").innerText,
`Transferred Size: ${expectedChartData.totals.transferred}`,
`The total transferred size is correct`
);
}
/**
* Sums together a specified field from a list of requests based
* on the condition.
*
* @param {Array} requests
* @param {String} field
* @param {Function} condition
* @returns {number}
*/
function accumulate(requests, field, condition = () => true) {
return requests.reduce((acc, request) => {
if (condition(request)) {
return acc + request[field];
}
return acc;
}, 0);
}
/**
* Assert that two values are equal or different by up to a specific margin
*
* @param {Number} actual
* @param {Number} expected
* @param {Number} margin
* @param {String} comment
*/
function isSimilar(actual, expected, margin, comment) {
Assert.greaterOrEqual(actual, expected - margin, comment);
Assert.lessOrEqual(actual, expected + margin, comment);
}