أتمتة Visual Regression: امنع كسر الواجهة قبل النشر
مستوى القارئ: متوسط
هتكسب من المقال ده gate عملي في GitHub Actions يمسك كسر الواجهة قبل ما يوصل للمستخدم، بدل ما تكتشفه من رسالة عميل أو screenshot في Slack.
المشكلة باختصار
اختبارات الـ unit ممتازة في منطق الكود، لكنها ضعيفة جدًا في سؤال بسيط: هل زر الدفع اتحرك تحت الـ fold؟ هل الـ navbar غطّى العنوان؟ هل RTL اتكسر بعد تعديل CSS؟
اللي بيحصل فعلاً إن تغيير صغير في spacing أو font loading ممكن يعدّي من كل الاختبارات. في موقع عنده 8 صفحات تسويقية وصفحتين checkout، فرق بصري واحد في صفحة الدفع قد يخفض التحويل 2% خلال يوم. لو عندك 50,000 زيارة يوميًا، ده رقم يستاهل gate واضح.
مثال بسيط قبل التعريف الدقيق
ركز في المثال ده: عندك صفحة pricing. أول مرة تشغّل الاختبار، Playwright يحفظ صورة مرجعية اسمها pricing.png. بعد أي تعديل، يفتح نفس الصفحة، يأخذ screenshot جديدة، ويقارنها بالمرجعية. لو الفرق أكبر من 0.5%، الـ CI يفشل ويرفع صور actual وdiff كـ artifact.
بالتفاصيل، Visual Regression Testing هو مقارنة بصرية آلية بين حالة واجهة معتمدة وحالة جديدة. Playwright يدعم toHaveScreenshot()، وبيستخدم مقارنة screenshots مع إعدادات مثل maxDiffPixelRatio وmaxDiffPixels. الفكرة مش إنك تمنع أي تغيير بصري. الفكرة إن أي تغيير بصري مهم يبقى مقصود ومراجع.
الإعداد العملي
الافتراض إن عندك تطبيق ويب Node.js، وتقدر تشغله محليًا بأمر مثل npm run dev. أفضل طريقة تبدأ بثلاث صفحات فقط: الصفحة الرئيسية، صفحة pricing، وصفحة checkout. لا تبدأ بـ 60 صفحة؛ هتغرق في noise.
npm init playwright@latest
npm install -D @playwright/test
npx playwright install --with-deps chromiumبعدها اعمل ملف اختبار بسيط:
// tests/visual.spec.ts
import { test, expect } from '@playwright/test';
const pages = [
['home', '/'],
['pricing', '/pricing'],
['checkout', '/checkout'],
] as const;
for (const [name, path] of pages) {
test(`${name} visual check`, async ({ page }) => {
await page.goto(path);
await page.addStyleTag({ content: `
[data-dynamic], .chat-widget { visibility: hidden !important; }
`});
await expect(page).toHaveScreenshot(`${name}.png`, {
maxDiffPixelRatio: 0.005,
animations: 'disabled',
fullPage: true,
});
});
}