-
Notifications
You must be signed in to change notification settings - Fork 30
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
base: main
Are you sure you want to change the base?
Changes from all commits
a857852
e40e66a
a6c8b77
7c29c35
9588998
2683fee
a252f58
8bc8db6
020852f
421c5be
08c45fd
308f194
e7bc850
918b7ed
c660f4e
d9ef768
a738efd
f71a44e
c343f85
5ffa10b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,4 +20,4 @@ yarn-error.log* | |
report.* | ||
*.swp | ||
*.swo | ||
*~ | ||
*~ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Ignore everything in this directory | ||
* | ||
# Except this file | ||
!.gitignore |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Ignore everything in this directory | ||
* | ||
# Except this file | ||
!.gitignore |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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', | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New config to be used only when |
||
|
||
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()); | ||
} | ||
|
There was a problem hiding this comment.
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
beforeyarn screenshot
to test the updated Gosling.js.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will add!