الاختبار البصري (Visual Testing): كيف أنقذنا واجهاتنا من كارثة الأخطاء الصامتة؟

بتذكرها زي كأنها مبارح. ليلة خميس، الساعة كانت داخلة على ١١ بالليل، وأنا يا دوب مخلّص فنجان الميرمية وبستعد لراحة نهاية الأسبوع. فجأة، التلفون برنّ… رقم مدير المنتج. قلبي نقزني، لأنه مكالمة بهيك وقت عمرها ما كانت خبر خير.

فتحت الخط، وصوته كان متوتر: “أبو عمر، الحق! الموقع خربان!”. دخلت بسرعة، فتحت الموقع على جهازي، كل إشي تمام التمام. الأزرار بتشتغل، النماذج بتبعت بيانات، وكل اختبارات الـ End-to-End اللي عملناها قبل ما نطلق التحديث الجديد كانت ناجحة 100%. قلتله بهدوء: “يا زلمة، شو بتحكي؟ هيو شغال عندي زي الحلاوة”.

بعتلي سكرين شوت… وهون كانت الصدمة. في متصفح Safari على أجهزة الماك، كان الـ Header تبع الموقع نازل لتحت ومغطي على نص المحتوى. كارثة بصرية بكل معنى الكلمة، بس كارثة “صامتة” بالنسبة لاختباراتنا. اختباراتنا كانت عمياء، بتفحص الوظيفة (Functionality) بس ما بتشوف الشكل (Appearance). هذيك الليلة، أدركت إننا بحاجة لعيون جديدة… عيون آلية لا تنام، وهيك بلشت رحلتنا مع الـ Visual Testing.

ما هو الاختبار البصري (Visual Testing)؟ وليش هو مهم؟

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

تخيل إنك بتاخد “صورة مرجعية” (Baseline Image) لواجهتك وهي في أفضل حالاتها. مع كل تغيير جديد في الكود، النظام بياخد صورة جديدة وبيعمل مقارنة بصرية (Pixel by Pixel) مع الصورة المرجعية. لو في أي اختلاف، حتى لو بيكسل واحد، النظام بينبهك.

تشبيه بسيط: الاختبارات التقليدية (Unit, E2E) بتشبه لما تتأكد إن كل مكونات السيارة شغالة (المحرك، العجلات، الفرامل). أما الاختبار البصري، فهو اللي بتأكد إن “دهان” السيارة ما تقشّر، وإن الأبواب راكبة صح على بعضها، وإن شكلها النهائي هو الشكل اللي صممناه بالضبط.

اختباراتنا التقليدية كانت بتجاوب على سؤال “هل الزر يعمل؟”، لكنها كانت عاجزة عن الإجابة على سؤال “هل الزر يظهر في مكانه الصحيح وبشكله الصحيح؟”.

اختباراتنا كانت عمياء… كيف كنا بنشتغل قبل؟

قبل ما نتبنى الاختبار البصري، كانت منظومة الجودة عنا بتعتمد على الهرم التقليدي للاختبارات:

  • Unit Tests: للتحقق من أصغر وحدات الكود.
  • Integration Tests: للتحقق من تفاعل الوحدات مع بعضها.
  • End-to-End (E2E) Tests: باستخدام أدوات مثل Cypress أو Playwright لمحاكاة رحلة المستخدم كاملة.

كنا فخورين بهالمنظومة، وكانت بتصيد 90% من الأخطاء الوظيفية. لكن الـ 10% الباقية كانت الأخطاء المرئية الخبيثة اللي بتتسلل بدون ما حدا يحس عليها، مثل:

  • CSS Regressions: تغيير بسيط في ملف CSS مشترك ممكن يضرب الدنيا في صفحة تانية خالص ما إلها علاقة.
  • مشاكل الـ Responsive: الواجهة تبدو رائعة على شاشة 1080p، لكنها متداخلة ومكسورة على شاشة الموبايل.
  • تداخل العناصر: زر يظهر فوق نص، أو صورة تغطي على قائمة.
  • أخطاء في الخطوط والأيقونات: الخط الافتراضي يظهر بدل الخط المخصص لأن ملف الخط فشل في التحميل.

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

الإنقاذ: رحلتنا مع أدوات الاختبار البصري

بعد ليلة الكارثة إياها، كان القرار واضح: لازم نضيف طبقة جديدة من الحماية. طبقة بتشوف زي ما المستخدم بشوف.

