Skip to content

Commit

Permalink
chore: use jest & playwright testing 3d plots (#16)
Browse files Browse the repository at this point in the history
* chore: use jest & playwright testing 3d plots

* chore: add missing dep

* chore: fix jest environment error

* chore: fix jest environment error

* chore: modify snapshots dirname

* chore: modify snapshots dirname

* chore: adjust snapshots dir

* chore: adjust snapshots dir

* chore: adjust snapshots dir
  • Loading branch information
xiaoiver authored Nov 21, 2023
1 parent 68ea682 commit 4fa29fa
Show file tree
Hide file tree
Showing 36 changed files with 345 additions and 133 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/test-3d.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: build

on: [push]

jobs:
build:
runs-on: macOS-latest
defaults:
run:
working-directory: ./3d

steps:
- name: Checkout
uses: actions/[email protected]

- name: Setup Node.js environment
uses: actions/[email protected]
with:
node-version: "16"

- name: Install Playwright browsers
run: npx playwright install --with-deps

- name: Run CI
run: |
npm install
npm run ci
- name: Upload snapshots to GitHub Actions Artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: snapshots
path: |
3d/__tests__/e2e/snapshots/*-actual.*
3d/__tests__/e2e/snapshots/*-diff.*
retention-days: 1
19 changes: 0 additions & 19 deletions .github/workflows/test-ava.yml

This file was deleted.

4 changes: 4 additions & 0 deletions 3d/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ node_modules
esm
lib
dist

playwright-report
*-actual.png
*-diff.png
8 changes: 8 additions & 0 deletions 3d/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,11 @@ chart
palette: "spectral",
});
```

## Run Test Cases

To run playwright, you must download new browsers first:

```bash
$npx playwright install
```
39 changes: 39 additions & 0 deletions 3d/__tests__/e2e/snapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { chromium, devices } from "playwright";
import * as tests from "../plots";
import "./utils/useSnapshotMatchers";
import { sleep } from "./utils/sleep";

describe("Charts", () => {
Object.keys(tests).forEach((key) => {
it(key, async () => {
// Setup
const browser = await chromium.launch({
args: ["--headless", "--no-sandbox"],
});
const context = await browser.newContext(devices["Desktop Chrome"]);
const page = await context.newPage();

await page.addInitScript(() => {
window["USE_PLAYWRIGHT"] = 1;
});

// Go to test page served by vite devServer.
const url = `http://localhost:${globalThis.PORT}/?name=${key}`;
await page.goto(url);

await sleep(300);

// Chart already rendered, capture into buffer.
const buffer = await page.locator("canvas").screenshot();

const dir = `${__dirname}/snapshots`;
try {
const maxError = 0;
await expect(buffer).toMatchCanvasSnapshot(dir, key, { maxError });
} finally {
await context.close();
await browser.close();
}
});
});
});
Binary file added 3d/__tests__/e2e/snapshots/Bar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/BarPerspective.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/Line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/LinePerspective.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/Scatter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/ScatterLegend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/ScatterPerspective.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/ScatterPoint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/ScatterTriangle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/Surface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions 3d/__tests__/e2e/utils/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(n: number) {
return new Promise((resolve) => {
setTimeout(resolve, n);
});
}
89 changes: 89 additions & 0 deletions 3d/__tests__/e2e/utils/toMatchCanvasSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as fs from "fs";
import * as path from "path";
import pixelmatch from "pixelmatch";
import { PNG } from "pngjs";

export type ToMatchCanvasSnapshotOptions = {
maxError?: number;
};

function writePNG(buffer: Buffer, path: string) {
const png = PNG.sync.read(buffer);
fs.writeFileSync(path, PNG.sync.write(png));
}

/**
* diff between PNGs
*/
function diff(src: string, target: string, diff: string, maxError = 0, showMismatchedPixels = true) {
const img1 = PNG.sync.read(fs.readFileSync(src));
const img2 = PNG.sync.read(fs.readFileSync(target));
const { width, height } = img1;

let diffPNG: PNG | null = null;
let output: Buffer | null = null;
if (showMismatchedPixels) {
diffPNG = new PNG({ width, height });
output = diffPNG.data;
}

// @see https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options
const mismatch = pixelmatch(img1.data, img2.data, output, width, height, {
threshold: 0.1,
});

if (showMismatchedPixels && mismatch > maxError && diffPNG) {
fs.writeFileSync(diff, PNG.sync.write(diffPNG));
}

return mismatch;
}

// @see https://jestjs.io/docs/26.x/expect#expectextendmatchers
export async function toMatchCanvasSnapshot(
buffer: Buffer,
dir: string,
name: string,
options: ToMatchCanvasSnapshotOptions = {},
): Promise<{ message: () => string; pass: boolean }> {
const { maxError = 0 } = options;
const namePath = path.join(dir, name);
const actualPath = path.join(dir, `${name}-actual.png`);
const expectedPath = path.join(dir, `${name}.png`);
const diffPath = path.join(dir, `${name}-diff.png`);

try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
if (!fs.existsSync(expectedPath)) {
if (process.env.CI === "true") {
throw new Error(`Please generate golden image for ${namePath}`);
}
console.warn(`! generate ${namePath}`);
writePNG(buffer, expectedPath);
return {
message: () => `generate ${namePath}`,
pass: true,
};
} else {
writePNG(buffer, actualPath);
const error = diff(actualPath, expectedPath, diffPath, maxError);
if (error <= maxError) {
if (fs.existsSync(diffPath)) fs.unlinkSync(diffPath);
fs.unlinkSync(actualPath);
return {
message: () => `match ${namePath}`,
pass: true,
};
}
return {
message: () => `mismatch ${namePath} (error: ${error}) `,
pass: false,
};
}
} catch (e) {
return {
message: () => `${e}`,
pass: false,
};
}
}
14 changes: 14 additions & 0 deletions 3d/__tests__/e2e/utils/useSnapshotMatchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { toMatchCanvasSnapshot, ToMatchCanvasSnapshotOptions } from "./toMatchCanvasSnapshot";

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
interface Matchers<R> {
toMatchCanvasSnapshot(dir: string, name: string, options?: ToMatchCanvasSnapshotOptions): R;
}
}
}

