V10.437 harden sales chart rendering
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s
This commit is contained in:
@@ -122,30 +122,47 @@ function safeName(input) {
|
||||
|
||||
async function collectChartMetrics(page, contract) {
|
||||
return page.evaluate(({ expectedCanvases, readyDataset }) => {
|
||||
function countInkPixels(canvas) {
|
||||
function countCanvasPixels(canvas) {
|
||||
const context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
if (!context || !canvas.width || !canvas.height) return 0;
|
||||
if (!context || !canvas.width || !canvas.height) return { inkPixels: 0, colorPixels: 0 };
|
||||
|
||||
const sampleWidth = Math.min(canvas.width, 900);
|
||||
const sampleHeight = Math.min(canvas.height, 520);
|
||||
const pixels = context.getImageData(0, 0, sampleWidth, sampleHeight).data;
|
||||
let ink = 0;
|
||||
let color = 0;
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const alpha = pixels[i + 3];
|
||||
if (alpha > 10) {
|
||||
ink += 1;
|
||||
if (ink > 250) break;
|
||||
const r = pixels[i];
|
||||
const g = pixels[i + 1];
|
||||
const b = pixels[i + 2];
|
||||
const channelSpread = Math.max(r, g, b) - Math.min(r, g, b);
|
||||
if (alpha > 24 && channelSpread > 18) color += 1;
|
||||
}
|
||||
}
|
||||
return ink;
|
||||
return { inkPixels: ink, colorPixels: color };
|
||||
}
|
||||
|
||||
function datasetPointCount(chart) {
|
||||
if (!chart || !chart.data || !Array.isArray(chart.data.datasets)) return 0;
|
||||
return chart.data.datasets.reduce((total, dataset) => {
|
||||
function datasetStats(chart) {
|
||||
if (!chart || !chart.data || !Array.isArray(chart.data.datasets)) {
|
||||
return { dataPoints: 0, nonZeroPoints: 0, min: null, max: null };
|
||||
}
|
||||
const values = [];
|
||||
chart.data.datasets.forEach((dataset) => {
|
||||
const data = Array.isArray(dataset.data) ? dataset.data : [];
|
||||
return total + data.filter((value) => value !== null && value !== undefined && Number.isFinite(Number(value))).length;
|
||||
}, 0);
|
||||
data.forEach((value) => {
|
||||
const number = Number(value);
|
||||
if (value !== null && value !== undefined && Number.isFinite(number)) values.push(number);
|
||||
});
|
||||
});
|
||||
return {
|
||||
dataPoints: values.length,
|
||||
nonZeroPoints: values.filter(value => Math.abs(value) > 1e-9).length,
|
||||
min: values.length ? Math.min(...values) : null,
|
||||
max: values.length ? Math.max(...values) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function visibleElementCount(chart) {
|
||||
@@ -172,6 +189,8 @@ async function collectChartMetrics(page, contract) {
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const chart = chartGetter(canvas);
|
||||
const pixels = countCanvasPixels(canvas);
|
||||
const stats = datasetStats(chart);
|
||||
return {
|
||||
id,
|
||||
exists: true,
|
||||
@@ -179,10 +198,14 @@ async function collectChartMetrics(page, contract) {
|
||||
cssHeight: Math.round(rect.height),
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
inkPixels: countInkPixels(canvas),
|
||||
inkPixels: pixels.inkPixels,
|
||||
colorPixels: pixels.colorPixels,
|
||||
hasChart: Boolean(chart),
|
||||
datasetCount: chart && chart.data && Array.isArray(chart.data.datasets) ? chart.data.datasets.length : 0,
|
||||
dataPoints: datasetPointCount(chart),
|
||||
dataPoints: stats.dataPoints,
|
||||
nonZeroPoints: stats.nonZeroPoints,
|
||||
min: stats.min,
|
||||
max: stats.max,
|
||||
visibleElements: visibleElementCount(chart),
|
||||
ariaHidden: canvas.getAttribute('aria-hidden') || '',
|
||||
};
|
||||
@@ -217,8 +240,12 @@ function evaluateMetrics(route, status, metrics, consoleErrors) {
|
||||
if (item.datasetCount <= 0 || item.dataPoints <= 0) {
|
||||
failures.push(`${item.id} has no drawable dataset`);
|
||||
}
|
||||
if (item.nonZeroPoints <= 0) {
|
||||
failures.push(`${item.id} has no non-zero dataset values`);
|
||||
}
|
||||
if (item.visibleElements <= 0) failures.push(`${item.id} has no visible chart elements`);
|
||||
if (item.inkPixels <= 250) failures.push(`${item.id} canvas appears blank`);
|
||||
if (item.inkPixels <= 900) failures.push(`${item.id} canvas appears blank`);
|
||||
if (item.colorPixels <= 40) failures.push(`${item.id} has no colored chart marks`);
|
||||
}
|
||||
for (const err of consoleErrors) {
|
||||
failures.push(`console ${err}`);
|
||||
@@ -301,7 +328,7 @@ async function main() {
|
||||
} else {
|
||||
for (const result of results) {
|
||||
const canvasSummary = result.metrics.canvases
|
||||
.map((item) => `${item.id}:chart=${item.hasChart ? 'yes' : 'no'},points=${item.dataPoints || 0},ink=${item.inkPixels || 0}`)
|
||||
.map((item) => `${item.id}:chart=${item.hasChart ? 'yes' : 'no'},points=${item.dataPoints || 0},nonzero=${item.nonZeroPoints || 0},ink=${item.inkPixels || 0},color=${item.colorPixels || 0}`)
|
||||
.join(' ');
|
||||
console.log(`${result.passed ? 'PASS' : 'FAIL'} ${result.route} status=${result.status} ${canvasSummary}`);
|
||||
for (const failure of result.failures) {
|
||||
|
||||
Reference in New Issue
Block a user