اختيار الأداة المناسبة: مش كل الطرق بتودي لروما!

بدأنا رحلة البحث عن الأداة المناسبة. السوق مليان خيارات، من الخدمات المدفوعة الضخمة زي Applitools و Percy، للحلول مفتوحة المصدر المدمجة في أدوات بنستخدمها أصلاً.

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

قررنا نبدأ مع Playwright لسببين:

  1. مجاني ومدمج: ما في تكلفة إضافية ولا حاجة لتعلم أداة جديدة من الصفر.
  2. سهولة التطبيق: إضافة الاختبار البصري كانت مجرد سطر كود إضافي على اختباراتنا الحالية.

خطوات عملية للتطبيق باستخدام Playwright

هاي هي الخطوات العملية اللي اتبعناها لدمج الاختبار البصري في مشروعنا. لو بتستخدم Playwright، بتقدر تطبقها اليوم.

1. كتابة أول اختبار بصري

الأمر أبسط مما تتخيل. في ملف الاختبار تبعك، كل اللي عليك تضيفه هو سطر واحد بعد ما تتأكد إن الصفحة حملت بالكامل.


import { test, expect } from '@playwright/test';

test('Homepage should look correct', async ({ page }) => {
  // 1. انتقل إلى الصفحة المطلوبة
  await page.goto('https://my-awesome-app.com');

  // 2. انتظر تحميل عنصر معين لضمان اكتمال الصفحة
  await page.waitForSelector('.main-content');

  // 3. هذا هو السطر السحري!
  // التقط صورة وقارنها بالصورة المرجعية
  await expect(page).toHaveScreenshot();
});

2. إنشاء الصور المرجعية (Baselines)

لما تشغل هذا الاختبار لأول مرة، Playwright رح يفشل ويحكيلك: “ما لقيت صورة مرجعية (Baseline) أقارن فيها، بس أنا عملتلك وحدة جديدة”.

بتشغل الأمر التالي لإنشاء كل الصور المرجعية لأول مرة:


npx playwright test --update-snapshots

هذا الأمر رح ينشئ مجلد جديد (عادة `tests/example.spec.ts-snapshots`) ويحط فيه الصور المرجعية بصيغة PNG. لازم تضيف هاي الصور للـ Git repository تبعك، لأنها جزء أساسي من الكود.

3. دورة حياة الاختبار البصري

من هاي اللحظة، دورة العمل بتصير كالتالي:

  1. المطور يعمل تغيير: مثلاً، غيّر لون زر.
  2. يشغل الاختبارات محلياً: الاختبار البصري رح يفشل، ورح ينتج Playwright ثلاث ملفات: الصورة المرجعية (expected)، الصورة الجديدة (actual)، وصورة بتوضح الاختلافات (diff).
  3. المراجعة: المطور بيفتح صورة الفروقات (diff).
    • إذا كان التغيير مقصوداً (لون الزر الجديد صحيح): يقوم بتحديث الصورة المرجعية باستخدام أمر `–update-snapshots` ويعمل commit للصورة الجديدة.
    • إذا كان التغيير غير مقصود (التغيير أثر على عنصر آخر): فهذا يعتبر “Bug”! يقوم بإصلاح المشكلة ويعيد تشغيل الاختبار حتى ينجح.

تحديات ونصائح من المطبخ

الطريق ما كان مفروش بالورود، وواجهتنا شوية تحديات تعلمنا منها دروس غالية.

التعامل مع المحتوى الديناميكي والتواريخ

أول مشكلة واجهتنا كانت مع الصفحات اللي فيها محتوى متغير، مثل تاريخ اليوم، أو اسم المستخدم. الاختبار كان بفشل كل مرة لأن التاريخ بيتغير!

الحل: خاصية الإخفاء (Masking). Playwright بيسمحلك تحدد عناصر معينة عشان “يخفيها” قبل ما ياخد الصورة. بيغطيها بلون ثابت، وبالتالي ما بتأثر على المقارنة.


test('User profile should look correct', async ({ page }) => {
  await page.goto('/profile/abu-omar');

  // "أخفِ" العنصر اللي بيعرض تاريخ آخر تسجيل دخول
  await expect(page).toHaveScreenshot({ 
    mask: [page.locator('.last-login-date')] 
  });
});

الحساسية المفرطة (Flaky Tests)

أحياناً، كانت الاختبارات تفشل بسبب اختلافات طفيفة جداً (1-2 بيكسل) ناتجة عن طريقة عرض الخطوط (Anti-aliasing) المختلفة بين بيئة الـ CI وبيئة المطور المحلية.

