الاختبارات المتذبذبة
الاختبار المتذبذب (Flaky test) اختبار ينجح ويفشل على الشيفرة نفسها دون أي تغيير في المنتج. وهو أكثر المشكلات إفساداً لحزمة الأتمتة: فحين يتعلّم الفريق أن البناء الأحمر «متذبذب على الأرجح» يتوقف عن تصديق الحزمة، وتتسلل حالات الانحدار (Regressions) الحقيقية مع الضجيج.
يشرح هذا الدليل الأسباب الشائعة للتذبذب في اختبارات الواجهة والاختبارات الشاملة (end-to-end)، وكيف تشخّص اختباراً متذبذباً بالأدلة لا بالتخمين، وكيف تعالج كل سبب، وكيف تستخدم إعادة المحاولة والحجر (Quarantine) للاحتواء دون أن يخفيا المشكلات. وهو جزء من دليل أتمتة اختبار واجهة المستخدم.
ما الاختبار المتذبذب، ولماذا يهم
يكون الاختبار متذبذباً عندما تعطي التشغيلات المتكررة على الإيداع (commit) نفسه وإعدادات البيئة نفسها نتائج مختلفة. شغّله عشر مرات فقد ينجح ثماني مرات ويفشل مرتين. ولأن النتيجة غير حتمية فلا تخبرك أي نتيجة بمفردها بالكثير عن المنتج.
وكلفته أكبر بكثير من معدل حدوثه. فكل فشل متذبذب يكلّف أحداً تحقيقاً أو إعادة تشغيل؛ وبعد بضع مرات يبدأ الفريق بمعاملة كل فشل كضجيج. ومقال Martin Fowler عن عدم الحتمية في الاختبارات يقولها صراحة: الحزمة التي تفشل عشوائياً تفقد قيمتها كإشارة. وليس الاختبار المتذبذب دائماً اختباراً سيئاً — فأحياناً تكون حالة السباق (Race condition) في المنتج نفسه، والاختبار هو الوحيد الذي لاحظها.
- الاختبار نفسه يفشل في تشغيل وينجح عند إعادة التشغيل، دون أي تغيير في الشيفرة بينهما.
- يفشل في CI فقط، أو عند التوازي فقط، أو في أوقات معينة من اليوم، أو في متصفح واحد فقط.
- رسالة الفشل تتغير بين التشغيلات — انتهاء مهلة في أحدها، و«العنصر غير موجود» في التالي.
الأسباب الشائعة لتذبذب اختبارات الواجهة
- التوقيت وعدم التزامن — التصرف أو التحقق قبل جاهزية الصفحة: النقر على زر ما زال معطّلاً، أو قراءة نص قبل وصول استجابة API، أو انتظار ثابت يكون أحياناً أقصر من اللازم.
- الحالة المشتركة وترتيب الاختبارات — اختبارات تعتمد على بيانات أو حالة متصفح أنشأها اختبار آخر أو غيّرها. تنجح بترتيب وتفشل بآخر، أو تفشل عند تشغيلها منفردة.
- تصادم بيانات الاختبار — اختبارات متوازية تستخدم الحساب نفسه أو البريد نفسه أو مخزون المنتج نفسه؛ وقيود تفرّد تفشل عندما يختار تشغيلان القيمة «العشوائية» نفسها.
- البيئة — خادم CI محمّل فوق طاقته، أو بيئة اختبار مشتركة يُعاد نشرها أثناء التشغيل، أو بدء بطيء للحاويات، أو حجم شاشة أو لغة أو منطقة زمنية مختلفة على الخادم.
- خدمات الأطراف الثالثة والخدمات البعيدة — بيئات اختبار الدفع، وبوابات الرسائل النصية، وبلاطات الخرائط، وخدمات أعلام الميزات، وشبكات CDN البطيئة أو غير المتاحة أو المحدودة المعدل.
- الحركات والانتقالات — النقر على عنصر أثناء انزلاقه إلى مكانه، أو التحقق من إشعار منبثق يختفي بعد ثوانٍ.
- الوقت والتواريخ — اختبارات تنكسر حول منتصف الليل ونهايات الأشهر وتغييرات التوقيت الصيفي، أو عندما تختلف المنطقة الزمنية للخادم عن منطقة التطبيق.
- بيانات غير مرتبة — التحقق من الصف الأول في قائمة يعيدها الخادم دون ترتيب مضمون.
- تسرّب الموارد — متصفحات أو جلسات أو اتصالات قاعدة بيانات لا تُغلق، فتتباطأ الاختبارات اللاحقة في التشغيل الطويل أو تفشل.
- عيوب حقيقية في المنتج — حالات سباق، وإرسال مزدوج، وذاكرة تخزين مؤقت لا تُبطَل. هذه أخطاء يُبلَّغ عنها، لا ضجيج اختبارات يُحجر.
الأعراض والأسباب الجذرية: أمثلة عملية
يقرن الجدول أعراضاً متذبذبة نموذجية، مكتوبة كما تظهر في متتبّع العيوب، بالسبب الجذري الذي كشفه التحقيق والإصلاح الذي أزاله. لاحظ أن أياً من الإصلاحات ليس «أضف انتظاراً أطول» ولا «فعّل إعادة المحاولة».
- checkout.spec: النقر على «Place order» يُتجاهل في نحو تشغيل من كل 10 ← السبب: الزر لا يُفعَّل إلا بعد إعادة حساب السعر ← الإصلاح: تحقّق من حالة التفعيل واعتمد على الانتظار التلقائي.
- orders.spec يفشل فقط عند تشغيله بعد refunds.spec ← السبب: refunds.spec يلغي الطلب المشترك ← الإصلاح: كل اختبار يُنشئ طلبه الخاص عبر API.
- signup.spec: رسالة «email already registered» في تشغيلات CI المتوازية ← السبب: عاملان يولّدان البريد نفسه ← الإصلاح: أضف معرّف التشغيل/العامل وUUID إلى البيانات المولّدة.
- report.spec يفشل بين 00:00 و03:00 بتوقيت الرياض ← السبب: المشغّل يستخدم UTC فيختلف «اليوم» ← الإصلاح: تحكّم في الساعة أو احسب التواريخ في منطقة زمنية واحدة.
- profile.spec: إشعار «Saved» لا يُعثر عليه في CI فقط ← السبب: الإشعار يختفي قبل التحقق البطيء ← الإصلاح: تحقّق من الحالة المحفوظة لا من الإشعار العابر.
- map.spec تنتهي مهلته تحت الحمل المتوازي ← السبب: خدمة بلاطات الخرائط الخارجية تحدّ معدل الطلبات ← الإصلاح: استبدل استجابات الخرائط باستجابات وهمية واحتفظ بفحص عقد واحد.
For example (fictional):
Symptom (as a bug title) Root cause Fix
checkout.spec: "Place order" click ignored in ~1 of 10 runs Button enabled only after price recalculated Assert the enabled state; rely on auto-wait
orders.spec fails only when run after refunds.spec refunds.spec cancels the shared order Each test creates its own order via the API
signup.spec: "email already registered" in parallel CI runs Two workers generate the same email Add run/worker id + UUID to generated data
report.spec fails between 00:00 and 03:00 Riyadh time Runner uses UTC, so "today" differs Control the clock or compute dates in one zone
profile.spec: "Saved" toast not found on CI only Toast disappears before the slow assertion Assert the saved state, not the transient toast
map.spec times out under parallel load Third-party tile service rate-limits the run Stub map responses; keep one contract checkتشخيص اختبار متذبذب
عامل الاختبار المتذبذب كأي خطأ متقطع: أعد إنتاجه، واجمع الأدلة من التشغيلات الفاشلة، وغيّر متغيراً واحداً في كل مرة حتى يتحرك معدل الفشل.
- أعد الإنتاج بالتكرار — شغّل الاختبار المفرد مرات كثيرة (يوفّر Playwright الخيار --repeat-each؛ ومع المشغّلات الأخرى استخدم خيار التكرار فيها أو حلقة في الطرفية). فمعدل الفشل يحوّل «يفشل أحياناً» إلى رقم تقارنه قبل الإصلاح وبعده.
- غيّر شرطاً واحداً — منفرداً مقابل ضمن الحزمة كاملة، وتسلسلياً مقابل متوازياً، وترتيباً مختلفاً، ومحلياً مقابل CI، ومعالجاً أو شبكة مخنوقين، ومتصفحاً آخر. الشرط الذي يغيّر معدل الفشل يشير إلى السبب.
- اقرأ الأدلة من التشغيل الفاشل — لقطة الشاشة عند الفشل، والفيديو، والأثر (trace). يعرض عارض الآثار في Playwright كل إجراء مع لقطات DOM وطلبات الشبكة ورسائل وحدة التحكم؛ ويستطيع Cypress التقاط لقطات شاشة وفيديو للتشغيلات؛ ومع Selenium التقط لقطة شاشة وسجلات المتصفح ومصدر الصفحة في خطاف الفشل لدى مشغّلك.
- اربط بالخلفية — سجّل معرّفات الطلبات والطوابع الزمنية التي أنتجها الاختبار وابحث عن النافذة الزمنية نفسها في سجلات الخادم. انتبه للمناطق الزمنية: كثيراً ما تستخدم خوادم CI توقيت UTC بينما يسجّل التطبيق بالتوقيت المحلي.
- راجع التاريخ — تُظهر تقارير الاختبار ولوحات CI متى بدأ التذبذب؛ قارن ذلك التاريخ بالإيداعات وترقيات الاعتماديات وتغييرات البيئة.
// playwright.config.ts — keep evidence from failing runs
export default defineConfig({
retries: process.env.CI ? 1 : 0,
use: {
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
});
# reproduce locally: run one spec 50 times with 4 workers
npx playwright test tests/checkout.spec.ts --repeat-each=50 --workers=4
# open the trace from a failed CI run
npx playwright show-trace trace.zipمعالجة الأسباب الشائعة
- التوقيت — استبدل الانتظار الثابت بانتظار مشروط: الانتظار الصريح في Selenium، والإجراءات ذات الانتظار التلقائي والتأكيدات web-first في Playwright، والاستعلامات المعادة مع .should() في Cypress. انتظر إشارة التطبيق نفسه — استجابة أو تغيّر حالة — لا الوقت.
- الحالة المشتركة — اجعل كل اختبار يُنشئ بياناته وحالة متصفحه، ويبدأ من سياق متصفح جديد، ولا يعتمد أبداً على تشغيل اختبار آخر قبله.
- تصادم البيانات — ولّد قيماً فريدة لكل اختبار ولكل عامل؛ وأخرج الحسابات الثابتة مثل «مستخدم الاختبار 1» من التشغيلات المتوازية.
- البيئة — ثبّت إصدارات المتصفحات والاعتماديات، وخصّص للحزمة بيئة مستقلة أو جمّد النشر أثناء التشغيل، واجعل حجم خوادم CI مناسباً لعدد العمّال.
- خدمات الأطراف الثالثة — استبدلها باستجابات وهمية في معظم اختبارات الواجهة (page.route في Playwright، وcy.intercept في Cypress، أو خادم محاكاة) واحتفظ بفحص صغير يُبلَّغ عنه منفصلاً مقابل بيئة الاختبار الحقيقية.
- الحركات — تحقّق من الحالة النهائية لا من حالة عابرة؛ وحيث يحترم التطبيق تفضيل تقليل الحركة (reduced motion) فحاكِه في الاختبارات لتقصير الانتقالات أو إزالتها.
- الوقت — تحكّم في الساعة (واجهة clock في Playwright، وcy.clock في Cypress) أو احسب التواريخ المتوقعة بالمنطقة الزمنية للتطبيق.
- الترتيب — رتّب أو صفِّ صراحةً قبل التحقق؛ ولا تفترض أبداً أن الصف الأول هو ما أنشأه اختبارك.
// Flaky: guesses how long the save takes, then reads the text once
await page.getByRole("button", { name: "Save" }).click();
await page.waitForTimeout(2000);
expect(await page.getByTestId("status").textContent()).toBe("Saved");
// Stable: waits for the app’s own signal and retries the assertion
const saved = page.waitForResponse(
(r) => r.url().includes("/api/profile") && r.request().method() === "PUT");
await page.getByRole("button", { name: "Save" }).click();
expect((await saved).ok()).toBeTruthy();
await expect(page.getByTestId("status")).toHaveText("Saved");جرّبها: مولّد مستخدمين وهميين جرّبها: مصنع بيانات الاختبار جرّبها: مولّد استجابات API وهمية جرّبها: منشئ طلبات API
إعادة المحاولة والحجر: احتواء لا علاج
إعادة المحاولة التلقائية (Retries) — إعداد retries في Playwright، وخيار retries في Cypress، وميزات إعادة التشغيل في مشغّلات مثل TestNG وMaven Surefire — تجعل الخط أخضر عندما ينجح اختبار متذبذب في محاولة لاحقة. هذا مقبول كاحتواء قصير الأمد وضار كسياسة: فإعادة المحاولة تُخفي العيوب المتقطعة الحقيقية، وتبطئ الخط، وتترك معدل التذبذب ينمو دون أن يُرى. وإن استخدمتها فاجعلها مرئية؛ فمثلاً يعرض Playwright الاختبار الذي فشل ثم نجح عند إعادة المحاولة على أنه «flaky» لا «passed».
والحجر (Quarantine) هو أداة الاحتواء الأخرى: انقل الاختبار المعروف بتذبذبه من الحزمة التي تمنع الدمج إلى مهمة منفصلة غير مانعة، حيث يستمر في العمل والإبلاغ دون إيقاف الدمج. ويصف Fowler الحجر بأنه وسيلة لإبقاء الحزمة الرئيسية جديرة بالثقة — مع التحذير من أن الاختبارات المحجورة يجب إصلاحها سريعاً، وإلا توقفت عن حماية أي شيء.
- اسمح بمحاولة إعادة واحدة على الأكثر في CI ولا شيء محلياً، وأبلغ عن «نجح عند إعادة المحاولة» على أنه متذبذب.
- تحقّق أولاً مما إذا كان الفشل خطأً في المنتج؛ ولا تحجر عيباً حقيقياً أبداً.
- امنح كل اختبار محجور مالكاً وتذكرة وموعداً نهائياً، وراجع القائمة أسبوعياً.
- حين يتجاوز الاختبار موعده في الحجر: أصلحه، أو أعد كتابته في مستوى أدنى، أو احذفه. فالاختبار المتخطّى دائماً شيفرة ميتة تبدو كتغطية.
- ضع سقفاً للحجر. وإن ظل ينمو فتوقف عن الإضافة إليه وعالج السبب المشترك — وهو عادةً البيئة أو استراتيجية بيانات الاختبار.
- ويضيف مثال السياسة أدناه أرقاماً ملموسة يمكن تكييفها: يصنّف CI الاختبار متذبذباً إذا فشل ونجح على الإيداع نفسه؛ ويُفرز خلال يوم عمل واحد (خطأ في المنتج أم في الاختبار أم في البيئة؟)؛ ولا يتجاوز الموعد النهائي في الحجر 10 أيام عمل؛ ويخرج الاختبار من الحجر بعد إصلاحه و50 نجاحاً متتالياً مع --repeat-each، أو بإعادة كتابته أو حذفه؛ وتُراجع قائمة الاختبارات المتذبذبة أسبوعياً مع اتجاه معدل التذبذب لكل حزمة.
Flaky test policy (example)
- Detection: a test that fails and passes on the same commit is flagged flaky by CI
- Retries: at most 1 in CI, 0 locally; "passed on retry" is reported as flaky, not passed
- Triage: within 1 working day: product bug, test bug or environment?
- Quarantine: non-blocking job, with ticket + owner + due date (max 10 working days)
- Exit: fixed and 50 consecutive passes with --repeat-each, or rewritten / deleted
- Review: weekly flaky list in the QA sync; flaky-rate trend per suite| # | من | إلى | الحدث أو الشرط |
|---|---|---|---|
| 1 | الحزمة المانعة للدمجالبداية | مصنَّف متذبذباً | يفشل وينجح على الإيداع نفسه، أو ينجح عند إعادة المحاولة: يعرضه CI متذبذباً لا ناجحاً |
| 2 | مصنَّف متذبذباً | الفرز | خلال يوم عمل واحد: خطأ في المنتج أم في الاختبار أم في البيئة؟ |
| 3 | الفرز | يُعالَج كخطأ في المنتجالنهاية | خطأ في المنتج: لا يُحجر العيب الحقيقي أبداً |
| 4 | الفرز | محجور | خطأ في الاختبار أو البيئة: يُنقل إلى مهمة غير مانعة بمالك وتذكرة وموعد نهائي |
| 5 | محجور | مُصلَح: قيد التحقق | أُصلح |
| 6 | مُصلَح: قيد التحقق | الحزمة المانعة للدمجالبداية | 50 نجاحاً متتالياً مع --repeat-each: يعود إلى الحزمة المانعة للدمج |
| 7 | محجور | أُعيدت كتابته في مستوى أدنىالنهاية | أعد كتابته في مستوى أدنى |
| 8 | محجور | محذوفالنهاية | احذفه |
مثال على سياسة يمكن تكييف أرقامها. بعد تجاوز الموعد النهائي (10 أيام عمل على الأكثر): أصلحه أو أعد كتابته أو احذفه، فالاختبار المتخطّى دائماً شيفرة ميتة تبدو كتغطية. راجع القائمة أسبوعياً؛ والحجر الذي ينمو يعني سبباً مشتركاً يجب علاجه.
الملكية والقياس
تبقى الاختبارات المتذبذبة متذبذبة حين لا يملكها أحد. وزّع الملكية حسب منطقة المنتج — الفريق الذي يملك إتمام الشراء يملك اختباراته — لا على «شخص الأتمتة» الوحيد، واجعل فرز الاختبارات المتذبذبة جزءاً من تعريف الإنجاز (Definition of Done) لا مشروعاً جانبياً.
بضعة مقاييس بسيطة تُظهر ما إذا كان الوضع يتحسن. تتبّعها لكل حزمة وراجع الاتجاه لا أسبوعاً واحداً.
- معدل التذبذب — نسبة تشغيلات الاختبار التي فشلت ثم نجحت عند إعادة المحاولة، أو فشلت دون تغيير في الشيفرة.
- أكبر المتسببين — الاختبارات القليلة المسؤولة عن معظم الإخفاقات المتذبذبة؛ وإصلاحها يعطي عادةً أكبر تحسن.
- زمن الإصلاح — كم يبقى الاختبار موسوماً بالتذبذب أو محجوراً.
- كلفة إعادة التشغيل — دقائق الخط وساعات العمل المصروفة على إعادة المحاولة وإعادة التشغيل اليدوية.
منع التذبذب عند كتابة الاختبارات
- لا انتظار ثابتاً؛ كل انتظار شرط مع مهلة قصوى.
- تأكيدات تعيد المحاولة على نتائج يراها المستخدم، لا قراءات لمرة واحدة.
- محددات متينة مبنية على الدور أو التسمية أو النص أو معرّفات الاختبار — انظر دليل استراتيجيات المحددات.
- كل اختبار يُنشئ بياناته ويملكها، بقيم فريدة لكل تشغيل ولكل عامل.
- تُهيّأ الحالة عبر API، لا بالنقر عبر شاشات لا علاقة لها بالاختبار.
- تُستبدل خدمات الأطراف الثالثة باستجابات وهمية، ويُتحكم في الساعة والمنطقة الزمنية حيث تهم التواريخ.
- تُشغَّل الاختبارات الجديدة مرات كثيرة — مثلاً من 20 إلى 50 تكراراً — قبل انضمامها إلى الحزمة المانعة.
- تعمل الحزمة بالتوازي، وبترتيب عشوائي من حين لآخر، حتى تظهر الاعتماديات الخفية مبكراً.
الأسئلة الشائعة
ما الاختبار المتذبذب؟
هو اختبار آلي يعطي نتائج مختلفة — نجاحاً أو فشلاً — على الشيفرة والإعدادات نفسها دون أي تغيير في المنتج. ولا يمكن الوثوق بنتيجته بمفردها، ما يقلل فائدة الحزمة كلها.
هل نكتفي بتفعيل إعادة المحاولة التلقائية؟
فقط كاحتواء مؤقت، بحد منخفض، ومع الإبلاغ عن «نجح عند إعادة المحاولة» كتذبذب. فإعادة المحاولة وحدها تُخفي أخطاء متقطعة حقيقية وتترك التذبذب ينمو؛ وكل اختبار متذبذب ما زال يحتاج إلى تشخيص وإصلاح.
ما حجر الاختبارات؟
الحجر ينقل اختباراً معروفاً بتذبذبه من الحزمة التي تمنع الدمج إلى تشغيل منفصل غير مانع. يُبقي الحزمة الرئيسية جديرة بالثقة ريثما يُصلح الاختبار، ولا ينجح إلا إذا كان لكل اختبار محجور مالك وموعد نهائي.
كيف أجد سبب اختبار متذبذب؟
أعد إنتاجه بتشغيله مرات كثيرة، ثم غيّر شرطاً واحداً في كل مرة — العزل والتوازي والترتيب والبيئة والمتصفح — وراقب معدل الفشل. واستخدم لقطات الشاشة والفيديو والآثار وسجلات الخادم من التشغيلات الفاشلة كأدلة.
هل الاختبار المتذبذب مشكلة في الاختبار دائماً؟
لا. بعض الاختبارات المتذبذبة تكشف حالات سباق حقيقية أو إرسالاً مزدوجاً أو أخطاء في التخزين المؤقت داخل المنتج. استبعد ذلك قبل لوم الاختبار، وأبلغ عنه كعيب إن كان الخلل في المنتج.
ما السبب الأكثر شيوعاً لتذبذب اختبارات الواجهة؟
عملياً، مشكلات التوقيت من أكثرها تكراراً: يتصرف الاختبار أو يتحقق قبل جاهزية التطبيق. ويزيل الانتظار المشروط والإجراءات ذات الانتظار التلقائي والتأكيدات التي تعيد المحاولة معظمها؛ وتأتي البيانات المشتركة والبيئات غير المستقرة بعدها.
كيف نمنع دخول اختبارات متذبذبة جديدة إلى الحزمة؟
راجع الاختبارات الجديدة بحثاً عن الانتظار الثابت والمحددات الهشّة والبيانات المشتركة، وشغّل كل اختبار جديد مرات كثيرة في ظروف CI قبل إضافته إلى الحزمة المانعة. وتتبّع معدل التذبذب لكل حزمة يُظهر سريعاً متى يسوء الوضع بسبب تغيير ما.
المراجع
- Eradicating Non-Determinism in Tests (Martin Fowler)لماذا تُفقد الاختبارات المتذبذبة الحزمةَ قيمتها، وأسبابها الشائعة (العزل وعدم التزامن والخدمات البعيدة والوقت وتسرّب الموارد)، والحجر كاستجابة.
- Trace viewer — Playwrightتسجيل تشغيل الاختبار وإعادة عرضه خطوة بخطوة — لقطات DOM والشبكة ووحدة التحكم — لتشخيص الإخفاقات، خصوصاً القادمة من CI.
- Auto-waiting — Playwrightفحوص الجاهزية التي يجريها Playwright قبل كل إجراء — مرئي، ومستقر، ومفعّل، ويستقبل الأحداث — والتي تُغني عن معظم الانتظارات المكتوبة يدوياً.
- Waiting Strategies — Seleniumلماذا تتسابق اختبارات المتصفح مع الصفحة، وكيف تزامن الانتظارات الضمنية والصريحة سكربت WebDriver مع التطبيق.
- Best Practices — Cypressإرشادات Cypress لاختيار العناصر بسمات مخصّصة، وتجنّب الانتظار الثابت، وإبقاء الاختبارات مستقلة.
- Best Practices — Playwrightاختبر السلوك الذي يراه المستخدم، واجعل الاختبارات معزولة، وفضّل المحدِّدات المتينة والتأكيدات التي تنتظر، واستخدم التتبّع لتصحيح الأخطاء.
تمرين عملي
افرز ستة اختبارات متذبذبة قبل الإصدار
أنت مهندس الجودة في تطبيق ويب لحجز الفنادق (مثال خيالي). يضيف الإصدار 3.4 رموز الخصم عند إتمام الشراء ويُطلَق بعد عشرة أيام. تعمل حزمة اختبارات الواجهة المكتوبة بـ Playwright مع كل طلب دمج بثلاثة عمّال (Workers) على بيئة Staging مشتركة، وليلياً في 02:30 بتوقيت الرياض، بإعداد retries: 2 ودون أثر (trace). وخلال الأسبوعين الأخيرين لم تصبح 21 من 60 تشغيلاً لطلبات الدمج خضراء إلا بعد إعادة المحاولة، ووُسمت ستة اختبارات بأنها فشلت ثم نجحت على الإيداع (commit) نفسه. تعرض المادة الإعدادات، والاختبارات الستة مع الأدلة المجموعة حتى الآن، وملاحظتين من الفريق.
اعمل على الورق مستعيناً بقائمة الأسباب في الدليل وخطوات التشخيص والإصلاحات وقواعد إعادة المحاولة والحجر (Quarantine). يقترح أحد المطورين رفع عدد إعادات المحاولة وتخطّي اختبار، وتسأل مديرة الإصدار هل يمكن الوثوق بالتشغيلات الخضراء. ويساعدك محوّل المناطق الزمنية في أحد الاختبارات. خصّص لذلك 30–45 دقيقة.
تطبيق ويب لحجز الفنادق (مثال خيالي) - البحث عن الغرف، والحجز، والدفع، وإدارة الحجز
يضيف الإصدار 3.4 رموز الخصم عند إتمام الشراء. الإطلاق: الأحد 20 سبتمبر 2026.
اليوم: الخميس 10 سبتمبر.
حزمة اختبارات الواجهة: Playwright (TypeScript)، 48 اختباراً. تعمل مع كل طلب دمج بثلاثة
عمّال (Workers) على بيئة Staging المشتركة، مع تشغيل ليلي واحد في 02:30 بتوقيت الرياض.
تُستعاد بيانات البذرة (Seed) قبل كل تشغيل، ويمكن لأي فريق إعادة نشر Staging في أي وقت.
ويعدّ ملخص CI التشغيل أخضر إذا نجح كل اختبار في أي محاولة.
playwright.config.ts (الحالي)
retries: 2, workers: 3, fullyParallel: true,
use: { trace: 'off', screenshot: 'off', video: 'off' }
آخر أسبوعين: 60 تشغيلاً لطلبات الدمج و10 تشغيلات ليلية على فروع الإصدار 3.4.
21 من التشغيلات الستين لم تصبح خضراء إلا بعد إعادة المحاولة. الاختبارات التي فشلت ثم
نجحت على الإيداع نفسه، مع ما وجده التحقيق حتى الآن:
T1 booking.spec.ts > «Reserve» ينشئ الحجز 9 من 60 تشغيلاً
الخطأ: «Booking confirmed» غير ظاهر (انتهاء المهلة). أثر (trace) التُقط يدوياً في
تشغيل فاشل: ينقر الاختبار «Reserve» مع { force: true } والزر ما زال معطّلاً
وGET /api/quote لم يُجَب بعد؛ فلا تفعل النقرة شيئاً. يستغرق عرض السعر على CI
0.4-1.8 ثانية، ومحلياً 0.2 ثانية. سجل التغييرات: أُضيف force: true قبل 3 أشهر
«لإصلاح انتهاء مهلة».
T2 cancel.spec.ts > الإلغاء يحرّر ليالي الغرفة 6 من 60 تشغيلاً
يفشل فقط عندما يعمل modify.spec.ts قبله في التشغيل نفسه؛ وينجح منفرداً.
يستخدم الاختباران حجز البذرة BK-1001 (الغرفة 204، 12-14 أكتوبر). ينقله
modify.spec.ts إلى 15-17 أكتوبر. ويلغيه cancel.spec.ts ويتوقع «12-14 Oct released»،
وتعرض التشغيلات الفاشلة «15-17 Oct released».
T3 signup.spec.ts > ضيف جديد يستطيع التسجيل 2 من 60 تشغيلاً
«Email already registered»، فقط عندما يشغّل عاملان اختبارات التسجيل في الوقت نفسه.
يبدأ الملف بـ faker.seed(42)، فيولّد كل عامل البريد الأول نفسه:
sara.guest@example.com. ويحذف كل اختبار حسابه عند انتهائه.
T4 payment.spec.ts > يدفع ببطاقة اختبارية 3 من 60 تشغيلاً
الثلاثة كلها الثلاثاء 8 سبتمبر بين 14:00 و15:00 في تشغيلات متتالية: أجابت بيئة
اختبار الدفع بـ 504 Gateway Timeout. وتذكر صفحة حالتها حادثة من 13:50 إلى 15:05.
يستدعي الاختبار بيئة الاختبار الحقيقية.
T5 promo.spec.ts > رمز الخصم يحدّث المجموع 4 من 60 تشغيلاً
يعرض المجموع 480.00 ريال (قبل الرمز) بدلاً من 432.00 ريال. السبب غير مؤكد. أثر
واحد التُقط يدوياً: نُقر «Apply» مرة واحدة؛ أجاب GET /api/quote (المرسَل سابقاً من
منتقي التاريخ) بعد POST /api/promo، وعرضت الصفحة الاستجابة التي وصلت أخيراً.
محلياً لا يفشل أي تشغيل من 20؛ ومع خنق الشبكة (تأخير إضافي لكل طلب) يفشل 7 من 10.
T6 dates.spec.ts > تاريخ الوصول الافتراضي هو اليوم 10 من 10 ليلياً، 0 من 60 طلب دمج
يُحسب التاريخ المتوقع بـ new Date() على المشغّل الذي يستخدم UTC، بينما يعرض التطبيق
اليوم بتوقيت Asia/Riyadh. تجري تشغيلات طلبات الدمج بين 08:00 و18:00 بتوقيت الرياض.
ملاحظات الفريق
طارق (مطوّر): «اضبطوا retries: 3 وتخطّوا promo.spec.ts حتى ما بعد الإصدار».
هند (مديرة الإصدار): «هل أستطيع الوثوق بالتشغيلات الخضراء للإصدار 3.4؟»
المالكون الحجز والمدفوعات: لمى | حسابات الضيوف: عمر | البحث والتقويم: يارا- صنّف الاختبارات من T1 إلى T6: اذكر السبب كما يسمّيه الدليل، والأدلة التي تدعمه، وهل هو عيب في الاختبار، أم مشكلة في البيئة أو في طرف ثالث، أم عيب في المنتج. وحدّد الاختبارات التي ليس فشلها عشوائياً أصلاً.
- سبب T5 غير مؤكد. اكتب التشخيص: كيف تعيد إنتاجه، وأي شرط واحد تغيّره، وأي أدلة تقرأ وكيف تربطها بالواجهة الخلفية، ثم استنتاجك وما تفعله بالاختبار.
- اكتب الإصلاح لكل اختبار. ولا يجوز أن يكون أي منها انتظاراً أطول أو إعادة محاولة.
- قرّر احتواء اليوم: أي الاختبارات تصلحها فوراً، وأيها يدخل الحجر (المالك، والتذكرة، والموعد النهائي، ومعيار الخروج)، وما الذي تبلّغ عنه كعيب. ثم ردّ على طارق.
- صحّح الإعدادات وملخص CI وفق الدليل، واحسب معدل التذبذب لكل اختبار وللحزمة، وسمّ أكبر المتسببين، وأجب هند.
إظهار الإجابة النموذجية
التصنيف. T1: التوقيت وعدم التزامن، وهو عيب في الاختبار. يبقى الزر معطّلاً حتى يعود GET /api/quote، و{ force: true } يتخطى فحوص القابلية للتنفيذ (Actionability) في Playwright، فتُرسَل النقرة والزر معطّل ولا يحدث شيء. ولا يفشل إلا عندما يتأخر عرض السعر، ولهذا يظهر على CI (0.4–1.8 ثانية) ولا يظهر محلياً (0.2 ثانية). و«الإصلاح» الذي أُضيف قبل ثلاثة أشهر أخفى مشكلة انتظار بدلاً من حلّها. T2: الحالة المشتركة وترتيب الاختبارات، وهو عيب في الاختبارين. يغيّر الاختباران حجز البذرة نفسه BK-1001، فتتوقف النتيجة على أيهما عمل أولاً في التشغيل: بعد modify.spec.ts يكون الحجز على 15–17 أكتوبر، فيحرّر إلغاؤه تلك الليالي لا 12–14 أكتوبر. يبدو عشوائياً مع أن الترتيب يحدده تماماً.
T3: تصادم بيانات الاختبار، وهو عيب في الاختبار. يجعل faker.seed(42) البريد «العشوائي» حتمياً: كل عامل يولّد sara.guest@example.com أولاً، والعامل الثاني الذي يسجّله بينما ما زال الاختبار الأول يحتفظ بالحساب يخالف قيد التفرّد. ولا يفشل إلا عندما يصل عاملان إلى اختبارات التسجيل في الوقت نفسه، ومن هنا 2 من 60، وتنجح إعادة المحاولة بعد أن يحذف الاختبار الآخر حسابه. T4: خدمة طرف ثالث، لا عيب في الاختبار ولا في المنتج. ثلاثة إخفاقات متتالية داخل نافذة حادثة موثّقة (13:50–15:05) ولا شيء خارجها هي انقطاع خدمة لا تذبذب في الاختبار؛ لكنها تُظهر أن الحزمة المانعة تعتمد في كل تشغيل على بيئة اختبار بعيدة. ولا يشير أي من الأدلة إلى إعادات نشر Staging، لكن قارن أوقات النشر بأوقات الإخفاقات قبل أن تستبعد البيئة. T6: الوقت والتواريخ، وهو عيب في الاختبار. الساعة 02:30 في الرياض هي 23:30 بتوقيت UTC من اليوم السابق، فالتاريخ الذي يستخرجه المشغّل من new Date() بتوقيت UTC هو تاريخ الأمس، بينما يعرض التطبيق تاريخ اليوم بتوقيت Asia/Riyadh؛ وبين 08:00 و18:00 بتوقيت الرياض يتفق الاثنان. يفشل في 10 من 10 تشغيلات ليلية: ليس عشوائياً أصلاً، بل حتمي تحت شرط لا تحققه تشغيلات طلبات الدمج أبداً. وينطبق الأمر على T2 (الترتيب) وT3 (عاملان في الوقت نفسه) وجزئياً على T1 (تأخر عرض السعر): «متذبذب» يعني عادةً أن الشرط مخفي، لا أنه لا شرط.
T5: التشخيص. أعد الإنتاج بالتكرار: شغّل الملف وحده مرات كثيرة، مثلاً npx playwright test tests/promo.spec.ts --repeat-each=20؛ فيعطي محلياً 0 من 20 مقابل 4 من 60 على CI، أي أن شيئاً في ظروف CI هو المؤثر. غيّر شرطاً واحداً: اخنق الشبكة ولا تغيّر شيئاً آخر، فيقفز المعدل إلى 7 من 10؛ فالسبب هو التوقيت النسبي لطلبين، لا البيانات ولا الترتيب. اقرأ الأدلة: يُظهر الأثر نقرة واحدة على «Apply»، وإجابة GET /api/quote بعد POST /api/promo، وعرض الصفحة للاستجابة الأخيرة، فيطغى عرضُ سعرٍ قديم على المجموع المخصوم. اربط بالواجهة الخلفية: ابحث عن معرّفَي الطلبين وطابعيهما الزمنيين في سجل الخادم (وانتبه للمناطق الزمنية، فالمشغّل يستخدم UTC) لتتأكد أن طلب عرض السعر انتهى بعد طلب الخصم. الاستنتاج: حالة سباق (Race condition) في المنتج، إذ لا يتجاهل العميل الاستجابات المتقادمة، وهي من العيوب الحقيقية التي يقول الدليل إنها لا تُحجر أبداً. الاختبار صحيح؛ والضيف على اتصال بطيء قد يرى 480.00 ريال بعد إدخال رمز صالح. أبلغ لمى بالعيب مع الأثر ومعرّفات الطلبات ومعدل 7 من 10 تحت الخنق، والمتوقع 432.00 ريال والفعلي 480.00 ريال؛ ورموز الخصم هي عنوان الإصدار 3.4، فيجب إصلاحه قبل 20 سبتمبر. ويعني اختبار التأكيد (Confirmation testing) بعد ذلك تكرار الاختبار تحت الخنق مرات كثيرة، لا تشغيلاً أخضر واحداً.
الإصلاحات، وليس بينها انتظار ثابت ولا إعادة محاولة. T1: احذف { force: true } لتنتظر النقرة تلقائياً تفعيل الزر، واجعل الإشارة صريحة قبل النقر: await expect(reserveButton).toBeEnabled()، أو انتظر استجابة /api/quote بـ page.waitForResponse؛ ثم يتحقق تأكيد web-first على «Booking confirmed» من النتيجة. T2: ينشئ كل اختبار حجزه الخاص عبر API في تهيئته، بسياق متصفح جديد، وينظّفه بعده؛ ويصبح حجز البذرة BK-1001 للقراءة فقط في الاختبارات أو يُحذف. T3: احذف faker.seed(42) وولّد قيماً فريدة لكل اختبار ولكل عامل، مثل ${testInfo.workerIndex}-${randomUUID()}@example.com أو معرّف التشغيل في العنوان. T4: استبدل بيئة اختبار الدفع باستجابات وهمية في payment.spec.ts وسائر اختبارات الواجهة، بـ page.route إن كان المتصفح هو الذي يستدعيها، أو بخادم محاكاة إن كانت الواجهة الخلفية هي التي تستدعيها، واحتفظ بفحص عقد صغير واحد مقابل البيئة الحقيقية في مهمة منفصلة غير مانعة تُبلغ عن نفسها. T5: لا شيء يُصلَح في الاختبار؛ وبعد إصلاح المنتج يبقى الاختبار فحصَ الانحدار. T6: احسب التاريخ المتوقع بالمنطقة الزمنية للتطبيق بـ Intl.DateTimeFormat مع timeZone: 'Asia/Riyadh'، أو، إن كانت الصفحة تأخذ «اليوم» من المتصفح، تحكّم في الساعة بواجهة clock في Playwright (page.clock) وتوقّع التاريخ الذي يوافق الوقت الثابت؛ وتؤكد التشغيلات الليلية التالية في 02:30 الإصلاح.
احتواء اليوم. يُصلَح فوراً دون حجر، فكل منها تغيير صغير داخل اختبار واحد: T1 (لمى) وT3 (عمر) وT6 (يارا)؛ شغّل كلاً منها بـ --repeat-each=50 قبل الدمج. يُحجر بشروط الدليل: T2 (المالكة لمى، وتذكرة، والموعد النهائي الخميس 17 سبتمبر على الأكثر، أي آخر يوم عمل قبل الإطلاق يوم الأحد، وضمن سقف أيام العمل العشرة بفارق مريح، والخروج بعد إعادة الكتابة و50 نجاحاً متتالياً مع --repeat-each) وT4 (المالكة لمى، وتذكرة، والموعد نفسه، والخروج عندما تُستبدل بيئة الدفع باستجابات وهمية ويعمل فحص العقد في مهمته الخاصة). يستمر الاثنان في العمل في المهمة غير المانعة ويبقيان في القائمة الأسبوعية. يُبلَّغ عنه ولا يُحجر أبداً: T5 يصبح عيباً للمى مع الأدلة أعلاه، ويبقى promo.spec.ts في الحزمة المانعة؛ وما دام العيب مفتوحاً تبقى إخفاقاته ظاهرة، حمراء أو موسومة بالتذبذب، لسبب حقيقي. الرد على طارق: جعلت إعادة المحاولة 21 تشغيلاً أخضر دون إصلاح سبب واحد؛ وretries: 3 تُخفي أكثر، وتضيف محاولة أخرى لكل اختبار فاشل، ولا تفيد T6 بشيء (فهو يتبع الساعة لا الصدفة)، وتُبقي T5 مخفياً، وهو الإخفاق الوحيد الذي هو عيب في الإصدار 3.4. وتخطّي promo.spec.ts يزيل الفحص الوحيد الذي لاحظه. والسياسة هي العكس: محاولة إعادة واحدة على الأكثر في CI، ولا شيء محلياً، والإخفاق الذي ينجح عند إعادة المحاولة يُبلَّغ عنه متذبذباً لا ناجحاً.
الإعدادات والقياس. غيّر playwright.config.ts إلى retries: process.env.CI ? 1 : 0 وtrace: 'on-first-retry' وscreenshot: 'only-on-failure' وvideo: 'retain-on-failure' كما في الدليل، فيترك كل إخفاق متذبذب لقطة شاشة وفيديو، وتترك إعادة محاولته أثراً، بدلاً من أثر يُلتقط يدوياً. وغيّر ملخص CI: التشغيل الذي احتاج إلى إعادة محاولة يُبلَّغ عنه متذبذباً مع أسماء الاختبارات، وPlaywright يعطي هذه الحالة أصلاً. معدل التذبذب لكل اختبار على التشغيلات الستين: T1 9/60 = 15%، وT2 6/60 = 10%، وT5 4/60 ≈ 6.7%، وT4 3/60 = 5% (حادثة واحدة)، وT3 2/60 ≈ 3.3%؛ وT6 100% من التشغيلات الليلية و0% من تشغيلات طلبات الدمج. وعلى مستوى الحزمة: 21 من 60 تشغيلاً احتاج إلى إعادة محاولة (35%)، بسبب 24 نتيجة متذبذبة في 60 × 48 = 2880 تنفيذاً للاختبارات (نحو 0.8%)؛ فمعدل صغير لكل اختبار يكفي ليصيب ثلث التشغيلات. أكبر المتسببين: T1 وT2 مسؤولان عن 15 من 24 إخفاقاً متذبذباً في تشغيلات طلبات الدمج (62.5%)، فإصلاح اختبارين يزيل معظم الضجيج. ابدأ اليوم عدّاد زمن الإصلاح لكل اختبار محجور، وراجع القائمة أسبوعياً. الجواب لهند: ليس بعد. 21 من تشغيلات طلبات الدمج الستين (35%) لم تكن خضراء إلا بفضل إعادة المحاولة ودون أثر، وأخفت إعادة المحاولة عيباً حقيقياً في ميزة رموز الخصم؛ وتصبح الحزمة جديرة بالثقة للإصدار 3.4 عندما تُصلَح T1 وT3 وT6، ويخرج T2 وT4 من الحجر بحلول 17 سبتمبر، ويُصلَح T5 ويُؤكَّد، ويعرض الملخص النتائج المتذبذبة؛ وعندها يجب أن يكون تشغيل مرشّح الإصدار أخضر دون أي نتيجة متذبذبة.
إن اختلفت إجاباتك فراجعها مقابل قواعد الدليل: كل إصلاح ينتظر شرطاً أو يزيل اعتماداً مشتركاً، ولا ينتظر الوقت أبداً؛ وإعادة المحاولة والحجر يحتويان ولا يعالجان؛ والإخفاق الذي قد يكون عيباً في المنتج يُحقَّق فيه قبل أن يُلام الاختبار. والاختيار المختلف المعلَّل مقبول، مثل حجر T1 يوماً واحداً ريثما يُكتب انتظار عرض السعر، ما دام له مالك وتذكرة وموعد نهائي.
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.
جزء من مسار تعلم ضمان الجودة
قبل هذا الموضوع
الموضوعات التالية
الأدوات المذكورة في هذا الدليل
ولّد سجلات مستخدمين وهمية (الاسم واسم المستخدم والبريد الإلكتروني وكلمة المرور وبذرة الصورة الرمزية والدور) لحسابات الاختبار.
مصنع متكامل لبيانات الاختبار الاصطناعية: مستخدمون وعملاء وشركات وعناوين وأرقام وتواريخ وسلاسل نصية بصيغة JSON أو CSV أو SQL أو XML.
أنشئ طلبات HTTP وأرسلها (الطريقة وعنوان URL والترويسات والمصادقة والجسم) وافحص الاستجابة — بديل خفيف لـ Postman داخل متصفحك.
ولّد استجابات API وهمية كاملة (الحالة والترويسات وجسم JSON) لحالات النجاح والقوائم والتفاصيل والأخطاء والنتائج الفارغة.