Skip to content

test(core): add visual regression testing #962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ yarn-error.log*
report.*
*.swp
*.swo
*~
*~
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ yarn build-editor
yarn preview
```

## Visual Regression

To test if your changes make a difference visually across a large number of specifications, you can use the `yarn screenshot` command.
``` bash
yarn build # needed to get the latest gosling build
yarn screenshot # takes screenshots of all of the example specs and puts them in img/visual-regression/new-screenshots
yarn update-ref-screenshots # update the reference screenshots with your new screenshots
```
This command will use `dist/gosling.js` in a headless browser (using puppeteer) and take a screenshot of the visualization that is included in the editor.
Copy link
Member

@sehilyi sehilyi Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could briefly mention that one needs to run yarn build before yarn screenshot to test the updated Gosling.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add!

It will save these screenshots in `img/visual-regression/new-screenshots`. If there is a difference compared to the screenshots in `img/visual-regression/reference-screenshots`
it will show the differences in an image in `img/visual-regression/diffs`.

## Editing `gosling.js/embed`

This repo also contains the source code for `gosling.js/embed`, an ES Module intended to be
Expand Down
4 changes: 4 additions & 0 deletions img/visual-regression/diffs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
4 changes: 4 additions & 0 deletions img/visual-regression/new-screenshots/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
Binary file added img/visual-regression/screenshots/ALIGNMENT.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 img/visual-regression/screenshots/BAM_PILEUP.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 img/visual-regression/screenshots/BAND.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/CIRCOS.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/CYTOBANDS.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/DEBUG.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/GIVE.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 img/visual-regression/screenshots/GREMLIN.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/LINKING.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/MATRIX.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/MOUSE_EVENT.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 img/visual-regression/screenshots/RULE.png
Binary file added img/visual-regression/screenshots/SARS_COV_2.png
Binary file added img/visual-regression/screenshots/SEQUENCE.png
Binary file added img/visual-regression/screenshots/TEMPLATE.png
Binary file added img/visual-regression/screenshots/doc_area.png
Binary file added img/visual-regression/screenshots/doc_bar.png
Binary file added img/visual-regression/screenshots/doc_bed.png
Binary file added img/visual-regression/screenshots/doc_brush.png
Binary file added img/visual-regression/screenshots/doc_gff.png
Binary file added img/visual-regression/screenshots/doc_line.png
Binary file added img/visual-regression/screenshots/doc_link.png
Binary file added img/visual-regression/screenshots/doc_point.png
Binary file added img/visual-regression/screenshots/doc_rect.png
Binary file added img/visual-regression/screenshots/doc_text.png
116 changes: 116 additions & 0 deletions img/visual-regression/visual-regression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import puppeteer, { Page, Browser } from 'puppeteer';
import { examples } from '../../editor/example';
import * as fs from 'fs';
import { beforeAll } from 'vitest';

import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';

function delay(time: number) {
return new Promise(resolve => {
setTimeout(resolve, time);
});
}
// Based on https://github.com/hms-dbmi/chromoscope/blob/master/src/script/gosling-screenshot.js
function html(
spec: string,
gosling: string,
{ reactVersion = '16', pixijsVersion = '6', higlassVersion = '1.11' } = {}
) {
const baseUrl = 'https://unpkg.com';
return `\
<!DOCTYPE html>
<html>
<link rel="stylesheet" href="${baseUrl}/higlass@${higlassVersion}/dist/hglib.css">
<script src="${baseUrl}/react@${reactVersion}/umd/react.production.min.js"></script>
<script src="${baseUrl}/react-dom@${reactVersion}/umd/react-dom.production.min.js"></script>
<script src="${baseUrl}/pixi.js@${pixijsVersion}/dist/browser/pixi.min.js"></script>
<script src="${baseUrl}/higlass@${higlassVersion}/dist/hglib.js"></script>
<script type="text/javascript">${gosling}</script>
<body>
<div id="vis"></div>
<script>
gosling.embed(document.getElementById("vis"), JSON.parse(\`${spec}\`))
</script>
</body>
</html>`;
Comment on lines +24 to +36
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have to change when we update Gosling to only have a ESM build

}

function readFile(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}

/**
* Compares two PNG files and writes the difference to a third file if a difference is found
*/
function comparePNG(path1: string, path2: string, diffPath: string) {
const img1 = PNG.sync.read(fs.readFileSync(path1));
const img2 = PNG.sync.read(fs.readFileSync(path2));
const { width, height } = img1;
const diff = new PNG({ width, height });
const pixeldifference = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
// only write to file if there is a difference in the images
if (pixeldifference > 0) {
fs.writeFileSync(diffPath, PNG.sync.write(diff));
}
}

let browser: Browser;
let page: Page;
let currentGosling: string;

/**
* Setup the browser and page
*/

beforeAll(async () => {
currentGosling = await readFile('./dist/gosling.js');
browser = await puppeteer.launch({
headless: true,
devtools: true,
args: ['--use-gl=swiftshader'] // necessary for canvas to not be blank in the screenshot
});
page = await browser.newPage();
await page.goto('http://gosling-lang.org/docs/'); // must first go to a page with a URL for workers to work properly
});

console.warn('Expect this to take about 10 minutes to run, depending on your internet speed');

/**
* Loop over all examples and take a screenshot
*/
Object.entries(examples)
// .filter(([name]) => name === 'doc_text') // we only want to see the broken example now
.forEach(([name, example]) => {
test(
name,
async () => {
let spec = JSON.stringify(example.spec);
spec = spec.replaceAll('\\', '\\\\');
await page.setContent(html(spec, currentGosling), { waitUntil: 'networkidle0' });
await page.addScriptTag({ path: './dist/gosling.js' });
const component = await page.waitForSelector('.gosling-component');
await page.waitForNetworkIdle({ idleTime: 2000 });
await delay(2000); // wait extra 2 seconds. Should be enough time for any rendering to finish
await component!.screenshot({ path: `./img/visual-regression/new-screenshots/${name}.png` });
comparePNG(
`img/visual-regression/reference-screenshots/${name}.png`,
`img/visual-regression/new-screenshots/${name}.png`,
`img/visual-regression/diffs/${name}.png`
);
},
20000
);
});

afterAll(async () => {
await browser.close();
});
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"preview": "vite preview",
"check": "tsc --noEmit",
"test": "vitest",
"screenshot": "vitest --mode screenshot",
"update-ref-screenshots": "rm ./img/visual-regression/reference-screenshots/*.png; mv ./img/visual-regression/new-screenshots/*.png ./img/visual-regression/reference-screenshots/",
"coverage": "vitest run --coverage",
"format": "eslint src/ editor/ --fix && prettier 'editor/**/*.css' --write",
"schema": "node scripts/generate-schemas.mjs",
Expand Down Expand Up @@ -93,6 +95,8 @@
"@types/d3-request": "^1.0.6",
"@types/d3-selection": "^2.0.0",
"@types/lodash-es": "^4.17.5",
"@types/pixelmatch": "^5.2.4",
"@types/pngjs": "^6.0.2",
"@types/pubsub-js": "^1.8.2",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
Expand Down Expand Up @@ -121,8 +125,11 @@
"jsdom": "^19.0.0",
"jsoncrush": "^1.1.6",
"npm-run-all": "^4.1.5",
"pixelmatch": "^5.3.0",
"pixi.js": "^6.3.0",
"pngjs": "^7.0.0",
"prettier": "^2.0.5",
"puppeteer": "^13.5.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^5.0.3",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"editor/index.tsx",
"embed/index.ts"
],
"include": ["src", "src/**/*.d.ts", "editor"],
"include": ["src", "src/**/*.d.ts", "editor", "img"],
"exclude": [
"node_modules",
"public",
Expand Down
25 changes: 25 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ const external = [...Object.keys(pkg.dependencies), ...Object.keys(pkg.peerDepen
dep => !skipExt.has(dep)
);

/**
* Used when yarn build-lib is run
*/
const esm = defineConfig({
build: {
emptyOutDir: false,
Expand Down Expand Up @@ -101,9 +104,13 @@ const dev = defineConfig({
plugins: [bundleWebWorker, manualInlineWorker]
});

/**
* This config is used when vitest is run
*/
const testing = defineConfig({
resolve: { alias },
test: {
exclude: ['./node_modules/**', './dist/**', './img/**'], // img is excluded because it is used for visual regression testing
globals: true,
setupFiles: [path.resolve(__dirname, './scripts/setup-vitest.js')],
environment: 'jsdom',
Expand All @@ -121,9 +128,27 @@ const testing = defineConfig({
}
});

/**
* This config is used to take screenshots of every example Gosling spec using a headless browser (puppeteer)
*/
const screenshot = defineConfig({
test: {
include: ['./img/visual-regression/*'],
globals: true,
environment: 'jsdom',
threads: false,
environmentOptions: {
jsdom: {
resources: 'usable'
}
}
}
});
Comment on lines +134 to +146
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New config to be used only when yarn screenshot is called so that only tests in img/visual-regression will be run


export default ({ command, mode }) => {
if (command === 'build' && mode === 'lib') return esm;
if (mode == 'test') return testing;
if (mode == 'screenshot') return screenshot;
if (mode === 'editor') {
dev.plugins.push(reactRefresh());
}
Expand Down
Loading