From 3dd5139ea3d89e3dc8f867af3bc218cc3e6fe7f8 Mon Sep 17 00:00:00 2001 From: lovasoa Date: Sat, 24 Oct 2026 22:32:30 +0200 Subject: [PATCH] Move detailed chart checks to minimal SQL fixtures --- .../fixtures/chart/hidden-legend.sql | 2 + .../chart/horizontal-reference-lines.sql | 4 + .../fixtures/chart/stacked-time-series.sql | 2 +- tests/end-to-end/fixtures/chart/test.ts | 57 ++++++- .../fixtures/chart/x-reference-band.sql | 4 + .../fixtures/chart/y-reference-band.sql | 4 + tests/end-to-end/official-site.spec.ts | 149 +++--------------- 7 files changed, 96 insertions(+), 126 deletions(-) create mode 100644 tests/end-to-end/fixtures/chart/hidden-legend.sql create mode 100644 tests/end-to-end/fixtures/chart/horizontal-reference-lines.sql create mode 100644 tests/end-to-end/fixtures/chart/x-reference-band.sql create mode 100644 tests/end-to-end/fixtures/chart/y-reference-band.sql diff --git a/tests/end-to-end/fixtures/chart/hidden-legend.sql b/tests/end-to-end/fixtures/chart/hidden-legend.sql new file mode 100644 index 000000000..8e554246d --- /dev/null +++ b/tests/end-to-end/fixtures/chart/hidden-legend.sql @@ -0,0 +1,2 @@ +SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'bar' AS type, TRUE AS stacked, FALSE AS show_legend; +WITH points(series, x, y) AS (VALUES ('CPU', 'Mon', 10), ('CPU', 'Tue', 20), ('GPU', 'Mon', 5), ('GPU', 'Tue', 15)) SELECT * FROM points; diff --git a/tests/end-to-end/fixtures/chart/horizontal-reference-lines.sql b/tests/end-to-end/fixtures/chart/horizontal-reference-lines.sql new file mode 100644 index 000000000..eaec2b289 --- /dev/null +++ b/tests/end-to-end/fixtures/chart/horizontal-reference-lines.sql @@ -0,0 +1,4 @@ +SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'bar' AS type, TRUE AS horizontal, 100 AS ymax; +SELECT 90 AS yline, 'full' AS label; +SELECT 'db-1' AS xline, 'watched' AS label; +WITH points(x, y) AS (VALUES ('backup-1', 41), ('db-1', 88)) SELECT * FROM points; diff --git a/tests/end-to-end/fixtures/chart/stacked-time-series.sql b/tests/end-to-end/fixtures/chart/stacked-time-series.sql index 3f83a6176..bb9870975 100644 --- a/tests/end-to-end/fixtures/chart/stacked-time-series.sql +++ b/tests/end-to-end/fixtures/chart/stacked-time-series.sql @@ -1,2 +1,2 @@ SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'area' AS type, TRUE AS stacked, TRUE AS time, 4 AS marker; -WITH points(series, x, y) AS (VALUES ('CPU', '2024-01-01T00:00:00Z', 10), ('CPU', '2024-01-01T00:01:00Z', 20), ('CPU', '2024-01-01T00:02:00Z', 30), ('CPU', '2024-01-01T00:03:00Z', 40), ('GPU', '2024-01-01T00:01:00Z', 50), ('GPU', '2024-01-01T00:02:00Z', 50), ('GPU', '2024-01-01T00:03:00Z', 50)) SELECT * FROM points; +WITH points(series, x, y) AS (VALUES ('CPU', '2024-01-01T00:00:00Z', 10), ('CPU', '2024-01-01T00:01:00Z', 20), ('CPU', '2024-01-01T00:02:00Z', 30), ('CPU', '2024-01-01T00:03:00Z', 40), ('CPU', '2024-01-01T00:04:00Z', 30), ('GPU', '2024-01-01T00:01:00Z', 50), ('GPU', '2024-01-01T00:02:00Z', 50), ('GPU', '2024-01-01T00:03:00Z', 50)) SELECT * FROM points; diff --git a/tests/end-to-end/fixtures/chart/test.ts b/tests/end-to-end/fixtures/chart/test.ts index 9b41ba0c8..939d548dd 100644 --- a/tests/end-to-end/fixtures/chart/test.ts +++ b/tests/end-to-end/fixtures/chart/test.ts @@ -308,6 +308,7 @@ test("gives a stacked series a zero at every x it did not measure", async ({ ["2024-01-01T00:01:00.000Z", 50], ["2024-01-01T00:02:00.000Z", 50], ["2024-01-01T00:03:00.000Z", 50], + ["2024-01-01T00:04:00.000Z", 0], ]); }); @@ -316,8 +317,13 @@ test("stacks a series above the one it shares an x with", async ({ page }) => { const [cpu, gpu] = chart.drawnPerSeries; assert.ok(cpu && gpu, "Both series were drawn"); - expect(gpu.heights).toHaveLength(4); - expect(gpu.heights[0]).toBe(cpu.heights[0]); + expect(cpu.lefts).toHaveLength(5); + expect(gpu.lefts).toEqual(cpu.lefts); + expect(gpu.heights).toHaveLength(5); + expect([gpu.heights[0], gpu.heights[4]]).toEqual([ + cpu.heights[0], + cpu.heights[4], + ]); const cpuHeight = cpu.heights[1]; assert.ok(cpuHeight !== undefined, "CPU has a second point"); expect(gpu.heights[1]).toBeLessThan(cpuHeight); @@ -507,6 +513,53 @@ test("gives every slice of a pie chart its label and its share", async ({ expect(chart.dataLabels).toEqual(["Yes: 65%", "No: 35%"]); }); +test("shows series legends by default and hides them when requested", async ({ + page, +}) => { + await renderChart(page, "stacked-time-series"); + await expect(page.locator("#test-chart .apexcharts-legend-text")).toHaveText([ + "CPU", + "GPU", + ]); + + await renderChart(page, "hidden-legend"); + await expect(page.locator("#test-chart .apexcharts-legend")).toBeHidden(); +}); + +for (const { axis, fixture, labels } of [ + { axis: "y", fixture: "y-reference-band", labels: ["target", "throttling"] }, + { axis: "x", fixture: "x-reference-band", labels: ["deploy", "incident"] }, +]) { + test(`draws a ${axis} reference line and band with their labels`, async ({ + page, + }) => { + await renderChart(page, fixture); + const annotations = page.locator( + `#test-chart .apexcharts-${axis}axis-annotations`, + ); + await expect(annotations.locator("line")).toHaveCount(1); + await expect( + annotations.locator(".apexcharts-annotation-rect"), + ).toHaveCount(1); + for (const label of labels) { + await expect(annotations.getByText(label, { exact: true })).toBeVisible(); + } + }); +} + +test("horizontal bars keep reference lines on their data axes", async ({ + page, +}) => { + await renderChart(page, "horizontal-reference-lines"); + const down = page.locator("#test-chart .apexcharts-xaxis-annotations"); + const across = page.locator("#test-chart .apexcharts-yaxis-annotations"); + + await expect(down.locator("line")).toHaveCount(1); + await expect(down.getByText("full", { exact: true })).toBeVisible(); + await expect(across.locator("line")).toHaveCount(1); + await expect(across.getByText("watched", { exact: true })).toBeVisible(); +}); + test("draws a reference line that carries no label", async ({ page }) => { const chart = await renderChart(page, "unlabeled-reference-lines"); diff --git a/tests/end-to-end/fixtures/chart/x-reference-band.sql b/tests/end-to-end/fixtures/chart/x-reference-band.sql new file mode 100644 index 000000000..8d743f2c3 --- /dev/null +++ b/tests/end-to-end/fixtures/chart/x-reference-band.sql @@ -0,0 +1,4 @@ +SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'area' AS type, TRUE AS time; +SELECT '2024-05-01T10:00:00Z' AS xline, 'deploy' AS label; +SELECT '2024-05-01T11:30:00Z' AS xline, '2024-05-01T13:00:00Z' AS xline_end, 'incident' AS label; +WITH points(x, y) AS (VALUES ('2024-05-01T08:00:00Z', 120), ('2024-05-01T12:00:00Z', 512), ('2024-05-01T15:00:00Z', 133)) SELECT * FROM points; diff --git a/tests/end-to-end/fixtures/chart/y-reference-band.sql b/tests/end-to-end/fixtures/chart/y-reference-band.sql new file mode 100644 index 000000000..ec4a94137 --- /dev/null +++ b/tests/end-to-end/fixtures/chart/y-reference-band.sql @@ -0,0 +1,4 @@ +SELECT 'chart' AS component, 'test-chart' AS id, 'Chart test fixture' AS title, 'line' AS type, TRUE AS time, 100 AS ymax; +SELECT 70 AS yline, 'target' AS label; +SELECT 90 AS yline, 100 AS yline_end, 'throttling' AS label; +WITH points(x, y) AS (VALUES ('2024-05-01T08:00:00Z', 52), ('2024-05-01T11:00:00Z', 83), ('2024-05-01T14:00:00Z', 63)) SELECT * FROM points; diff --git a/tests/end-to-end/official-site.spec.ts b/tests/end-to-end/official-site.spec.ts index 20d2554f3..3210ae48e 100644 --- a/tests/end-to-end/official-site.spec.ts +++ b/tests/end-to-end/official-site.spec.ts @@ -1,4 +1,4 @@ -import { expect, type Locator, type Page, test } from "@playwright/test"; +import { expect, type Page, test } from "@playwright/test"; import { checkModal, checkTableFiltering, @@ -20,131 +20,38 @@ test("Open documentation", async ({ page }) => { } }); -async function openChartDocumentation(page: Page) { - await page.goto("/documentation.sql?component=chart#component"); - // This page initializes every documented chart, which can take several seconds. - await expect(page.locator('[data-pre-init="chart"]')).toHaveCount(0, { - timeout: 15_000, - }); -} - -test("chart", async ({ page }) => { - await openChartDocumentation(page); - await expect(page.getByText("Loading...")).toHaveCount(0); - await expect(page.locator(".apexcharts-canvas").first()).toBeVisible(); -}); - -test("chart supports hiding legend", async ({ page }) => { - await openChartDocumentation(page); - - const expensesChart = page.locator(".card", { - has: page.getByRole("heading", { name: "Expenses" }), - }); - - await expect(expensesChart.locator(".apexcharts-canvas")).toBeVisible(); - await expect(expensesChart.locator(".apexcharts-legend")).toBeHidden(); -}); - -const DATA_POINT_MARKERS = ".apexcharts-series-markers > .apexcharts-marker"; - -const chartCard = (page: Page, title: string) => - page.locator(".card", { has: page.getByRole("heading", { name: title }) }); - -const drawnPoints = (card: Locator, series: string) => - card - .locator(`.apexcharts-series[seriesName='${series}'] ${DATA_POINT_MARKERS}`) - .evaluateAll((markers) => - markers.map((m) => ({ - x: m.getAttribute("cx"), - y: m.getAttribute("cy"), - })), - ); - -test("stacked chart draws every series at every x of the chart", async ({ - page, -}) => { - await openChartDocumentation(page); - const powerChart = chartCard(page, "Power draw"); - await expect(powerChart.locator(".apexcharts-canvas")).toBeVisible(); - - const cpu = await drawnPoints(powerChart, "CPU"); - const gpu = await drawnPoints(powerChart, "GPU"); - - expect(cpu).toHaveLength(4); - expect(gpu.map((p) => p.x)).toEqual(cpu.map((p) => p.x)); -}); - -test("stacked chart raises a series only where it has a value", async ({ - page, -}) => { - await openChartDocumentation(page); - const powerChart = chartCard(page, "Power draw"); - await expect(powerChart.locator(".apexcharts-canvas")).toBeVisible(); - - const cpu = await drawnPoints(powerChart, "CPU"); - const gpu = await drawnPoints(powerChart, "GPU"); - - expect([gpu[0], gpu[3]]).toEqual([cpu[0], cpu[3]]); - expect(Number(gpu[1]?.y)).toBeLessThan(Number(cpu[1]?.y)); -}); - -test("chart draws a yline as a line and a yline_end as a band", async ({ +test("chart documentation renders its examples without browser errors", async ({ page, }) => { - await openChartDocumentation(page); - - const temperature = page.locator(".card", { - has: page.getByRole("heading", { name: "CPU temperature" }), + const errors: string[] = []; + page.on("pageerror", (error) => errors.push(error.message)); + page.on("console", (message) => { + if (message.type() === "error") errors.push(message.text()); }); - await expect(temperature.locator(".apexcharts-canvas")).toBeVisible(); - - const annotations = temperature.locator(".apexcharts-yaxis-annotations"); - const lines = annotations.locator("line"); - const bands = annotations.locator(".apexcharts-annotation-rect"); - - await expect(lines).toHaveCount(1); - await expect(bands).toHaveCount(1); - await expect(annotations.getByText("target")).toBeVisible(); - await expect(annotations.getByText("throttling")).toBeVisible(); -}); -test("chart draws an xline as a line and an xline_end as a band", async ({ - page, -}) => { - await openChartDocumentation(page); - - const latency = page.locator(".card", { - has: page.getByRole("heading", { name: "Request latency" }), + await test.step("Open the chart reference", async () => { + await page.goto("/documentation.sql?component=chart#component"); + await expect( + page.getByRole("heading", { name: "The chart component", exact: true }), + ).toBeVisible(); }); - await expect(latency.locator(".apexcharts-canvas")).toBeVisible(); - - const annotations = latency.locator(".apexcharts-xaxis-annotations"); - const lines = annotations.locator("line"); - const bands = annotations.locator(".apexcharts-annotation-rect"); - await expect(lines).toHaveCount(1); - await expect(bands).toHaveCount(1); - await expect(annotations.getByText("deploy")).toBeVisible(); - await expect(annotations.getByText("incident")).toBeVisible(); -}); - -test("horizontal chart draws a yline down it and an xline across it", async ({ - page, -}) => { - await openChartDocumentation(page); - - const disks = page.locator(".card", { - has: page.getByRole("heading", { name: "Disk usage" }), + await test.step("Render every inline and embedded example", async () => { + // Work around Chromium's slow :has() invalidation on this many-chart page. + // https://issues.chromium.org/issues/576187899 + await expect(page.getByText("Loading...")).toHaveCount(0, { + timeout: 15_000, + }); + await expect(page.locator('[data-pre-init="chart"]')).toHaveCount(0); + const examples = page.locator( + '[data-sqlpage-js*="apexcharts."], [data-embed]', + ); + await expect(examples.first()).toBeVisible(); + for (const example of await examples.all()) { + await expect(example.locator(".apexcharts-canvas")).toBeVisible(); + } + expect(errors).toEqual([]); }); - await expect(disks.locator(".apexcharts-canvas")).toBeVisible(); - - const down = disks.locator(".apexcharts-xaxis-annotations"); - const across = disks.locator(".apexcharts-yaxis-annotations"); - - await expect(down.locator("line")).toHaveCount(1); - await expect(down.getByText("full")).toBeVisible(); - await expect(across.locator("line")).toHaveCount(1); - await expect(across.getByText("watched")).toBeVisible(); }); test("map", async ({ page }) => { @@ -254,10 +161,6 @@ test("no console errors on table page", async ({ page }) => { await checkNoConsoleErrors(page, "table"); }); -test("no console errors on chart page", async ({ page }) => { - await checkNoConsoleErrors(page, "chart"); -}); - test("no console errors on map page", async ({ page }) => { await checkNoConsoleErrors(page, "map"); });