وداعاً لكابوس “هل كسرنا شيئاً؟”: كيف أنقذ اختبار التراجع البصري واجهاتنا الأمامية

يا أهلاً وسهلاً فيكم جميعاً، معكم أخوكم أبو عمر.

اسمحوا لي أن أبدأ بقصة قصيرة حدثت معنا قبل سنوات، قصة لا تزال تفاصيلها محفورة في ذاكرتي. كنا نعمل على مشروع ضخم لأحد العملاء، منصة تجارة إلكترونية متكاملة. اقترب موعد الإطلاق الكبير، والضغط كان “للرُكب”. في ليلة خميس، وقبل الإطلاق بيومين، لاحظ أحد المطورين مشكلة بسيطة في محاذاة أيقونة صغيرة في صفحة المنتج على متصفح فايرفوكس. “شغلة بسيطة، سطر 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 بعمل سحري:

  1. يلاحظ أنه لا توجد صورة مرجعية باسم `homepage-landing.png`.
  2. يلتقط صورة للشاشة الحالية ويحفظها كصورة مرجعية في مجلد خاص (عادة `tests/visual.spec.ts-snapshots`).
  3. ينجح الاختبار.

الآن، أصبح لدينا أول صورة مرجعية معتمدة. قمنا بإضافة هذه الصورة إلى Git، وبذلك أصبحت جزءاً من تاريخ المشروع.

السحر يبدأ: كيف يعمل اختبار التراجع البصري في الممارسة؟

الجمال الحقيقي لهذه التقنية يظهر عندما تصبح جزءاً من دورة حياة التطوير اليومية، خصوصاً ضمن طلبات الدمج (Pull Requests).

دورة حياة التغيير البرمجي الجديدة

تخيل السيناريو التالي:

  1. مطور يقوم بتعديل على ملف CSS لتغيير لون الأزرار الرئيسية.
  2. يفتح طلب دمج (Pull Request).
  3. بشكل آلي، يقوم نظام التكامل المستمر (CI) مثل GitHub Actions بتشغيل مجموعة الاختبارات، بما فيها اختبارات التراجع البصري.
  4. يقوم Playwright بالذهاب إلى الصفحة الرئيسية والتقاط صورة جديدة.
  5. يقارن الصورة الجديدة بالصورة المرجعية `homepage-landing.png` بكسل مقابل بكسل.

عندما تسوء الأمور: تحليل الاختلافات

في هذه الحالة، بما أن لون الأزرار تغير، سيفشل الاختبار. لكن Playwright لا يكتفي بإخبارك أن الاختبار فشل، بل يقدم لك تقريراً مفصلاً يحتوي على ثلاث صور:

  • الصورة المتوقعة (Expected): الصورة المرجعية القديمة.
  • الصورة الفعلية (Actual): الصورة الجديدة التي تم التقاطها.
  • صورة الاختلاف (Diff): وهي الأهم! صورة تظهر لك الواجهة باهتة، مع تلوين المناطق التي اختلفت باللون الأحمر الفاقع.

هذه الصورة (Diff) هي بمثابة المنقذ. في لمحة بصر، يمكنك أن ترى بالضبط ما الذي تغير. هل هو مجرد لون الزر الذي قصدت تغييره؟ أم أن هناك عنصراً آخر تحرك من مكانه عن طريق الخطأ؟

هل التغيير مقصود أم خطأ؟

