99 lines
6.0 KiB
JavaScript
99 lines
6.0 KiB
JavaScript
// Optional browser QA: npm install in this directory, then npm test.
|
|
const { chromium } = require('playwright');
|
|
const sharp = require('sharp');
|
|
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
const { pathToFileURL } = require('node:url');
|
|
const root = path.resolve(__dirname, '../..');
|
|
|
|
async function ready(page) {
|
|
await page.waitForFunction(() => document.getElementById('preview-frame').style.display === 'block' && document.getElementById('loading').style.display === 'none');
|
|
const frame = page.frameLocator('#preview-frame');
|
|
await frame.locator('body').waitFor();
|
|
return frame;
|
|
}
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({headless:true, executablePath:process.env.BROWSER_EXECUTABLE_PATH || undefined});
|
|
try {
|
|
const page = await browser.newPage({viewport:{width:1650,height:1050}});
|
|
const errors = [];
|
|
page.on('pageerror', error => errors.push(error.message));
|
|
await page.goto(pathToFileURL(path.join(root,'preview/index.html')).href);
|
|
await page.addStyleTag({content:'*{transition:none!important;animation:none!important}'});
|
|
await ready(page);
|
|
// Floating inspector is preview chrome, not part of the rendered email.
|
|
if (await page.locator('#panel-close').isVisible()) await page.click('#panel-close');
|
|
const languages = await page.locator('#sel-locale option').evaluateAll(options => options.map(o=>o.value));
|
|
const themes = await page.locator('#sel-theme option').evaluateAll(options => options.map(o=>o.value));
|
|
const types = await page.locator('#sel-template option').evaluateAll(options => options.map(o=>o.value));
|
|
const buttonColors = {aurora:'rgb(224, 245, 243)',bloom:'rgb(255, 255, 255)',ember:'rgb(255, 255, 255)',heritage:'rgb(255, 255, 255)',horizon:'rgb(255, 255, 255)',ink:'rgb(254, 253, 251)',mono:'rgb(0, 0, 0)',neon:'rgb(10, 10, 15)',terminal:'rgb(13, 17, 23)',terra:'rgb(255, 255, 255)'};
|
|
if (!languages.includes('en-US') || !languages.includes('zh-CN')) throw new Error('official languages missing');
|
|
for (const lang of languages) {
|
|
await page.selectOption('#sel-locale',lang);
|
|
const frame = await ready(page);
|
|
if (!(await frame.locator('body').textContent()).trim()) throw new Error('empty '+lang+' preview');
|
|
}
|
|
await page.selectOption('#sel-locale','pl-PL');
|
|
await page.selectOption('#sel-template','team_invite');
|
|
await ready(page);
|
|
await page.waitForFunction(()=>document.getElementById('locale-message')?.textContent.includes('[UPSTREAM-WARN]'));
|
|
await page.selectOption('#sel-locale','zh-CN');
|
|
await page.selectOption('#sel-locale','en-US');
|
|
await ready(page);
|
|
const screenshotDir = path.join(root,'build','screenshots');
|
|
await fs.mkdir(screenshotDir,{recursive:true});
|
|
for (const theme of themes) {
|
|
await page.selectOption('#sel-theme',theme);
|
|
await page.selectOption('#sel-template','register_notify');
|
|
await page.click('#btn-desk');
|
|
const desktop = await ready(page);
|
|
const cta = desktop.locator('.card-body .email-btn').first();
|
|
const color = await cta.evaluate(el=>getComputedStyle(el).color);
|
|
if (buttonColors[theme] && color!==buttonColors[theme]) throw new Error('CTA styling overridden: '+theme+' '+color);
|
|
const label = (await cta.textContent()).trim();
|
|
if (!label || /^https?:/i.test(label)) throw new Error('button displays URL: '+theme);
|
|
const fallback = desktop.locator('.email-link-url a').first();
|
|
if (await cta.getAttribute('href') !== await fallback.getAttribute('href')) throw new Error('button/fallback target mismatch: '+theme);
|
|
const logo = desktop.locator('img.email-logo');
|
|
if (await logo.count()) {
|
|
if (!(await logo.getAttribute('src')).startsWith('data:image/png;base64,')) throw new Error('preview logo is not generated official asset: '+theme);
|
|
if (!(await logo.evaluate(img=>img.complete && img.naturalWidth>0))) throw new Error('logo failed to load: '+theme);
|
|
}
|
|
// Capture the actual 600px email, not the wide preview canvas/inspector.
|
|
await page.waitForFunction(()=>document.getAnimations().every(a=>a.playState!=='running'));
|
|
const box = await desktop.locator('.email-wrap').boundingBox();
|
|
if (!box) throw new Error('email frame missing: '+theme);
|
|
const png = await page.screenshot({clip:box,animations:'disabled'});
|
|
const background = await desktop.locator('body').evaluate(el=>getComputedStyle(el).backgroundColor);
|
|
const image = await sharp(png).extend({top:16,bottom:16,left:16,right:16,background}).resize({width:600}).png({compressionLevel:9}).toBuffer();
|
|
await fs.writeFile(path.join(screenshotDir,theme+'.png'),image);
|
|
for (const type of types) {
|
|
await page.selectOption('#sel-template',type);
|
|
await page.click('#btn-mob');
|
|
const frame = await ready(page);
|
|
const overflow = await frame.locator('html').evaluate(el=>el.scrollWidth > window.innerWidth + 1);
|
|
if (overflow) throw new Error('mobile overflow: '+theme+'/'+type);
|
|
}
|
|
}
|
|
if (errors.length) throw new Error(errors.join('\n'));
|
|
console.log('[PASS] file:// language bundles, warning, rapid switching, '+themes.length+' theme screenshots and mobile overflow');
|
|
// A running dev server can be included without making static QA depend on it.
|
|
if (process.env.PREVIEW_DEV_URL) {
|
|
await page.goto(process.env.PREVIEW_DEV_URL);
|
|
await ready(page);
|
|
await page.selectOption('#sel-locale','zh-CN');
|
|
await ready(page);
|
|
console.log('[PASS] HTTP preview and language switching');
|
|
}
|
|
if (process.argv.includes('--update-gallery')) {
|
|
for (const theme of themes) {
|
|
const image=await fs.readFile(path.join(screenshotDir,theme+'.png'));
|
|
if (image.length>50*1024) throw new Error('gallery image exceeds 50 KiB: '+theme);
|
|
await fs.writeFile(path.join(root,'docs','images',theme+'.png'),image);
|
|
}
|
|
console.log('[PASS] gallery screenshots updated');
|
|
}
|
|
} finally { await browser.close(); }
|
|
})().catch(error=>{console.error(error);process.exitCode=1;});
|