Skip to content

Commit ac91a84

Browse files
committed
Add integration testing script
1 parent c50e15d commit ac91a84

File tree

3 files changed

+498
-9
lines changed

3 files changed

+498
-9
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@
4040
"caniuse-lite": "^1.0.30001272",
4141
"cssnano": "^5.0.8",
4242
"esbuild": "^0.13.10",
43+
"jest-diff": "^27.4.2",
44+
"node-fetch": "^3.1.0",
4345
"parcel": "^2.0.1",
44-
"postcss": "^8.3.11"
46+
"postcss": "^8.3.11",
47+
"puppeteer": "^12.0.1"
4548
},
4649
"scripts": {
4750
"build": "node build.js",

test-integration.mjs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import puppeteer from 'puppeteer';
2+
import fetch from 'node-fetch';
3+
import assert from 'assert';
4+
import {diff} from 'jest-diff';
5+
import css from './native.js';
6+
7+
let urls = [
8+
'https://getbootstrap.com/docs/5.1/examples/headers/',
9+
'https://getbootstrap.com/docs/5.1/examples/heroes/',
10+
'https://getbootstrap.com/docs/5.1/examples/features/',
11+
'https://getbootstrap.com/docs/5.1/examples/sidebars/',
12+
'https://getbootstrap.com/docs/5.1/examples/footers/',
13+
'https://getbootstrap.com/docs/5.1/examples/dropdowns/',
14+
'https://getbootstrap.com/docs/5.1/examples/list-groups/',
15+
'https://getbootstrap.com/docs/5.1/examples/modals/',
16+
'http://csszengarden.com',
17+
'http://csszengarden.com/221/',
18+
'http://csszengarden.com/219/',
19+
'http://csszengarden.com/218/',
20+
'http://csszengarden.com/217/',
21+
'http://csszengarden.com/216/',
22+
'http://csszengarden.com/215/'
23+
];
24+
25+
let success = true;
26+
const browser = await puppeteer.launch();
27+
const page = await browser.newPage();
28+
29+
for (let url of urls) {
30+
await test(url);
31+
}
32+
33+
async function test(url) {
34+
console.log(`Testing ${url}...`);
35+
36+
await page.goto(url);
37+
await page.waitForNetworkIdle();
38+
39+
// Snapshot the computed styles of all elements
40+
let elements = await page.$$('body *');
41+
let computed = [];
42+
for (let element of elements) {
43+
let style = await element.evaluate(node => {
44+
let res = {};
45+
let style = window.getComputedStyle(node);
46+
for (let i = 0; i < style.length; i++) {
47+
res[style.item(i)] = style.getPropertyValue(style.item(i));
48+
}
49+
return res;
50+
});
51+
52+
for (let key in style) {
53+
style[key] = normalize(key, style[key]);
54+
}
55+
56+
computed.push(style);
57+
}
58+
59+
// Find stylesheets, load, and minify.
60+
let stylesheets = await page.evaluate(() => {
61+
return [...document.styleSheets].map(styleSheet => styleSheet.href).filter(Boolean);
62+
});
63+
64+
let texts = await Promise.all(stylesheets.map(async url => {
65+
let res = await fetch(url);
66+
return res.text();
67+
}));
68+
69+
let minified = texts.map((code, i) => {
70+
let minified = css.transform({
71+
filename: 'test.css',
72+
code: Buffer.from(code),
73+
minify: true,
74+
targets: {
75+
chrome: 95 << 16
76+
}
77+
}).code.toString();
78+
console.log(new URL(stylesheets[i]).pathname, '–', code.length + ' bytes', '=>', minified.length + ' bytes');
79+
return minified;
80+
});
81+
82+
await page.setCacheEnabled(false);
83+
84+
// Disable the original stylesheets and insert a <style> element containing the minified CSS for each.
85+
await page.evaluate(minified => {
86+
let i = 0;
87+
for (let stylesheet of [...document.styleSheets]) {
88+
if (stylesheet.href) {
89+
stylesheet.disabled = true;
90+
91+
let style = document.createElement('style');
92+
style.textContent = minified[i++].replace(/url\((.*?)\)/g, (orig, url) => {
93+
if (/['"]?data:/.test(url)) {
94+
return orig;
95+
}
96+
97+
// Rewrite urls so they are relative to the original stylesheet.
98+
return `url(${new URL(url, stylesheet.href)})`;
99+
});
100+
101+
stylesheet.ownerNode.insertAdjacentElement('beforebegin', style);
102+
}
103+
}
104+
}, minified);
105+
106+
await page.waitForNetworkIdle();
107+
108+
// Now get the computed style for each element again and compare.
109+
let i = 0;
110+
for (let element of elements) {
111+
let style = await element.evaluate(node => {
112+
let res = {};
113+
let style = window.getComputedStyle(node);
114+
for (let i = 0; i < style.length; i++) {
115+
let name = style.item(i);
116+
res[name] = style.getPropertyValue(name);
117+
}
118+
return res;
119+
});
120+
121+
for (let key in style) {
122+
style[key] = normalize(key, style[key]);
123+
124+
// Ignore prefixed properties that were removed during minification.
125+
if (key.startsWith('-webkit-box-') && style[key] !== computed[i][key]) {
126+
style[key] = computed[i][key];
127+
}
128+
}
129+
130+
try {
131+
assert.deepEqual(style, computed[i]);
132+
} catch (err) {
133+
success = false;
134+
console.log(diff(computed[i], style));
135+
console.log(await element.evaluate(node => node.outerHTML))
136+
console.log(minified[0]);
137+
}
138+
139+
i++;
140+
}
141+
142+
console.log('');
143+
}
144+
145+
function normalize(key, value) {
146+
if (key === 'background-position') {
147+
value = value.replace(/(^|\s)0(%|px)/g, '$10');
148+
}
149+
150+
if (key === 'background-image') {
151+
// Center is implied.
152+
value = value.replace('radial-gradient(at center center, ', 'radial-gradient(');
153+
}
154+
155+
return value.split(' ').map(v => {
156+
if (/^[\d\.]+px$/.test(v)) {
157+
let val = parseFloat(v);
158+
return Math.round(val) + 'px';
159+
}
160+
161+
return v;
162+
}).join(' ');
163+
}
164+
165+
if (success) {
166+
console.log('Pass!');
167+
process.exit(0);
168+
} else {
169+
console.log('Fail!');
170+
process.exit(1);
171+
}

0 commit comments

Comments
 (0)