الآن، أمام المطور خياران:

  1. تغيير غير مقصود (Bug): يرى المطور أن تغييره تسبب في مشكلة أخرى لم يكن يتوقعها (مثلاً، النص داخل الزر خرج عن حدوده). هنا يقول “أوبس!” ويعود لإصلاح الكود. هذا هو جوهر العملية: اكتشاف الأخطاء قبل وصولها للمستخدم.
  2. تغيير مقصود (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 للسماح بنسبة صغيرة جداً من الاختلاف. لكن استخدمه بحذر شديد! إذا جعلت النسبة كبيرة، فقد تتسلل الأخطاء البصرية الصغيرة دون أن تلاحظها. ابدأ بقيمة صغيرة جداً وزدها عند الضرورة القصوى فقط.

اجعل الاختبارات جزءاً من ثقافة الفريق

النجاح الحقيقي لا يأتي من الأداة، بل من الفريق. تأكد من أن:

  • الجميع يفهم العملية: المطورون، مهندسو الجودة، وحتى المصممون.
  • التقارير سهلة الوصول: يجب أن تكون تقارير الاختبارات مرئية للجميع.
  • المراجعة مسؤولية جماعية: عند فشل اختبار بصري، يجب أن يشارك في المراجعة أكثر من شخص للتأكد من أن التغيير مقصود ومقبول من ناحية التصميم وتجربة المستخدم.

الخلاصة: من الروليت الروسية إلى راحة البال 🧘

رحلتنا مع اختبار التراجع البصري حولت عملية تطوير الواجهات الأمامية من مصدر دائم للقلق والخوف إلى عملية منظمة وموثوقة. لم نعد نخشى الضغط على زر “النشر”. بل على العكس، أصبحنا أكثر جرأة على التطوير والتحسين بسرعة، لأننا نملك شبكة أمان قوية تلتقط الأخطاء البصرية التي لا تراها العين المجردة أو الاختبارات الأخرى.

نصيحتي الأخيرة لك: لا تنتظر حتى تحترق طبختك (أو يختفي زر مهم من موقعك) لتتعلم الدرس. ابدأ اليوم، ولو باختبار واحد لصفحة واحدة. استثمر قليلاً من الوقت الآن، وستوفر على نفسك وفريقك ساعات طويلة من تصحيح الأخطاء المحرجة في المستقبل. صدقني، نومك في الليل سيصبح أعمق بكثير. بالتوفيق يا أصدقائي!

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

الحوسبة السحابية

كنا ندفع ثمن الخوادم الخاملة: كيف أنقذتنا ‘الحوسبة بلا خوادم’ (Serverless) من جحيم التكاليف المهدرة؟

في هذه المقالة، أشارككم قصة حقيقية من تجربتي كمطور برمجيات عن كيفية انتقالنا من الخوادم التقليدية باهظة الثمن إلى معمارية الحوسبة بلا خوادم (Serverless). سنغوص...

21 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كانت طلباتنا تتعثر في أوقات الذروة: كيف أنقذتنا ‘طوابير الرسائل’ (Message Queues) من جحيم الاختناقات؟

أشارككم قصة حقيقية من قلب المعركة البرمجية، كيف كاد تطبيقنا أن ينهار تحت ضغط المستخدمين في يوم إطلاق مهم، وكيف كانت "طوابير الرسائل" (Message Queues)...

21 مايو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

كانت بياناتنا المالية سجينة البنوك: كيف حررتها واجهات ‘الصيرفة المفتوحة’ (Open Banking)؟

من واقع تجربتي كمبرمج، كانت بياناتنا المالية حبيسة جدران البنوك الرقمية. في هذه المقالة، أسرد لكم كيف حولت واجهات برمجة التطبيقات للصيرفة المفتوحة (Open Banking)...

21 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كانت سجلاتنا متناثرة وضائعة: كيف أنقذنا التجميع المركزي (ELK/Loki) من جحيم تتبع الأخطاء؟

أشارككم قصة حقيقية من قلب المعركة البرمجية، كيف انتقلنا من فوضى السجلات المتناثرة على عشرات الخوادم إلى نظام مركزي أنقذنا من جحيم تتبع الأخطاء. مقالة...

21 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كانت ساحة لتبادل الاتهامات: كيف أنقذتنا ‘ثقافة ما بعد الحوادث عديمة اللوم’ من جحيم الخوف؟

من واقع تجربتي كمبرمج، تحولت اجتماعات مراجعة الحوادث التقنية من جلسات لوم واتهامات إلى بيئة آمنة للتعلم والنمو. في هذه المقالة، أشارككم كيف يمكن لـ...

21 مايو، 2026 قراءة المزيد
البودكاست