يا جماعة الخير، السلام عليكم ورحمة الله وبركاته. معكم أخوكم أبو عمر.
قبل كم سنة، كنا شغالين على مشروع كبير ومهم، وكان فريقنا “طاير” في الإنجاز. الأكواد تنكتب، الميزات تتسلم، والكل مبسوط. قبل موعد الإطلاق بأسبوع، عملنا اجتماع أخير لنستعرض المنتج بشكله النهائي. فتحنا الموقع، والأمور تبدو تمام… للوهلة الأولى.
فجأة، واحد من فريق التسويق بيحكي: “لحظة، ليش زر ‘اشتر الآن’ في صفحة المنتجات لونه باهت وطالع فوق الوصف؟ مبارح كان تمام!”.
صار الكل يطلع على الشاشة، وفعلاً، الزر كان “سابح” في مكان غلط ولونه متغير. بلشنا رحلة البحث عن السبب. فريق الواجهة الأمامية (Frontend) أقسموا إنهم ما لمسوا هاي الصفحة من أسبوع. فريق الواجهة الخلفية (Backend) أكدوا إن البيانات سليمة. قضينا ساعات طويلة، والله يا جماعة ساعات، ونحنا بننبش في الكود، بنراجع آخر التغييرات (commits)، والتوتر بيزيد مع كل دقيقة بتمر.
بالآخر، وبعد ما قربنا نستسلم، اكتشفنا الكارثة. واحد من المطورين كان بيشتغل على صفحة “اتصل بنا” (صفحة مختلفة تماماً)، وغير تنسيق (style) عام لأحد العناصر في ملف CSS مشترك. التغيير كان بسيط، سطر واحد، لكنه أثّر بشكل غير متوقع على الزر في صفحة المنتجات اللي بتستخدم نفس التنسيق. كان خطأ خفي، بصري بحت، ما بيظهر في أي اختبار منطقي أو وحدة (Unit Test).
هذاك اليوم، أدركت إنه اعتمادنا على عيوننا بس لمراجعة كل شبر في التطبيق مع كل تغيير هو وصفة لكارثة محققة. ومن هنا، بلشت رحلتي مع عالم “الاختبار البصري التراجعي” أو الـ Visual Regression Testing.
ما هو هذا “الشبح” الذي يغير واجهاتنا؟
المشكلة اللي واجهناها الها اسم: الأخطاء البصرية التراجعية (Visual Regressions). هاي الأخطاء هي تغييرات غير مقصودة في واجهة المستخدم (UI) بتصير نتيجة تعديلات في الكود. ممكن تكون:
- عناصر متداخلة فوق بعضها.
- ألوان أو خطوط خاطئة.
- اختفاء أجزاء من الصفحة.
- تغير في التخطيط (Layout) على شاشات معينة.
المشكلة الأكبر إن أدوات الاختبار التقليدية (مثل اختبارات الوحدة أو التكامل) عمياء عن هاي الأخطاء. هي بتتأكد إن الدالة بترجع القيمة الصحيحة، أو إن الزر بيعمل الأكشن المطلوب، لكنها ما بتعرف “شكل” الزر كيف صار.
الاختبار البصري التراجعي: حارس المرمى لواجهة المستخدم
ببساطة شديدة، الاختبار البصري التراجعي هو عملية مؤتمتة بتقارن “صور” لواجهة المستخدم قبل وبعد التغيير، وبتنبهك لو في أي اختلاف. فكر فيها كأنك بتوظف روبوت عنده ذاكرة فوتوغرافية عشان يلعب معك لعبة “أوجد الفروقات” على كل صفحة في تطبيقك.
الآلية تبعته بتمر بخطوات بسيطة ومنطقية:
- الصورة المرجعية (Baseline): في المرة الأولى، بتاخذ “لقطة شاشة” (screenshot) للواجهة في حالتها السليمة والمثالية. هاي بنسميها الصورة المرجعية أو الأساس.
- التغيير والتصوير: بعد ما أنت أو أي حدا في الفريق يعمل تغييرات على الكود، النظام بيقوم بأخذ لقطة شاشة جديدة لنفس الواجهة.
- المقارنة: يتم مقارنة الصورة الجديدة بالصورة المرجعية بكسل ببكسل.
- التقرير: إذا ما في أي اختلاف، الاختبار بينجح. أما إذا وُجد اختلاف، الاختبار بيفشل وبيولد صورة ثالثة بتوضح لك الفروقات باللون الأحمر عشان تشوفها بوضوح.
- القرار البشري: هنا يأتي دورك. بتراجع الاختلافات، وبتقرر:
- إذا كان التغيير مقصودًا: بتقبل التغيير وبتحدّث الصورة المرجعية لتصير هي الأساس الجديد.
- إذا كان التغيير خطأ (Bug): بترفض التغيير وبترجع الكود للمطور ليصلحه.
كيف نبدأ مع الاختبار البصري؟ الأدوات والخطوات العملية
الحكي النظري حلو، بس خلينا نشوف كيف بنطبق هالكلام على أرض الواقع. اليوم السوق مليان أدوات، منها المجاني ومنها المدفوع. من أشهرها: Playwright, Cypress (مع إضافات), Storybook, Percy, و Applitools.
شخصيًا، أنا بميل لاستخدام Playwright لأنه أداة متكاملة من مايكروسوفت بتدعم هذا النوع من الاختبارات بشكل أساسي وبدون إضافات معقدة.
الخطوة الأولى: إعداد بيئة Playwright
إذا ما عندك Playwright، إعداده بسيط جداً. افتح الطرفية (Terminal) في مشروعك واكتب:
npm init playwright@latest
هذا الأمر رح ينزل كل شي بتحتاجه ويهيئ لك ملفات الإعدادات الأساسية.
الخطوة الثانية: كتابة أول اختبار بصري
لنفترض عنا صفحة رئيسية وبدنا نتأكد إنها ما بتتغير بشكل غير متوقع. بنعمل ملف اختبار جديد، مثلاً tests/homepage.spec.js، وبنكتب فيه الكود التالي:
// tests/homepage.spec.js
const { test, expect } = require('@playwright/test');
test('الصفحة الرئيسية يجب أن تبدو كما هي', async ({ page }) => {
// 1. اذهب إلى الصفحة المطلوبة
await page.goto('http://localhost:3000');
// 2. التقط صورة وقارنها بالمرجعية
// اسم اللقطة هنا هو 'homepage-screenshot.png'
await expect(page).toHaveScreenshot('homepage-screenshot.png');
});
شرح الكود:
page.goto(...): بتوجه المتصفح الافتراضي للعنوان اللي بدنا نختبره.expect(page).toHaveScreenshot(...): هاي هي الجوهرة. هذا السطر هو اللي بيقوم بكل السحر.
الخطوة الثالثة: تشغيل الاختبار وفهم النتائج
لما تشغل الاختبار لأول مرة، Playwright رح يشوف إنه ما في صورة مرجعية اسمها homepage-screenshot.png، فرح يقوم بإنشاء واحدة وحفظها في مجلد خاص (عادة اسمه tests/homepage.spec.js-snapshots). ورح ينجح الاختبار.
الآن، روح غير أي شي في الصفحة الرئيسية، مثلاً لون الخلفية أو حجم الخط، وشغل الاختبار مرة ثانية:
npx playwright test
هالمرة، الاختبار رح يفشل! والجميل في الموضوع إن Playwright رح يعطيك تقرير مفصل. رح تلاقي 3 صور في مجلد النتائج:
- actual.png: الصورة الجديدة بعد التغيير.
- expected.png: الصورة المرجعية القديمة.
- diff.png: صورة بتوضح الفروقات بين الصورتين باللون الأحمر.
هذه الصورة “diff” هي أفضل صديق لك. في ثوانٍ معدودة، بتكشف لك الخطأ اللي كان ممكن ياخذ منك ساعات من البحث.
الخطوة الرابعة: تحديث الصور المرجعية
طيب، لو كان التغيير اللي عملته مقصود؟ مثلاً، غيرنا تصميم الهيدر بشكل رسمي. في هاي الحالة، بدنا نحدّث الصورة المرجعية. بنعمل هالشي بأمر بسيط:
npx playwright test --update-snapshots
هذا الأمر رح ياخذ اللقطات الجديدة ويعتمدها كالصور المرجعية للمرات القادمة.
نصائح من خبرة “أبو عمر”: كيف تتجنب الفوضى؟
تطبيق الاختبار البصري ممكن يتحول لفوضى إذا ما تم بشكل صحيح. هاي شوية نصائح من القلب عشان تبدأ بداية صحيحة:
- ابدأ صغيراً وتدريجياً: لا تحاول تغطية كل التطبيق من أول يوم. ابدأ بصفحة واحدة مهمة أو مكون حرج (Critical Component). “إشي أحسن من ولا إشي”. بعد ما تستقر العملية، توسع شوي شوي.
-
تعامل مع المحتوى الديناميكي: ماذا عن التاريخ والوقت، أو إعلانات بتتغير، أو محتوى بيجيبه المستخدم؟ هاي الأمور رح تسبب فشل دائم للاختبارات. الحل هو “إخفاء” (masking) هاي الأجزاء. Playwright بيسمح لك تعمل هالشي بسهولة:
// إخفاء عنصر له id معين قبل أخذ اللقطة await expect(page).toHaveScreenshot('my-screenshot.png', { mask: [page.locator('#dynamic-content')] }); -
حدد “هامش الخطأ” (Threshold): أحياناً، بيكون في اختلافات طفيفة جداً بين أنظمة التشغيل أو كروت الشاشة في طريقة عرض الخطوط (anti-aliasing). عشان تتجنب فشل الاختبار بسبب هاي الفروقات اللي ما بتُرى بالعين المجردة، ممكن تحدد نسبة سماح بسيطة:
// السماح باختلاف يصل إلى 5% من إجمالي البكسلات await expect(page).toHaveScreenshot('my-screenshot.png', { maxDiffPixelRatio: 0.05 }); - أتمتة العملية مع CI/CD: القوة الحقيقية بتظهر لما تدمج هاي الاختبارات مع نظام التكامل المستمر (CI/CD) مثل GitHub Actions. مع كل Pull Request جديد، الاختبارات البصرية بتشتغل تلقائياً. وإذا فشلت، بيتم رفع صور الفروقات (diffs) كـ “artifacts” ليراجعها الفريق قبل دمج الكود. هذا بيمنع وصول الأخطاء البصرية للفرع الرئيسي من الأساس.
الخلاصة: درهم وقاية خيرٌ من قنطار علاج
يا جماعة، الاستثمار في الاختبار البصري التراجعي مش رفاهية، بل هو ضرورة في عالم تطوير الويب الحديث. هو بمثابة شبكة أمان بتحمي تجربة المستخدم، وبتوفر على فريقك ساعات لا تحصى من التنبيش عن أخطاء خبيثة. صحيح، إعداده في البداية بيحتاج شوية وقت وجهد، لكن العائد على المدى الطويل ضخم جداً: ثقة أكبر عند إطلاق التحديثات، نوم أهنأ في ليلة الإطلاق، ومنتجات بجودة بصرية ثابتة وممتازة.
نصيحتي الأخيرة: لا تنتظروا “كارثة الزر السابح” تصير معكم. ابدأوا اليوم، ولو بخطوة صغيرة. جربوا أداة مثل Playwright على مكون واحد، وشوفوا الفرق بنفسكم. 😉