يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
اسمحوا لي أن أبدأ بقصة قصيرة حدثت معي ومع فريقي قبل بضع سنوات، قصة لا تزال تفاصيلها عالقة في ذهني. كانت ليلة خميس، وكنا على وشك إطلاق تحديث جديد ومهم لتطبيقنا. الأجواء كانت مشحونة بالحماس والترقب. كل الاختبارات الآلية (Unit, E2E) كانت خضراء، الأمور تبدو “عال العال”، والفريق كله ينتظر الضغط على زر الإطلاق.
قبل الإطلاق بدقائق، قرر أحد المطورين الشباب المتحمسين إجراء تعديل بسيط جداً على CSS. كان التعديل يهدف لتحسين مظهر زر صغير في صفحة الإعدادات، شيء لا يكاد يُذكر. قام بدفع التغيير، ومرّت الاختبارات الآلية مرة أخرى بنجاح تام. قلنا “توكل على الله” وأطلقنا التحديث.
في صباح اليوم التالي، استيقظنا على سيل من رسائل الدعم والشكاوى. الصفحة الرئيسية، وهي أهم صفحة في التطبيق، كانت “مكركبة” بالكامل عند بعض المستخدمين. القائمة العلوية متداخلة مع المحتوى، والصور خارج أماكنها… كارثة بصرية بكل معنى الكلمة. بعد ساعات من التحقيق والتوتر، اكتشفنا أن تغيير الـ CSS البسيط في صفحة الإعدادات، بسبب اسم class عام جداً، أثر بشكل مباشر ومدمر على الصفحة الرئيسية!
في تلك اللحظة، ونحن نصلح المشكلة على عجل، قلت للفريق: “يا جماعة، لازم نلاقي حل لهالأشباح اللي بتظهر فجأة. اختباراتنا الحالية عمياء، ما بتشوف!”. ومن هنا، بدأت رحلتنا الحقيقية مع ما يُعرف بـ “اختبار الانحدار البصري”.
ما هو “الشبح” الذي نواجهه؟ ولماذا اختباراتنا التقليدية لا تراه؟
المشكلة التي واجهناها تُعرف بـ “الانحدار البصري” (Visual Regression). ببساطة، هو عندما يؤدي تغيير في الكود إلى خطأ غير مقصود في شكل الواجهة الرسومية (UI). قد يكون هذا الخطأ:
- عنصر يختفي أو يتغير مكانه.
- نصوص متداخلة.
- ألوان خاطئة.
- انهيار في تصميم الصفحة على شاشات معينة.
الاختبارات التقليدية، مثل اختبارات الوحدة (Unit Tests) أو حتى الاختبارات الشاملة (End-to-End Tests)، مصممة للتحقق من الوظائف وليس المظهر. يمكن لاختبار E2E أن يؤكد لك أن “الزر موجود في الصفحة”، لكنه لن يخبرك أبداً ما إذا كان هذا الزر نصفه خارج الشاشة أو أن لونه أصبح وردياً فاقعاً بدلاً من الأزرق.
باختصار، كانت أدواتنا تتحقق من “وجود” العناصر، لكنها كانت عمياء عن “هيئة” هذه العناصر.
اختبار الانحدار البصري: النظارات التي تكشف الأشباح
اختبار الانحدار البصري (Visual Regression Testing – VRT) هو عملية آلية تهدف إلى اكتشاف هذه التغييرات البصرية غير المرغوب فيها. الفكرة في جوهرها بسيطة جداً، تشبه لعبة “أوجد الفروقات بين الصورتين”:
- التقاط صورة أساسية (Baseline): في المرة الأولى، نقوم بتشغيل الاختبارات لالتقاط صور لواجهاتنا (صفحات كاملة أو مكونات محددة) وهي في حالتها الصحيحة والسليمة. هذه الصور تسمى “الصور المرجعية” أو “Baseline Snapshots”.
- إجراء التغييرات البرمجية: يقوم المطورون بعملهم المعتاد، بتعديل الكود وإضافة ميزات جديدة.
- التقاط صورة جديدة: بعد إجراء التغييرات، يتم تشغيل الاختبارات مرة أخرى، والتي تقوم بالتقاط مجموعة جديدة من الصور لنفس الواجهات.
- المقارنة وكشف الفروقات: يقوم النظام بمقارنة الصور الجديدة بالصور المرجعية بكسل ببكسل.
- الإبلاغ عن الاختلاف: إذا تم العثور على أي اختلاف، يفشل الاختبار ويقوم بإنشاء صورة ثالثة توضح “الفرق” (Diff)، وتسلط الضوء على المناطق التي تغيرت بالضبط. عندها، يمكن للمطور أن يقرر: هل هذا التغيير مقصود (ويقوم بتحديث الصورة المرجعية) أم أنه خطأ (ويقوم بإصلاحه).
هذه العملية تضمن أن أي تغيير بصري، مهما كان صغيراً، لن يمر دون أن يلاحظه أحد.
كيف نبدأ؟ أشهر الأدوات ومثال عملي
هناك العديد من الأدوات الرائعة في هذا المجال، بعضها مستقل وبعضها مدمج في أطر عمل الاختبارات الحديثة. من أشهرها: Playwright, Cypress مع إضافات، Storybook مع إضافات مثل Chromatic, و Percy.
دعونا نأخذ مثالاً عملياً باستخدام Playwright، وهي أداة رائعة من مايكروسوفت أحبها لسهولتها وقوتها، حيث أن ميزة اختبار الانحدار البصري مدمجة فيها بشكل أساسي.
مثال عملي باستخدام Playwright
لنفترض أن لدينا صفحة رئيسية ونريد التأكد من أن تصميمها لا يتغير بشكل غير متوقع.
1. التثبيت والإعداد:
أولاً، قم بتثبيت Playwright في مشروعك:
npm init playwright@latest
2. كتابة الاختبار:
الآن، لنكتب ملف اختبار بسيط. سنسميه homepage.spec.ts.
import { test, expect } from '@playwright/test';
test.describe('Homepage Visual Tests', () => {
test('should not have visual regressions on the homepage', async ({ page }) => {
// 1. اذهب إلى الصفحة الرئيسية
await page.goto('https://your-website.com');
// 2. انتظر حتى يتم تحميل العناصر الأساسية (اختياري لكن موصى به)
await page.waitForSelector('.main-content');
// 3. التقط الصورة وقارنها بالصورة المرجعية
await expect(page).toHaveScreenshot('homepage.png');
});
test('should not have visual regressions for the header component', async ({ page }) => {
// يمكنك أيضاً اختبار مكونات محددة
await page.goto('https://your-website.com');
const header = page.locator('header');
// التقط صورة للهيدر فقط
await expect(header).toHaveScreenshot('header-component.png');
});
});
3. تشغيل الاختبارات:
- لإنشاء الصور المرجعية (أول مرة فقط):
في المرة الأولى التي تشغل فيها هذا الاختبار، لا توجد صور مرجعية للمقارنة بها. لذلك، نطلب من Playwright إنشاءها باستخدام الأمر:npx playwright test --update-snapshotsسيقوم هذا الأمر بإنشاء مجلد جديد (عادة ما يكون اسمه
tests/homepage.spec.ts-snapshots) ويضع فيه الصور المرجعية (homepage.pngوheader-component.png). - لتشغيل الاختبارات بشكل عادي:
بعد ذلك، في كل مرة تريد فيها التحقق من عدم وجود انحدارات بصرية، ما عليك سوى تشغيل الأمر العادي:npx playwright testإذا تطابقت الصور، سينجح الاختبار بصمت. ولكن إذا كان هناك اختلاف، سيفشل الاختبار وسينشئ لك Playwright تقريراً جميلاً يوضح لك الصورة الأصلية، الصورة الجديدة، وصورة الفروقات (Diff) التي تبرز التغييرات باللون الأحمر.
نصائح من مطبخ أبو عمر: أفضل الممارسات
بعد سنوات من استخدام هذه التقنية، تعلمت بعض الدروس بالطريقة الصعبة. إليكم خلاصة خبرتي لتجنب الوقوع في نفس الأخطاء:
1. لا تختبر كل شيء! (مش كل إشي بنعمله تست)
من المغري أن تبدأ بالتقاط صور لكل صفحة في تطبيقك، لكن هذا سيخلق كابوس صيانة. كل تغيير مقصود سيتطلب منك تحديث عشرات الصور. بدلاً من ذلك، ركز على:
- المكونات المشتركة (Shared Components): الأزرار، القوائم، الترويسة (Header)، التذييل (Footer). تغيير في هذه المكونات يؤثر على التطبيق بأكمله.
- الصفحات والمسارات الحرجة (Critical Flows): الصفحة الرئيسية، صفحة المنتج، عملية الدفع.
- التصاميم المعقدة: الصفحات التي تحتوي على رسوم بيانية (Charts) أو تصاميم معقدة تكون عرضة للكسر.
2. تعامل بذكاء مع المحتوى الديناميكي
ماذا لو كانت صفحتك تعرض تاريخ اليوم، أو اسم المستخدم، أو إعلانات متغيرة؟ ستفشل اختباراتك في كل مرة لأن المحتوى يتغير. الحل هو “إخفاء” (Masking) هذه الأجزاء الديناميكية.
في Playwright، يمكنك فعل ذلك بسهولة:
// إخفاء عنصر التاريخ والوقت قبل التقاط الصورة
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.locator('.date-time-display')]
});
بهذه الطريقة، سيتجاهل محرك المقارنة هذه المنطقة تماماً ويركز على التصميم الثابت حولها.
3. حدد “هامش خطأ” مقبول (Threshold)
أحياناً، قد تكون هناك اختلافات طفيفة جداً (بكسل واحد أو اثنين) بسبب طريقة عرض الخطوط (Anti-aliasing) بين بيئات التشغيل المختلفة. المقارنة الصارمة (0% اختلاف) قد تجعل اختباراتك هشة. معظم الأدوات تسمح بتحديد نسبة خطأ مقبولة.
// السماح باختلاف يصل إلى 0.2% من إجمالي البكسلات
await expect(page).toHaveScreenshot({ threshold: 0.2 });
ابدأ بقيمة صغيرة جداً وقم بزيادتها بحذر فقط عند الضرورة.
4. ادمج الاختبارات في مسار الـ CI/CD
قوة اختبار الانحدار البصري الحقيقية تظهر عندما يتم تشغيله آلياً مع كل طلب سحب (Pull Request). هذا يمنع الأخطاء البصرية من الوصول إلى الفرع الرئيسي للكود من الأساس. سيقوم المطور بإجراء تغيير، وفتح PR، وبعد دقائق يخبره النظام الآلي: “انتبه، تغييرك أثر على مظهر المكون X”. هذه هي شبكة الأمان الحقيقية.
الخلاصة: درع لا غنى عنه في العصر الحديث 🛡️
في عالم تطوير الواجهات الأمامية سريع التغير، لم يعد اختبار الانحدار البصري ترفاً، بل أصبح ضرورة. إنه الدرع الخفي الذي يحمي سمعة منتجك وتجربة المستخدم من تلك الأخطاء الصامتة والمحرجة التي لا تراها الاختبارات التقليدية.
نصيحتي الأخيرة لك: ابدأ صغيراً. لا تحاول تغطية كل شيء من اليوم الأول. اختر مكوناً واحداً مهماً أو صفحة رئيسية، وقم بتطبيق أول اختبار انحدار بصري لك. ستتفاجأ بكمية راحة البال التي ستجلبها لك هذه الشبكة الأمان الإضافية، وستتساءل كيف كنت تعمل بدونها.
وفقكم الله، والسلام عليكم.