“يا زلمة، بس غيرت لون الزر!” – قصة كادت أن تكلفنا الكثير
أذكرها وكأنها البارحة. كنا في المراحل النهائية لإطلاق منصة تجارة إلكترونية كبيرة لأحد العملاء. الأجواء كانت مشحونة، القهوة لا تفارق مكاتبنا، وساعات النوم أصبحت عملة نادرة. في ليلة ما قبل التسليم المبدئي، لاحظ مدير المنتج أن أحد أزرار الدعوة لاتخاذ إجراء (Call to Action) لونه لا يتطابق 100% مع هوية العلامة التجارية.
تطوع أحد المطورين الشباب لإصلاح الأمر. قال بثقة: “ولا يهمك، إشي بسيط. كلها خاصية background-color في الـ CSS”. خلال دقائق، قام بالتغيير، دفعه إلى المستودع (repository)، وتم دمجه بعد مراجعة سريعة للكود الذي بدا بريئًا تمامًا. الكل تنفس الصعداء، وأغلقنا أجهزتنا ونحن نحلم بيوم الإطلاق الناجح.
في صباح اليوم التالي، استيقظت على وابل من الرسائل. فتحت رابط النسخة التجريبية على هاتفي لأجد الكارثة: قائمة التنقل الرئيسية (Navigation bar) في نسخة الموبايل كانت محطمة بالكامل. العناصر فوق بعضها، والروابط لا تظهر… فوضى عارمة. بدأ السباق المحموم مع الزمن، الكل يصرخ “مين آخر واحد عمل push؟”. توجهت أصابع الاتهام بسرعة إلى ذلك التغيير “البسيط” في لون الزر.
بعد تحليل عميق، اكتشفنا أن الكلاس الذي تم تعديله لم يكن مخصصًا للزر فقط، بل كان كلاسًا عامًا (utility class) يُستخدم في أماكن أخرى، بما في ذلك بعض العناصر داخل قائمة التنقل. هذا التغيير الصغير أحدث تأثيرًا متتاليًا (cascading effect) لم يتوقعه أحد، ولم يلحظه أحد أثناء المراجعة اليدوية السريعة. في تلك اللحظة، أدركت أن طريقتنا في التعامل مع الواجهات الأمامية كانت خاطئة بشكل أساسي. نحن بحاجة إلى عيون لا تنام، عيون آلية تخبرنا “انتبه، لقد تغير شيء ما بصريًا!”. هنا بدأت رحلتنا مع ما يُعرف بـ “الاختبار البصري التراجعي”.
لماذا التغييرات الصغيرة في CSS هي الأخطر؟
في عالم تطوير الواجهات الخلفية، عندما ترتكب خطأ، غالبًا ما يصرخ النظام في وجهك. تحصل على خطأ 500، استثناء (exception) واضح، أو اختبار فاشل يخبرك بالضبط أي وظيفة قد تعطلت. لكن عالم الـ CSS مختلف، إنه عالم صامت ومخادع.
- الطبيعة المتتالية (Cascading): تعديل بسيط في عنصر أب يمكن أن يدمر تصميم عشرات العناصر الأبناء دون أن تدري.
- حروب التخصيص (Specificity Wars): قد تكتب قاعدة CSS جديدة تظن أنها ستطبق على عنصر معين، لتكتشف أن قاعدة أخرى أقدم وأكثر تحديدًا هي التي لها الغلبة.
- الأنماط العامة (Global Styles): تغيير بسيط في
bodyأو*يمكن أن يغير شكل الموقع بأكمله بطرق غير متوقعة.
المشكلة أن هذه الأخطاء “بصرية” بحتة. لا يوجد انهيار في الخادم، ولا يوجد سجل أخطاء. الطريقة الوحيدة لاكتشافها هي بالنظر. ولكن من لديه الوقت لمراجعة كل صفحة وكل مكون في كل مرة يتم فيها دمج تغيير بسيط في الـ CSS؟ هذا عمل غير واقعي ومُهدر للوقت. أنت بحاجة إلى حارس آلي.
الاختبار البصري التراجعي (Visual Regression Testing): حارس الواجهات الأمامية
ببساطة، الاختبار البصري التراجعي هو عملية آلية لمقارنة كيف “تبدو” واجهة المستخدم الخاصة بك قبل وبعد التغيير. تخيلها كلعبة “أوجد الفروقات” ولكن الكمبيوتر هو الذي يلعبها بسرعة فائقة وبدقة متناهية.
كيف يعمل هذا السحر؟
- التقاط الصورة المرجعية (Baseline): في المرة الأولى التي تشغل فيها الاختبار على مكون أو صفحة، يتم التقاط “لقطة شاشة” (screenshot) لها وحفظها كصورة مرجعية أساسية. هذه هي النسخة “الصحيحة” والموافق عليها من التصميم.
- التقاط الصورة الجديدة: بعد إجراء تعديلات على الكود (مثلاً، تغيير CSS)، يتم تشغيل الاختبار مرة أخرى، فيلتقط لقطة شاشة جديدة لنفس المكون في نفس الظروف.
- المقارنة الدقيقة: يقوم البرنامج بمقارنة الصورة الجديدة بالصورة المرجعية بكسل ببكسل.
- إظهار الاختلافات: إذا وجد أي اختلاف، يفشل الاختبار ويقوم بإنشاء صورة ثالثة تسلط الضوء على المناطق المختلفة باللون الأحمر.
- القرار البشري: هنا يأتي دورك. تنظر إلى الاختلافات وتقرر:
- هل هذا التغيير مقصود؟ (مثلاً، قمت بتغيير لون الزر عن قصد). إذا كان كذلك، فأنت توافق على التغيير، وتصبح الصورة الجديدة هي الصورة المرجعية للمستقبل.
- هل هذا خطأ غير مقصود؟ (مثل ما حدث معنا في قصة قائمة التنقل). إذا كان كذلك، فهذا يعني أنك اكتشفت “علّة” أو bug، وعليك العودة وإصلاح الكود.
كيف نبدأ؟ رحلة عملية مع أداة Playwright
هناك العديد من الأدوات الرائعة مثل Percy، Applitools، و Cypress مع إضافات. ولكنني شخصيًا أحب البدء مع Playwright من مايكروسوفت، لأنه إطار عمل متكامل للاختبارات ويأتي مع دعم مدمج للاختبار البصري، مما يجعل البداية سهلة جدًا.
الخطوة الأولى: تجهيز البيئة
افتح الطرفية (Terminal) في مشروعك وقم بتثبيت Playwright:
npm init playwright@latest
سيقوم هذا الأمر بتوجيهك عبر عملية إعداد بسيطة، وإنشاء ملف الإعدادات playwright.config.ts وبعض الأمثلة.
الخطوة الثانية: كتابة أول اختبار بصري
لنفترض أن لدينا صفحة بسيطة تحتوي على زر نريد اختباره. سنقوم بإنشاء ملف اختبار جديد، وليكن اسمه ui.spec.ts.
import { test, expect } from '@playwright/test';
test('example button should look correct', async ({ page }) => {
// 1. اذهب إلى صفحتك أو المكون الذي تريد اختباره
await page.goto('http://localhost:3000/my-page');
// 2. حدد العنصر الذي تريد التقاط صورة له
const myButton = page.locator('#buy-now-button');
// 3. قم بتأكيد أن لقطة الشاشة للعنصر تطابق الصورة المرجعية
await expect(myButton).toHaveScreenshot('buy-now-button.png');
});
عند تشغيل هذا الاختبار لأول مرة باستخدام الأمر npx playwright test، سيقوم Playwright بإعلامك بأنه لا توجد صورة مرجعية، وسيقوم بإنشاء واحدة لك باسم buy-now-button.png-snapshots. هذه هي الصورة المرجعية.
الخطوة الثالثة: محاكاة الخطأ ورؤية السحر
الآن، اذهب إلى ملف الـ CSS الخاص بك وقم بتغيير شيء ما في الزر #buy-now-button. مثلاً، غير الحاشية (padding) من 10px إلى 15px.
شغل الاختبار مرة أخرى: npx playwright test
هذه المرة، سيفشل الاختبار! 💥 سيخبرك Playwright أن الصورة الجديدة لا تتطابق مع الصورة المرجعية. والأجمل من ذلك، أنه سيقوم بإنشاء تقرير HTML يمكنك فتحه في المتصفح. ستجد في التقرير ثلاث صور:
- Expected: الصورة المرجعية القديمة.
- Actual: الصورة الجديدة بعد التغيير.
- Diff: صورة تسلط الضوء على الاختلافات باللون الأحمر.
هذه الصورة “Diff” هي المنقذ. في ثوانٍ، تريك بالضبط أثر التغيير الذي قمت به، سواء كان مقصودًا أم لا. لقد انتهى عصر التخمين والبحث لساعات.
نصيحة من أبو عمر: إعدادات مهمة لتجنب النتائج الخاطئة
قد تفشل الاختبارات أحيانًا لأسباب غير متعلقة بالكود، مثل طريقة عرض الخطوط المختلفة بين الأنظمة. إليك بعض الإعدادات في ملف playwright.config.ts لتجعل اختباراتك أكثر استقرارًا:
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
// إعدادات المقارنة البصرية
toHaveScreenshot: {
// نسبة التفاوت المسموح بها (مثلاً، 0.2%) للتعامل مع الفروقات الطفيفة في العرض
maxDiffPixels: 100,
threshold: 0.2
},
},
use: {
// شغل الاختبارات دائمًا بدون واجهة رسومية لضمان الاتساق
headless: true,
},
});
أيضًا، إذا كان لديك محتوى ديناميكي (مثل تاريخ اليوم أو إعلان متغير)، يمكنك إخفاؤه قبل التقاط الصورة باستخدام خاصية mask:
await expect(page).toHaveScreenshot({ mask: [page.locator('#dynamic-date')] });
دمج الاختبار البصري في روتين العمل اليومي (CI/CD)
قوة الاختبار الحقيقية تظهر عندما يتم تشغيله تلقائيًا مع كل طلب دمج (Pull Request). هذا يمنع الأخطاء البصرية من الوصول إلى الفرع الرئيسي من الأساس.
إليك مثال بسيط لملف GitHub Action يقوم بذلك:
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/
retention-days: 7
مع هذا الإعداد، أي تغيير في الكود سيؤدي إلى تشغيل الاختبارات البصرية تلقائيًا. إذا فشل أي اختبار، سيتم إعلامك مباشرة في طلب الدمج، ويمكنك رؤية تقرير الاختلافات قبل أن توافق على دمج الكود. لقد حولنا عملية “البحث عن الخطأ” من مهمة مزعجة بعد الدمج إلى خطوة وقائية سهلة أثناء المراجعة.
الخلاصة: من الفوضى إلى الثقة 😌
تبني الاختبار البصري التراجعي لم يكن مجرد إضافة أداة جديدة، بل كان تغييرًا في العقلية. لقد انتقلنا من الخوف من تعديل الـ CSS إلى الثقة في إجراء تحسينات وإعادة هيكلة (refactoring) دون القلق من كسر شيء ما في مكان آخر بصمت.
نعم، يتطلب الأمر بعض الإعداد في البداية، وعليك أن تتعلم كيفية إدارة الصور المرجعية (أنصح بتخزينها مع الكود باستخدام Git LFS إذا كبر حجمها). لكن العائد على هذا الاستثمار هائل: ساعات لا تحصى من وقت المطورين تم توفيرها، جودة المنتج أصبحت أعلى، والنقاشات تحولت من “مين خرب التصميم؟” إلى “كيف يمكننا تحسين هذه التجربة للمستخدم؟”.
نصيحتي الأخيرة لك: لا تنتظر حتى تحدث الكارثة. ابدأ اليوم، ولو باختبار مكون واحد حرج فقط مثل الهيدر أو زر رئيسي. سيوفر عليك هذا الإجراء البسيط ساعات من الصداع والبحث عن أخطاء لاحقًا. صدقني، قهوتك الصباحية ستصبح ألذ بكثير بدون ذاك السؤال المرعب: “يا جماعة، شو اللي تغير في الواجهة؟”.