الحل: استخدام هامش للخطأ (Threshold). معظم الأدوات بتسمحلك تحدد نسبة مئوية من الاختلاف المسموح به قبل ما الاختبار يعتبر فاشل. استخدمها بحذر شديد!


// اسمح بنسبة اختلاف تصل إلى 5%
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.05 });

نصيحة شخصية: لا تلجأ للـ threshold إلا كملاذ أخير. الحل الأفضل هو التأكد من أن بيئة الاختبارات في الـ CI مطابقة تماماً لبيئة المطور (نفس نظام التشغيل، نفس المتصفح، نفس إصدار Docker image).

فوضى مراجعة التغييرات

لما يكون عندك 20 مطور بيشتغلوا على المشروع، وكل Pull Request فيه 10-15 تغيير بصري، عملية المراجعة ممكن تصير كابوس.

الحل:

  • ابدأ صغيراً: لا تحاول تغطية كل الموقع دفعة واحدة. ابدأ بالصفحات والمكونات الحرجة (صفحة الدفع، الواجهة الرئيسية، الـ Design System).
  • استخدم أدوات مساعدة: الخدمات المدفوعة مثل Percy و Applitools تتألق في هذه النقطة، حيث توفر واجهات رائعة لمراجعة التغييرات بشكل جماعي والموافقة عليها.
  • اجعلها جزءاً من ثقافة الفريق: مراجعة التغييرات البصرية يجب أن تكون بنفس أهمية مراجعة الكود.

الخلاصة: عيون لا تنام تراقب واجهاتك 👁️

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

العائد لم يكن فقط في تقليل الأخطاء، بل في زيادة ثقة المطورين. صار المطور يعمل Refactoring لملف CSS ضخم وهو مرتاح البال، لأنه بيعرف إن في شبكة أمان بصرية رح تصيد أي مشكلة غير متوقعة.

نصيحتي الأخيرة لك: ابدأ اليوم، ولو باختبار واحد لصفحة واحدة. لا تنتظر الكارثة لتتحرك. وجود عين آلية تراقب تطبيقك 24/7 هو استثمار في راحة بالك، وجودة منتجك، وسعادة مستخدميك. وصدقني، راح تدعيلي! 😉

أبو عمر

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

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

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

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

آخر المدونات

الشبكات والـ APIs

كانت خوادمنا تستجدي التحديثات: كيف أنقذتنا ‘خطاطيف الويب’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

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

17 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت بنيتنا التحتية قصراً من رمال: كيف أنقذتنا “البنية التحتية ككود” (IaC) من جحيم البيئات المتضاربة؟

أشارككم قصة حقيقية عن ليلة كادت أن تنهار فيها شركتنا بسبب الفوضى في البنية التحتية، وكيف كانت "البنية التحتية ككود" (IaC) طوق النجاة الذي انتشلنا...

17 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

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

هل تشعر أن ملفك الشخصي على GitHub لا يعكس قدراتك الحقيقية؟ في هذه المقالة، أشاركك يا صديقي تجربتي الشخصية، أنا أبو عمر، وكيف انتقلت من...

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

كانت قاعدة بياناتنا تتوسل الرحمة: كيف أنقذنا التخزين المؤقت (Caching) من جحيم الاستعلامات البطيئة

قصة حقيقية من واقع العمل عن كيفية انهيار نظامنا تحت ضغط الاستعلامات المتكررة، وكيف كان التخزين المؤقت (Caching) هو طوق النجاة. مقالة عملية للمطورين تشرح...

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

كان التحقق من هوية عملائنا يستغرق أياماً: كيف أنقذنا الذكاء الاصطناعي (eKYC) من جحيم الإجراءات اليدوية؟

بصفتي مبرمجاً فلسطينياً، سأروي لكم حكايتنا مع كابوس التحقق اليدوي من هوية العملاء (KYC) وكيف كانت رحلة الانتقال إلى التحقق الإلكتروني (eKYC) باستخدام الذكاء الاصطناعي...

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

كانت أعطالنا تكتشف بعد فوات الأوان: كيف أنقذنا Prometheus من جحيم المراقبة التفاعلية؟

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

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

كانت فرقنا صامتة أمام الأخطاء: كيف أنقذتنا ‘السلامة النفسية’ من جحيم ثقافة اللوم؟

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

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