expect.extend({
toMatchCanvasSnapshot,
});
11 changes: 10 additions & 1 deletion 3d/__tests__/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
</head>
<script type="module">
import * as plots from "./plots/index.ts";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";

const select = createSelect(() => {
const { value } = select;
Expand Down Expand Up @@ -39,7 +42,13 @@
async function render() {
// if (typeof preClear === "function") preClear();
const fn = plots[select.value];
const { finished, destroy } = await fn({ container: "container" });
// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
if (!window.USE_PLAYWRIGHT) {
renderer.registerPlugin(new ControlPlugin());
}
const { finished, destroy } = await fn({ container: "container", renderer });
preClear = destroy;
}
</script>
Expand Down
13 changes: 3 additions & 10 deletions 3d/__tests__/plots/bar-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function BarPerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand All @@ -27,7 +20,7 @@ export function BarPerspective(context) {
x: `x-${x}`,
z: `z-${z}`,
y: 10 - x - z,
color: Math.random() < 0.33 ? 0 : Math.random() < 0.67 ? 1 : 2,
color: x > 2 ? 1 : 2,
});
}
}
Expand Down
13 changes: 3 additions & 10 deletions 3d/__tests__/plots/bar.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function Bar(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand All @@ -28,7 +21,7 @@ export function Bar(context) {
x: `x-${x}`,
z: `z-${z}`,
y: 10 - x - z,
color: Math.random() < 0.33 ? 0 : Math.random() < 0.67 ? 1 : 2,
color: x > 2 ? 1 : 2,
});
}
}
Expand Down
10 changes: 1 addition & 9 deletions 3d/__tests__/plots/line-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function LinePerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
10 changes: 1 addition & 9 deletions 3d/__tests__/plots/line.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function Line(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
11 changes: 2 additions & 9 deletions 3d/__tests__/plots/scatter-legend.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

Expand Down Expand Up @@ -67,12 +65,7 @@ function legendColor(chart) {
}

export function ScatterLegend(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
11 changes: 2 additions & 9 deletions 3d/__tests__/plots/scatter-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function ScatterPerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
Loading

0 comments on commit 4fa29fa

Please sign in to comment.