Skip to content

Commit 09ce173

Browse files
committed
add screenshot tests
1 parent a8a5b9d commit 09ce173

File tree

14 files changed

+11225
-2
lines changed

14 files changed

+11225
-2
lines changed

packages/lit-virtualizer/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/lib/
2-
/lit-virtualizer.js
2+
/lit-virtualizer.js
3+
/test/screenshot/cases/*/actual.*.png
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Contributing
2+
3+
Here is an overview of the repository contents.
4+
5+
## Source and build
6+
7+
`/src` contains the source files, written in JS using ES modules.
8+
9+
`/src/lib/uni-virtualizer` contains the uni-virtualizer source files. They are copied over from the uni-virtual package in the parent monorepo. Uni-virtualizer will someday be its own package published to NPM. At that point, lit-virtualizer can simply refer to the `uni-virtualizer` package instead of build it into the lib directly.
10+
11+
`npm run build` builds uni-virtualizer into `/src/lib`, and then copies `/src` into `/`. This package is published as ES modules, leaving the responsibility of module resolution to the user. This allows the user control over package delivery, for example, leaving one the freedom to implement code splitting.
12+
13+
## Testing
14+
15+
### Integration and Unit
16+
17+
Karma with Mocha and Chai is used for integration and unit testing. Run `npm run test` to run these suites.
18+
19+
### Screenshots
20+
21+
Screenshot test cases live in `/test/screenshot/cases`. Each page in this directory is served and visited during screenshot testing. Since lit-virtualizer uses ES modules, each page is built with rollup before being served.
22+
23+
Puppeteer with Mocha and Chai is used for screenshot testing. Run `npm run test:screenshot` to run these tests. If a change to lit-virtualizer is made that affects the expected screenshots, run `npm run generate-screenshots` to regenerate them.
24+
25+
#### Adding a new screenshot test
26+
27+
1. See if any of the existing page setups can be used for your test.
28+
2. If not, add a new directory under `/test/screenshot/cases`. Create an `index.html` and a `main.js` in it, and set up your page. Your `main.js` will be built automatically, so reference it in `index.html` as `build/build.js`
29+
4. Add your test cases to `/test/screenshot/screenshot.js`.
30+
5. Generate screenshots for your new page and test cases. You can easily generate new screenshots for *only your new page* by adding `.only` to your `describe` block. For example: `describe.only('lit-virtual', function() { ... }`. Then run `npm run generate-screenshots`. Don't forget to remove the `.only` after.
31+
32+
33+
## Publishing
34+
35+
This package is published to NPM as `lit-virtualizer`. Only `lit-virtualizer.js` and `/lib` are published, along with default files such as `README.md`.
36+

packages/lit-virtualizer/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,24 @@
1313
"build": "npm run build:uni-virtualizer && cp -r src/ .",
1414
"build:uni-virtualizer": "cp -r ../uni-virtualizer/src/. ./src/lib/uni-virtualizer",
1515
"prepare": "npm run build",
16-
"test": "karma start karma.conf.js"
16+
"test": "karma start karma.conf.js",
17+
"test:screenshot": "cd test/screenshot && rollup -c && mocha screenshot.js",
18+
"generate-screenshots": "cd test/screenshot && rollup -c && mocha screenshot.js --generate-screenshots"
1719
},
1820
"author": "The Polymer Authors",
1921
"devDependencies": {
2022
"chai": "^4.2.0",
23+
"http-server": "^0.11.1",
2124
"karma": "^4.1.0",
2225
"karma-chai": "^0.1.0",
2326
"karma-chrome-launcher": "^2.2.0",
2427
"karma-mocha": "^1.3.0",
2528
"karma-mocha-reporter": "^2.2.5",
2629
"karma-rollup-preprocessor": "^7.0.0",
2730
"mocha": "^6.1.4",
31+
"pixelmatch": "^4.0.2",
32+
"pngjs": "^3.4.0",
33+
"puppeteer": "^1.17.0",
2834
"rollup": "^1.11.2",
2935
"rollup-plugin-node-resolve": "^4.2.3"
3036
},
Loading
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>Lit Virtualizer Displays Items</title>
8+
</head>
9+
<body>
10+
<script type="module" src="build/main.js"></script>
11+
</body>
12+
</html>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import '../../../../lit-virtualizer.js'
2+
import { html } from 'lit-html';
3+
4+
(async function go() {
5+
const contacts = await(await fetch('../../shared/contacts.json')).json();
6+
7+
const virtualizer = document.createElement('lit-virtualizer');
8+
virtualizer.items = contacts;
9+
virtualizer.template = ({ mediumText }) => html`<p>${mediumText}</p>`;
10+
11+
document.body.appendChild(virtualizer);
12+
})();
Loading
Loading
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>Scroll Displays Items</title>
8+
</head>
9+
<body>
10+
<script type="module" src="build/main.js"></script>
11+
</body>
12+
</html>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { scroll } from '../../../../lit-virtualizer.js'
2+
import { html, render } from 'lit-html';
3+
4+
(async function go() {
5+
const contacts = await(await fetch('../../shared/contacts.json')).json();
6+
7+
const virtualized = html`<div id="main">
8+
${scroll({
9+
items: contacts,
10+
template: ({ mediumText }) => html`<p>${mediumText}</p>`
11+
})}
12+
</div>`;
13+
14+
render(virtualized, document.body);
15+
})();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import resolve from 'rollup-plugin-node-resolve';
2+
const { readdirSync } = require('fs')
3+
4+
const builds = [];
5+
6+
// Create a build for each page in cases/.
7+
const pages = readdirSync('./cases/');
8+
for (let name of pages) {
9+
builds.push({
10+
input: `cases/${name}/main.js`,
11+
output: {
12+
dir: `cases/${name}/build`,
13+
format: 'esm'
14+
},
15+
plugins: [
16+
resolve(),
17+
]
18+
});
19+
}
20+
21+
export default builds;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const httpServer = require('http-server');
2+
const pixelmatch = require('pixelmatch');
3+
const puppeteer = require('puppeteer');
4+
const expect = require('chai').expect;
5+
const PNG = require('pngjs').PNG;
6+
const fs = require('fs');
7+
8+
const generating = process.argv.includes('--generate-screenshots');
9+
10+
11+
describe(generating ? 'Generating screenshots' : 'Screenshots', function() {
12+
let server, browser, page;
13+
14+
before(async function() {
15+
server = httpServer.createServer();
16+
await server.listen(8080);
17+
});
18+
19+
after(async function() {
20+
await server.close();
21+
})
22+
23+
beforeEach(async function() {
24+
browser = await puppeteer.launch();
25+
page = await browser.newPage();
26+
});
27+
28+
afterEach(() => browser.close());
29+
30+
/*
31+
* Test cases
32+
*/
33+
describe('lit-virtual', function() {
34+
beforeEach(async function() {
35+
await page.goto(`http://127.0.0.1:8080/cases/lit-virtual/`);
36+
await page.waitForSelector('lit-virtualizer');
37+
});
38+
39+
it('displays items', async function() {
40+
return takeAndCompareScreenshot(page, 'lit-virtual', 'displays-items')
41+
});
42+
43+
it('scrolls', async function() {
44+
await page.evaluate(() => {
45+
document.querySelector('lit-virtualizer').scrollBy(0, 200);
46+
});
47+
return takeAndCompareScreenshot(page, 'lit-virtual', 'scrolls')
48+
});
49+
});
50+
51+
describe('scroll', function() {
52+
beforeEach(async function() {
53+
await page.goto(`http://127.0.0.1:8080/cases/scroll/`);
54+
await page.waitForSelector('#main');
55+
});
56+
57+
it('displays items', async function() {
58+
return takeAndCompareScreenshot(page, 'scroll', 'displays-items')
59+
});
60+
61+
it('scrolls', async function() {
62+
await page.evaluate(() => {
63+
document.querySelector('#main').scrollBy(0, 200);
64+
});
65+
return takeAndCompareScreenshot(page, 'scroll', 'scrolls')
66+
});
67+
});
68+
});
69+
70+
/*
71+
* Take a screenshot of the current page and compare it to the expected screenshot.
72+
*/
73+
async function takeAndCompareScreenshot(page, testDir, caseName) {
74+
let imageType = generating ? 'expected' : 'actual';
75+
await page.screenshot({path: `./cases/${testDir}/${imageType}.${caseName}.png`});
76+
if (!generating) {
77+
return compareScreenshots(testDir, caseName);
78+
}
79+
}
80+
81+
function compareScreenshots(testDir, caseName) {
82+
return new Promise((resolve, _) => {
83+
const img1 = fs.createReadStream(`./cases/${testDir}/actual.${caseName}.png`).pipe(new PNG()).on('parsed', doneReading);
84+
const img2 = fs.createReadStream(`./cases/${testDir}/expected.${caseName}.png`).pipe(new PNG()).on('parsed', doneReading);
85+
86+
let filesRead = 0;
87+
function doneReading() {
88+
// Wait until both files are read.
89+
if (++filesRead < 2) return;
90+
91+
// The files should be the same size.
92+
expect(img1.width, 'image widths are the same').equal(img2.width);
93+
expect(img1.height, 'image heights are the same').equal(img2.height);
94+
95+
// Do the visual diff.
96+
const diff = new PNG({width: img1.width, height: img2.height});
97+
const numDiffPixels = pixelmatch(
98+
img1.data, img2.data, diff.data, img1.width, img1.height,
99+
{threshold: 0.1});
100+
101+
// The files should look the same.
102+
expect(numDiffPixels, 'number of different pixels').equal(0);
103+
resolve();
104+
}
105+
});
106+
}

0 commit comments

Comments
 (0)