يا أهلاً وسهلاً فيكم جميعاً، معكم أخوكم أبو عمر.
اسمحوا لي أن أبدأ بقصة قصيرة حدثت معنا قبل سنوات، قصة لا تزال تفاصيلها محفورة في ذاكرتي. كنا نعمل على مشروع ضخم لأحد العملاء، منصة تجارة إلكترونية متكاملة. اقترب موعد الإطلاق الكبير، والضغط كان “للرُكب”. في ليلة خميس، وقبل الإطلاق بيومين، لاحظ أحد المطورين مشكلة بسيطة في محاذاة أيقونة صغيرة في صفحة المنتج على متصفح فايرفوكس. “شغلة بسيطة، سطر CSS وبتمشي”، قالها بثقة.
قام بالتعديل، فتح طلب دمج (Pull Request)، نظرنا إلى الكود، سطر واحد فقط! بدا التغيير بريئاً جداً. دمجنا التغيير ونشرناه على البيئة التجريبية (Staging). كل الاختبارات الآلية الوظيفية نجحت، والفريق كله أعطى الضوء الأخضر. “يلا يا شباب، توكلنا على الله”.
في صباح اليوم التالي، استيقظنا على جحيم حقيقي. رسائل من العميل، تنبيهات من نظام المراقبة، الدنيا ولعت! زر “أضف إلى السلة”، أهم زر في الموقع بأكمله، اختفى تماماً على جميع أجهزة الآيفون ومتصفح سفاري. نعم، اختفى! سطر الـ CSS البريء ذاك، الذي أصلح محاذاة أيقونة، تسبب في تعارض مع خاصية أخرى أدت إلى إخفاء الزر بالكامل في بيئة WebKit.
تخيلوا الموقف، يا جماعة. ساعات من البحث المحموم، والضغط النفسي، ومحاولة فهم “يا زلمة شو اللي صار؟”. في النهاية، وجدنا المشكلة وأصلحناها، لكن الضرر قد وقع. خسر العميل مبيعات محتملة، وفقدنا نحن جزءاً من ثقته، والأهم، فقدنا ثقتنا في عملية النشر الخاصة بنا. كل تحديث جديد أصبح مصحوباً بسؤال مرعب: “هل كسرنا شيئاً آخر هذه المرة؟”. كانت تحديثات الواجهة الأمامية أشبه بلعبة الروليت الروسية. هذه الحادثة كانت نقطة التحول التي دفعتنا للبحث عن حل جذري، وهذا الحل كان: اختبار التراجع البصري (Visual Regression Testing).
ما هو اختبار التراجع البصري (Visual Regression Testing)؟
ببساطة شديدة، اختبار التراجع البصري هو عملية مؤتمتة لالتقاط صور لواجهات المستخدم الخاصة بك ومقارنتها مع صور “مرجعية” تم اعتمادها مسبقاً. إذا حدث أي اختلاف بصري بين الصورة الجديدة والصورة المرجعية، مهما كان صغيراً، يفشل الاختبار ويتم تنبيه الفريق.
ليس مجرد اختبار وظيفي أو اختبار طرفي (End-to-End)
قد يقول قائل: “يا أبو عمر، نحن نستخدم Cypress أو Playwright بالفعل لكتابة اختبارات E2E، أليس هذا كافياً؟”. والجواب هو: لا، ليس كافياً تماماً.
دعوني أوضح الفرق بمثال:
- الاختبار الوظيفي (Functional Test): يتأكد من أن زر “أضف إلى السلة” يعمل. أي عندما تضغط عليه، يتم إضافة المنتج إلى السلة. يمكنه أن يجد الزر في شجرة DOM ويتفاعل معه حتى لو كان الزر غير مرئي للمستخدم (مثلاً، `opacity: 0` أو مخفي خلف عنصر آخر).
- اختبار التراجع البصري (Visual Test): يتأكد من أن زر “أضف إلى السلة” يظهر بالشكل الصحيح. هل هو في مكانه؟ هل لونه صحيح؟ هل حجمه مناسب؟ هل هو مرئي أصلاً؟
في قصتنا، الاختبارات الوظيفية كانت كلها ناجحة لأن الزر كان موجوداً في الـ DOM ويمكن التفاعل معه برمجياً، لكنه كان غير مرئي للمستخدم. اختبار التراجع البصري كان سيكتشف هذه المشكلة في ثوانٍ لأنه كان سيلاحظ أن “صورة” الصفحة تغيرت بشكل جذري (اختفاء عنصر مهم).
رحلتنا من الفوضى إلى النظام: كيف بدأنا؟
بعد حادثة “الزر المختفي”، قررنا أن نتبنى اختبار التراجع البصري كجزء لا يتجزأ من عمليتنا. إليكم الخطوات التي اتبعناها.
الخطوة الأولى: اختيار الأداة المناسبة
السوق مليء بالأدوات، منها ما هو مفتوح المصدر ومنها ما هو مدفوع. درسنا عدة خيارات مثل Percy, Applitools, BackstopJS، ولكن في النهاية استقر رأينا على استخدام الإمكانيات المدمجة في Playwright.
لماذا Playwright؟
- مدمج وأصيل: خاصية مقارنة الصور (`toHaveScreenshot`) تأتي مدمجة معه، لا حاجة لتثبيت إضافات معقدة.
- دعم شامل للمتصفحات: يتيح لنا اختبار واجهاتنا على Chromium (Chrome, Edge), Firefox, و WebKit (Safari) بنفس الكود، وهذا كان سيحل مشكلتنا الأصلية.
- تحكم كامل: نحن ندير الصور المرجعية ونخزنها في مستودع الكود (Git repository)، مما يعطينا تحكماً كاملاً في العملية.
الخطوة الثانية: إعداد البيئة وإنشاء اللقطات المرجعية (Baseline)
البداية كانت بسيطة. قمنا بإنشاء ملف اختبار جديد يستهدف الصفحة الرئيسية. الهدف هو التقاط صورة “ذهبية” أو مرجعية (Baseline) لهذه الصفحة.
إليكم مثال بسيط جداً باستخدام Playwright و TypeScript:
// file: tests/visual.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Visual Regression Tests', () => {
test('Homepage should look the same', async ({ page }) => {
// 1. اذهب إلى الصفحة المطلوبة
await page.goto('/');
// 2. التقط صورة للشاشة الكاملة وقارنها بالمرجع
// اسم اللقطة هو 'homepage-landing.png'
await expect(page).toHaveScreenshot('homepage-landing.png', {
fullPage: true, // تأكد من أخذ لقطة للصفحة كاملة
maxDiffPixels: 100 // السماح باختلاف بسيط جداً (سنتحدث عنه لاحقاً)
});
});
});
عند تشغيل هذا الاختبار لأول مرة، يقوم Playwright بعمل سحري:
- يلاحظ أنه لا توجد صورة مرجعية باسم `homepage-landing.png`.
- يلتقط صورة للشاشة الحالية ويحفظها كصورة مرجعية في مجلد خاص (عادة `tests/visual.spec.ts-snapshots`).
- ينجح الاختبار.
الآن، أصبح لدينا أول صورة مرجعية معتمدة. قمنا بإضافة هذه الصورة إلى Git، وبذلك أصبحت جزءاً من تاريخ المشروع.
السحر يبدأ: كيف يعمل اختبار التراجع البصري في الممارسة؟
الجمال الحقيقي لهذه التقنية يظهر عندما تصبح جزءاً من دورة حياة التطوير اليومية، خصوصاً ضمن طلبات الدمج (Pull Requests).
دورة حياة التغيير البرمجي الجديدة
تخيل السيناريو التالي:
- مطور يقوم بتعديل على ملف CSS لتغيير لون الأزرار الرئيسية.
- يفتح طلب دمج (Pull Request).
- بشكل آلي، يقوم نظام التكامل المستمر (CI) مثل GitHub Actions بتشغيل مجموعة الاختبارات، بما فيها اختبارات التراجع البصري.
- يقوم Playwright بالذهاب إلى الصفحة الرئيسية والتقاط صورة جديدة.
- يقارن الصورة الجديدة بالصورة المرجعية `homepage-landing.png` بكسل مقابل بكسل.
عندما تسوء الأمور: تحليل الاختلافات
في هذه الحالة، بما أن لون الأزرار تغير، سيفشل الاختبار. لكن Playwright لا يكتفي بإخبارك أن الاختبار فشل، بل يقدم لك تقريراً مفصلاً يحتوي على ثلاث صور:
- الصورة المتوقعة (Expected): الصورة المرجعية القديمة.
- الصورة الفعلية (Actual): الصورة الجديدة التي تم التقاطها.
- صورة الاختلاف (Diff): وهي الأهم! صورة تظهر لك الواجهة باهتة، مع تلوين المناطق التي اختلفت باللون الأحمر الفاقع.
هذه الصورة (Diff) هي بمثابة المنقذ. في لمحة بصر، يمكنك أن ترى بالضبط ما الذي تغير. هل هو مجرد لون الزر الذي قصدت تغييره؟ أم أن هناك عنصراً آخر تحرك من مكانه عن طريق الخطأ؟
هل التغيير مقصود أم خطأ؟
الآن، أمام المطور خياران:
- تغيير غير مقصود (Bug): يرى المطور أن تغييره تسبب في مشكلة أخرى لم يكن يتوقعها (مثلاً، النص داخل الزر خرج عن حدوده). هنا يقول “أوبس!” ويعود لإصلاح الكود. هذا هو جوهر العملية: اكتشاف الأخطاء قبل وصولها للمستخدم.
- تغيير مقصود (Intentional Change): التغيير في لون الزر كان مقصوداً ومتوقعاً. في هذه الحالة، كل ما على المطور فعله هو تحديث الصورة المرجعية لتصبح الصورة الجديدة هي المعتمدة. يتم ذلك بتشغيل أمر بسيط:
npx playwright test --update-snapshotsبعد تشغيل هذا الأمر، يقوم المطور بإضافة الصور المحدثة إلى طلب الدمج الخاص به، وبذلك يوثق التغيير البصري ويصبح هو المرجع للمستقبل.
نصائح من الخبير (من كيس أبو عمر)
تبني هذه التقنية ليس مجرد كتابة كود، بل هو تغيير في ثقافة العمل. إليكم بعض النصائح العملية من تجربتنا:
ابدأ صغيراً، ولكن ابدأ الآن
لا تحاول تغطية 100% من تطبيقك دفعة واحدة، فهذا محبط ومستحيل. ابدأ بالصفحات والمكونات الأكثر أهمية وحساسية:
- الصفحة الرئيسية (Homepage)
- صفحات تسجيل الدخول وإنشاء الحساب
- مسار الدفع والشراء (Checkout Flow)
- المكونات الرئيسية المشتركة (Header, Footer, Buttons)
مع الوقت، يمكنك توسيع التغطية لتشمل المزيد من الصفحات.
تعامل مع المحتوى الديناميكي بذكاء
أكبر تحدٍ في اختبارات التراجع البصري هو المحتوى المتغير: التواريخ، أسماء المستخدمين، العدادات، الإعلانات. هذه العناصر ستؤدي إلى فشل الاختبارات دائماً. الحل هو “إخفاء” أو “تجاهل” هذه العناصر قبل التقاط الصورة.
يوفر Playwright خيار mask الرائع لهذا الغرض:
test('User profile page', async ({ page }) => {
await page.goto('/profile/abu-omar');
// أخبر Playwright أن يتجاهل العنصر الذي يعرض تاريخ آخر تسجيل دخول
// سيقوم بتغطيته بمربع وردي اللون قبل التقاط الصورة
await expect(page).toHaveScreenshot('user-profile.png', {
mask: [page.locator('.user-last-login-time')]
});
});
بهذه الطريقة، نختبر ثبات التصميم دون التأثر بالبيانات المتغيرة.
عتبة الحساسية (Threshold) هي صديقك… ولكن بحذر
أحياناً، قد تكون هناك اختلافات طفيفة جداً بين بيئة التطوير وبيئة الـ CI (مثلاً في طريقة عرض الخطوط – Anti-aliasing). هذا قد يسبب فشل الاختبارات بسبب اختلاف بكسل واحد أو اثنين.
يمكنك استخدام خيار مثل maxDiffPixels أو threshold للسماح بنسبة صغيرة جداً من الاختلاف. لكن استخدمه بحذر شديد! إذا جعلت النسبة كبيرة، فقد تتسلل الأخطاء البصرية الصغيرة دون أن تلاحظها. ابدأ بقيمة صغيرة جداً وزدها عند الضرورة القصوى فقط.
اجعل الاختبارات جزءاً من ثقافة الفريق
النجاح الحقيقي لا يأتي من الأداة، بل من الفريق. تأكد من أن:
- الجميع يفهم العملية: المطورون، مهندسو الجودة، وحتى المصممون.
- التقارير سهلة الوصول: يجب أن تكون تقارير الاختبارات مرئية للجميع.
- المراجعة مسؤولية جماعية: عند فشل اختبار بصري، يجب أن يشارك في المراجعة أكثر من شخص للتأكد من أن التغيير مقصود ومقبول من ناحية التصميم وتجربة المستخدم.
الخلاصة: من الروليت الروسية إلى راحة البال 🧘
رحلتنا مع اختبار التراجع البصري حولت عملية تطوير الواجهات الأمامية من مصدر دائم للقلق والخوف إلى عملية منظمة وموثوقة. لم نعد نخشى الضغط على زر “النشر”. بل على العكس، أصبحنا أكثر جرأة على التطوير والتحسين بسرعة، لأننا نملك شبكة أمان قوية تلتقط الأخطاء البصرية التي لا تراها العين المجردة أو الاختبارات الأخرى.
نصيحتي الأخيرة لك: لا تنتظر حتى تحترق طبختك (أو يختفي زر مهم من موقعك) لتتعلم الدرس. ابدأ اليوم، ولو باختبار واحد لصفحة واحدة. استثمر قليلاً من الوقت الآن، وستوفر على نفسك وفريقك ساعات طويلة من تصحيح الأخطاء المحرجة في المستقبل. صدقني، نومك في الليل سيصبح أعمق بكثير. بالتوفيق يا أصدقائي!