الاختبار الآلي لواجهة المستخدم في تطبيقات سطح مكتب Windows ── آلية UI Automation وبناء اختبارات لا تنكسر بسهولة باستخدام FlaUI

· آخر تحديث: · · اختبار, UI Automation, FlaUI, WinForms, WPF, C#, .NET, Windows, CI/CD

سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)

سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.

أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621612)

هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.

غو كومورا (2026). الاختبار الآلي لواجهة المستخدم في تطبيقات سطح مكتب Windows ── آلية UI Automation وبناء اختبارات لا تنكسر بسهولة باستخدام FlaUI. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621612 https://comcomponent.com/ar/blog/windows-desktop-ui-automation-testing/

DOI (أحدث نسخة)
10.5281/zenodo.21621612
DOI (هذه النسخة)
10.5281/zenodo.22240997

«في كل إصدار، يستغرق التحقّق اليدوي بالنقر على كل الشاشات يومين كاملين» أو «انكسرت شاشة أخرى بجوار الموضع الذي أُصلح الشهر الماضي، واكتُشف ذلك لدى العميل» أو «فريق الويب يؤتمت اختبارات الانحدار عبر Selenium أو Playwright، بينما بقي تطبيق سطح المكتب دون مساس». هذا النوع من الاستشارات نتلقّاه كثيراً من الجهات التي تصون تطبيقات أعمال WinForms / WPF منذ زمن طويل.

بالمقابل، نسمع بالقدر نفسه قصص فشل عن فرق بدأت بحماس بكتابة اختبارات لكل الشاشات، ثم تخلّت عنها بعد سنة لعجزها عن تحمّل عبء الصيانة. فإن أخطأت في اختيار الأداة وفي تحديد «إلى أين يمتد النطاق»، يصبح الاختبار الآلي لواجهة المستخدم أكلف من الاختبار اليدوي. وبالعكس، إن فهمت الآلية وصمّمت اختبارات لا تنكسر بسهولة، وحصرت النطاق في اختبارات الدخان، يتحوّل الأمر إلى استثمار يستبدل «اليومين قبل الإصدار» بـ«20 دقيقة ليلية دون مراقبة». في هذا المقال، نرتّب بالكامل النمط الذي نستخدمه في مشاريعنا الفعلية: من آلية UI Automation (UIA) التي تُعد الأساس، إلى وضع الأدوات الحالي (FlaUI / WinAppDriver / Appium)، والتنفيذ عبر FlaUI، والتصميم المقاوم للانكسار، وصولاً إلى التشغيل غير المراقَب في CI.

1. الخلاصة أولاً

في سطر واحد: «ابحث بـ AutomationId، وتحكّم بنمط التحكّم، واحصر النطاق في اختبارات الدخان». فيما يلي التفصيل.

  • الاختبار الآلي لواجهة المستخدم هو أعلى طبقة في هرم الاختبار. فهو بطيء وسهل الانكسار ويستغرق تحديد سبب الفشل وقتاً طويلاً، لذا لا يحل محل الاختبارات الوحدوية والتكاملية. احمِ المنطق في الطبقات السفلى، واقصر اختبار واجهة المستخدم على اختبارات دخان تتحقّق من «تشغيل التطبيق ونجاح المسارات الرئيسية» (الفصل 6).
  • أساس الآلية هو Windows UI Automation (UIA). يُبحث عن العناصر انطلاقاً من شجرة الأتمتة التي جذرها سطح المكتب، وتُحدَّد عبر خصائص مثل AutomationId / Name، وتُشغَّل عبر أنماط التحكّم (مثل Invoke وValue وSelectionItem).12
  • تحقّق من كيفية ظهور عناصر التطبيق المستهدف قبل كتابة أي شيفرة، عبر inspect.exe أو Accessibility Insights for Windows. أداة inspect أداة قديمة مرفقة مع Windows SDK، وأصبحت Accessibility Insights هي الموصى بها رسمياً حالياً.3
  • توصيتنا للأداة هي FlaUI + xUnit / NUnit. فـFlaUI مكتبة OSS برخصة MIT تغلّف UIA بشكل خفيف، وتدعم كلاً من UIA2 وUIA3، وما زالت تُصان بنشاط حتى الآن.4
  • WinAppDriver، الذي كان الخيار الرسمي من Microsoft سابقاً، توقّف عند نسخته المستقرّة الأخيرة v1.2.1 الصادرة في نوفمبر 2020، والشيفرة المصدرية للخادم غير منشورة فلا يمكن للمجتمع إصلاحها بنفسه. لا نُنصح باعتماده في مشاريع جديدة (الفصل 3).5
  • 80% من مقاومة الانكسار يتقرّر بـقاعدة يضعها فريق التطوير لتخصيص AutomationId. في WPF هو x:Name أو AutomationProperties.AutomationId، وفي WinForms هو Name / AccessibleName. امنع البحث المعتمد على النص المعروض (Name)، والنقر بالإحداثيات، وThread.Sleep، واكتب الاختبارات عبر الانتظار الشرطي (Retry) ونمط Page Object (الفصلان 4 و5).67
  • التشغيل غير المراقَب في CI يتطلّب جلسة سطح مكتب تفاعلية بالضرورة. لا يعمل مع عميل مهيَّأ كخدمة، ويتعطّل أيضاً مع قفل الشاشة أو انقطاع اتّصال RDP. الشكل الأساسي هو تهيئة مشغّل ذاتي الاستضافة مع تسجيل دخول تلقائي (autologon) (الفصل 7).8

في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 24، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle

2. الآلية ── شجرة UI Automation وخصائصها وأنماطها

2.1 شجرة الأتمتة

أدوات الاختبار الآلي لواجهة المستخدم لا تتعرّف على الشاشة كصورة. يملك Windows بنية أساس رسمية تُسمّى UI Automation (UIA) تتيح لتقنيات المساعدة مثل قارئات الشاشة قراءة واجهة التطبيق والتحكّم بها برمجياً، والاختبار الآلي يسير على المسار نفسه.1

في UIA، تُعرض جميع النوافذ المفتوحة وعناصر التحكّم بداخلها في بنية شجرية جذرها سطح المكتب. كل زر وصندوق نص وصف في شبكة هو عنصر أتمتة على هذه الشجرة. إلى جانب العرض الخام (raw) الذي يضم كل العناصر، توجد عروض مصفّاة هي عرض التحكّم (control view) المقتصر على عناصر التحكّم القابلة للتشغيل، وعرض المحتوى (content view) المقتصر على المحتوى فقط.1 وشيفرة الاختبار، من حيث الأساس، تتنقّل في عرض التحكّم بحثاً عن العناصر.

العروض الثلاثة ليست خيارات متوازية، بل اختلاف في شدّة المرشّح على الشجرة الواحدة نفسها. علاقة الاحتواء متداخلة.

عرض raw ── كل العناصر على الشجرةعرض control ── العناصر التي قد تكون هدفاً للتشغيلعرض content ── العناصر التي تنقل معلومات للمستخدمزر SaveButtonصندوق نص CustomerNameBoxعنصر قائمة شركة الاختبارشريط العنوانشريط التمريرإطار المجموعةخط فاصل للزينةعنصر وسيط للتخطيط فقط

طريقة القراءة: «ما في عرض content موجود أيضاً في عرض control، وما في عرض control موجود أيضاً في عرض raw». العناصر التي تبحث عنها في الاختبار تدخل عادة العرضين الداخليين. والعكس، إن اتخذت عنصراً لا يظهر إلا في عرض raw (كحاوية للتخطيط فقط) مرتكزاً للبحث، ينكسر الاختبار بمجرّد تغيير بسيط في بناء الواجهة. في inspect.exe يمكن تبديل هذا العرض من قائمة Options عبر «Raw View» و«Control View» و«Content View»، فانظر عند الجرد في القسم 2.3 «إلى أي عرض يبقى العنصر الذي تبحث عنه»، فيسهل تصميم شرط البحث.3

النقطة المهمة هنا أنه لا يمكن التحكّم إلا بما هو «مكشوف على الشجرة»، لا بما هو «مرئي بالعين». الشاشات المبنية بعناصر تحكّم قياسية تظهر بوضوح على الشجرة، لكن القوائم المرسومة ذاتياً (owner-drawn)، أو مناطق الرسم المرسومة بمكتبة رسوميات، أو أجزاء من شبكات لطرف ثالث، قد تظهر على الشجرة كـ«عنصر واحد فقط لا غير». وهذا الشكل الظاهر يصبح مباشرة الحد الأعلى لنطاق الاختبار الآلي لواجهة المستخدم. ولهذا السبب بالذات يكون التحقّق من الشجرة قبل كتابة أي شيفرة (القسم 2.3) هو العمل الأول.

2.2 الخصائص وأنماط التحكّم

الدليل الذي يحدّد العنصر المطلوب على الشجرة هو الخصائص. الأربع المستخدمة عملياً هي التالية.

الخاصية المحتوى مكانتها في الاختبار
AutomationId معرّف يضعه المطوّر، لا يعتمد على اللغة (locale) المرشّح الأول كمفتاح بحث. اجعله فريداً بين العناصر الشقيقة 6
Name اسم مشتق من النص المعروض (كتسمية الزر مثلاً) مفهوم للبشر، لكنه ينكسر مع تغيير الصياغة أو تعدّد اللغات
ControlType النوع مثل Button / Edit / ComboBox مساعد في التضييق
ClassName اسم صنف التنفيذ (كاسم صنف WinForms) الملاذ الأخير. سهل الانكسار مع تغيّر التنفيذ

عُرّف AutomationId رسمياً بأنه «ينبغي أن تبقى قيمته ثابتة حتى مع تغيّر اللغة» و«ينبغي أن يكون فريداً بين العناصر الشقيقة»، وهو خاصية مخصَّصة لتمكين الاختبار الآلي لواجهة المستخدم من إيجاد العناصر بثبات عبر اللغات والإصدارات.6 وبالمقابل، فإن اختبار واجهة المستخدم لتطبيق لم تُخصَّص فيه AutomationId يضطر إلى الاعتماد على أدلّة سهلة الانكسار كالنص المعروض أو الموضع على الشجرة. وهذا هو موضوع الفصل 5.

وسيلة التحكّم بالعنصر بعد إيجاده هي أنماط التحكّم. يكشف UIA الجوانب الوظيفية لعنصر التحكّم مثل «قابل للنقر» و«له قيمة» و«قابل للاختيار» كمجموعة من الأنماط مستقلّة عن نوع عنصر التحكّم. وكما توضّح الوثائق نفسها أن «العلاقة بين أنماط التحكّم وواجهة المستخدم مماثلة للعلاقة بين كائنات COM والواجهات»، فالتصميم يقوم على سؤال العنصر عن «أي الأنماط ينفّذها»، ثم التحكّم به عبر ذلك النمط.2 لمن لديه إلمام بـCOM، يمكن اعتبارها نسخة واجهة المستخدم من QueryInterface. الأنماط الرئيسية هي التالية.

النمط ما يمكن فعله عنصر تحكّم نمطي
Invoke تنفيذ الإجراء الافتراضي (يعادل النقر) الأزرار، عناصر القوائم
Value قراءة القيمة وتعيينها صندوق النص
SelectionItem / Selection اختيار عنصر، وقراءة حالة الاختيار القوائم، القائمة المنسدلة، التبويبات
Toggle التبديل بين تشغيل/إيقاف خانة الاختيار
ExpandCollapse التوسيع والطي القائمة المنسدلة، عناصر الشجرة
Text قراءة محتوى النص المستندات، النص المنسّق
Window التكبير والتصغير والإغلاق النافذة الرئيسية (top-level)
Scroll / ScrollItem التمرير، وإحضار عنصر إلى موضع مرئي القوائم، الشبكات

شيفرة اختبار «النقر على زر» هي، من الداخل، عملية «الحصول على نمط Invoke لذلك العنصر واستدعاء Invoke()». فبدل حساب الإحداثيات وإرسال حدث ماوس، يُستدعى الإجراء الذي يكشفه عنصر التحكّم نفسه ── وهذا الفرق هو أساس اختبار مستقر لا يعتمد على موضع النافذة أو DPI.

2.3 التحقّق من «ما هو ظاهر» عبر inspect.exe وAccessibility Insights

أفضل طريقة لمعرفة الـ AutomationId / Name / ControlType / الأنماط التي تُكشف بها عناصر التطبيق المستهدف هي رؤيتها فعلياً بأداة.

  • inspect.exe: أداة تقليدية مرفقة مع Windows SDK (توجد في bin\<version>\<platform> من موضع تثبيت الـ SDK). عند اختيار عنصر بالماوس أو تركيز لوحة المفاتيح، تُعرض قائمة بخصائص UIA وأنماطه، ويمكن أيضاً التحقّق من التنقّل على الشجرة. لكنها مصنَّفة رسمياً كـ«أداة قديمة» ويُوصى بالانتقال إلى Accessibility Insights.3
  • Accessibility Insights for Windows: الأداة الموصى بها حالياً من Microsoft. ميزة Live Inspect مفيدة إذ تتيح التحقّق من خصائص UIA لعنصر بمجرّد تمرير الماوس فوقه أو نقل التركيز إليه، وتتضمّن أيضاً فحصاً آلياً (FastPass) من زاوية إمكانية الوصول.3
  • FlaUInspect: أداة فحص مرفقة مع مشروع FlaUI. تتيح التحقّق من الشجرة من منظور كل من UIA2 وUIA3 اللذين تستخدمهما FlaUI فعلياً، لذا يُنصح بتثبيتها أيضاً عند كتابة اختبارات بـFlaUI.4

أين تنظر في inspect.exe

بدل صورة الشاشة، نكتب أي جزء من النافذة يعرض ماذا. inspect.exe موجود في bin\<version>\<platform> من موضع تثبيت Windows SDK، ولا يلزم عادة تشغيله كمسؤول. عند التشغيل يتتبع افتراضياً تركيز لوحة المفاتيح أو الماوس، وتظهر معلومات العنصر المحدَّد على اليمين.3

جزء النافذة ما يظهر ما تنظر إليه في الاختبار
عرض الشجرة (يسار) تراتب العناصر وجذره سطح المكتب. تمشّ هنا لتأكيد علاقة الأصل والفرع هل العنصر المطلوب ظاهر على الشجرة، وتحت أي أصل
عرض البيانات (يمين) خصائص UIA للعنصر المحدَّد كأسماء وقيم AutomationId، Name، ControlType، ClassName، IsEnabled، BoundingRectangle
قائمة Options تبديل وضع العرض وطريقة التتبّع «UI Automation Mode» (وليس MSAA Mode)، و«Raw View» و«Control View» و«Content View»، و«Watch Focus» و«Watch Cursor»، و«Show Highlight Rectangle»
قائمة Action يمكن تنفيذ دوال UIA على العنصر المحدَّد في وضع UI Automation تظهر أنماط التحكّم التي ينفّذها العنصر (القسم 2.2) كبنود كما هي
Options > Settings انتقاء الخصائص المعروضة في عرض البيانات قلّل العرض إن كثرت البنود. «Display unsupported properties» يظهر أيضاً الخصائص غير المدعومة

الاستخدام العملي كالتالي. تأكّد أولاً أن الوضع «UI Automation Mode»، فعّل «Watch Focus»، ثم نفّذ عمليات لوحة المفاتيح في التطبيق المستهدف؛ يتحدّث عرض البيانات كلما انتقل التركيز. انظر هناك هل AutomationId غير فارغ. إن كان فارغاً فتعديل جانب التطبيق في الفصل 5 أولاً. ثم افتح قائمة Action وتحقّق هل يظهر Invoke للزر الذي تريد ضغطه، وهل يظهر Value للحقل الذي تريد الإدخال فيه. ما يظهر هنا هو العمليات التي يمكن استدعاؤها من شيفرة الاختبار كما هي. لالتقاط عناصر لا ينتقل إليها التركيز، كالقوائم والأدوات الظاهرة ديناميكياً، بدّل إلى «Watch Cursor».3

بحسب خبرتنا، أول ما ينبغي فعله عند إدخال الاختبار الآلي لواجهة المستخدم ليس «كتابة شيفرة الاختبار»، بل فتح الشاشات الرئيسية بأدوات فحص من نوع inspect وجرد مدى تخصيص AutomationId فيها. فإن كانت شحيحة، فالبدء أولاً بتعديل التطبيق (الفصل 5) هو الطريق الأسرع في النهاية.

3. خيارات الأدوات ── لماذا نرجّح FlaUI ووضع WinAppDriver الحالي

يمكن استدعاء UIA مباشرة عبر COM، لكن الممارسة العملية تستخدم مكتبات تغليف. نرتّب هنا الخيارات ووضعها الحالي اعتباراً من عام 2026، استناداً إلى حقائق تحقّقنا منها.

الأداة الشكل الوضع الحالي (اعتباراً من 2026) معيار الاعتماد الجديد
FlaUI مكتبة .NET (MIT) OSS تُصان بنشاط. صدرت v5.0.0 في فبراير 2025 4 ◎ المرشّح الأول
WinAppDriver خادم بروتوكول WebDriver (من Microsoft) آخر نسخة مستقرّة v1.2.1 في نوفمبر 2020. بقيت v1.3 عند RC من يوليو 2020. أكثر من 1,100 issue غير محلولة 5 △ متوقّف عملياً. تجنّب الاعتماد الجديد
Appium Windows Driver مشغّل Windows الخاص بـAppium يستخدم WinAppDriver داخلياً فيرث القيود نفسها 9 △ فقط عند وجود أصول قائمة على Appium
Coded UI Test ميزة في Visual Studio أُهملت في VS 2019، وحُذفت في VS 2026 10 × هدف للترحيل

3.1 FlaUI ── الأكثر عملية الآن كمغلف خفيف لـ UIA

FlaUI مكتبة .NET تدعم الاختبار الآلي لواجهة المستخدم في تطبيقات Windows (Win32 / WinForms / WPF / تطبيقات المتجر)، وهي مصمَّمة كمغلف لمكتبة UIA الأصلية من Microsoft.4 تتكوّن الحزمة من FlaUI.Core المشترك، وتنقسم إلى FlaUI.UIA2 / FlaUI.UIA3 حسب تنفيذ UIA المستخدم.

الفرق في الاستخدام بين UIA2 وUIA3 موضَّح في الأسئلة الشائعة لـFlaUI. UIA2 تنفيذ مدار فقط، لا يدعم الميزات الجديدة كاللمس، وتوافقه مع WPF وتطبيقات المتجر ضعيف. أما UIA3 فهو التنفيذ الأحدث، وهو الأنسب لـ WPF وتطبيقات المتجر، لكنه قد يواجه في تطبيقات WinForms مشكلات لا توجد في UIA2 ── هذه هي العلاقة بينهما.4 بمعنى آخر، المعيار العملي هو تجربة UIA3 مع WPF وUIA2 مع WinForms، ثم استخدام الأكثر استقراراً مع التطبيق الفعلي. وميزة FlaUI كونها غلافاً خفيفاً هي إمكانية تثبيت الاثنين من NuGet والتبديل بينهما للتجربة.

بما أنها لا تملك إطار اختبار خاصاً بها، تُكتب كمشروع اختبار عادي بالدمج مع xUnit / NUnit / MSTest. وكونها تعمل على نفس مشغّل الاختبارات وخط أنابيب CI المستخدمين للاختبارات الوحدوية أمر مفيد جداً من الناحية التشغيلية.

3.2 WinAppDriver ── بصراحة، متوقّف عملياً

WinAppDriver خادم اختبار واجهة مستخدم من إنتاج Microsoft، وكان يُعامل كخيار أول لفترة، لأنه يتيح التحكّم بتطبيقات Windows عبر بروتوكول WebDriver نفسه المستخدم في Selenium. وعندما أُهملت اختبارات Coded UI في Visual Studio، أرشدت Microsoft نفسها إلى الانتقال إلى «Selenium للويب، وAppium + WinAppDriver لسطح المكتب وUWP».10

لكن عند التحقّق من الوضع على GitHub، نجد أن إصدار النسخة المستقرّة الأخيرة v1.2.1 كان في نوفمبر 2020، ولم تصدر بعدها أي نسخة مستقرّة. وبقيت v1.3 عند مرحلة Release Candidate (v1.2.99) من يوليو 2020 دون أن تتحوّل إلى نسخة رسمية، وتجاوز عدد المشكلات غير المحلولة 1,100 مشكلة.5 والأكثر إزعاجاً أن مستودع GitHub لا يحتوي إلا على التوثيق والأمثلة ومتتبّع المشكلات، أما الشيفرة المصدرية للخادم نفسه فغير منشورة. أي أن المجتمع لا يستطيع إصلاح الأخطاء بنفسه. تقديرنا هو أنه ليس هدفاً للاعتماد الجديد في 2026 لمجرّد أنه «رسمي فيُعد آمناً». إن كانت لديك أصول اختبار قائمة على WinAppDriver بالفعل فلا حاجة للتخلّي عنها فوراً، لكن أوقف التوسّع فيها ووجّه اختبارات المسارات الجديدة نحو FlaUI.

3.3 Appium Windows Driver وخيارات أخرى

Appium Windows Driver (appium-windows-driver) مشغّل للتحكّم بتطبيقات Windows من Appium، لكن حقيقته هي «واجهة إلى WinAppDriver المقدَّم من Microsoft»، وكل المعالجة الثقيلة تقع على عاتق WinAppDriver.9 لذا يرث ركود WinAppDriver كما هو. يبقى خياراً للفرق الموحّدة على Appium في الموبايل والويب، أو الفرق التي تريد كتابة شيفرة الاختبار بـ Java أو Python، لكن في هذه الحالة أيضاً، اعتمده مع إدراك القيود المنبثقة من WinAppDriver وآفاقه المستقبلية.

تطبيقات WinUI 3 (Windows App SDK) أيضاً تدعم UIA، ويمكن أن تكون هدفاً للاختبار عبر FlaUI (UIA3). لكن مقارنة بـ WinForms / WPF، لا يزال تراكم الحالات العملية قليلاً، فتصبح مراجعة سمات ظهور كل عنصر تحكّم مسبقاً بأدوات inspect أكثر أهمية. أما اختيار إطار واجهة المستخدم نفسه فقد رتّبناه في «كيفية اختيار WinForms/WPF/WinUI - جدول قرار عملي».

4. التنفيذ الأدنى عبر FlaUI ── التشغيل والبحث والتحكّم والتحقّق

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

ما يلزم تجهيزه

النوع ما يُجهَّز ملاحظة
هدف الاختبار جسم التطبيق المبني (المسار الكامل لـ exe) خيار تشغيل يتيح استبدال ملف الإعدادات وقاعدة البيانات لأغراض الاختبار يجعل الأمر أسهل بكثير (القسم 5.4)
SDK .NET SDK (يُنصح بـ 8 فما بعده) UIA خاص بـ Windows، فاجعل هدف مشروع الاختبار net8.0-windows
NuGet FlaUI.UIA3 (لـ WPF) أو FlaUI.UIA2 (لـ WinForms) FlaUI.Core المشترك يدخل كتابع. يمكن تثبيت الاثنين والتبديل بينهما (القسم 3.1)4
NuGet xunit + xunit.runner.visualstudio يدخلان بـ dotnet new xunit. NUnit / MSTest جائزان أيضاً
أداة التحقّق inspect.exe أو Accessibility Insights for Windows اجرد ظهور التطبيق المستهدف قبل كتابة الشيفرة (القسم 2.3)3
بيئة التشغيل Windows بشاشة. لا يلمس أحد الجهاز أثناء التشغيل شروط الانتقال إلى التشغيل غير المراقَب في الفصل 7

إنشاء المشروع ثلاثة أوامر.

dotnet new xunit -o OrderManager.UiTests
cd OrderManager.UiTests
dotnet add package FlaUI.UIA3

أعد كتابة TargetFramework في ملف .csproj المنشأ إلى net8.0-windows (لأن UIA خاص بـ Windows). وإلى جانب ذلك، اختبارات الواجهة يجب أن تجري تسلسلياً واحدة لكل جهاز (القسم 7.2)، فأوقف التنفيذ المتوازي في xUnit من البداية.

// テストプロジェクト内のどこか(AssemblyInfo.cs など)に1回だけ書く
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]

إلى هنا، الباقي مشروع اختبار عادي. لا مشغّل مخصّص ولا نوع مشروع مخصّص.

4.1 الشكل الأساسي لاختبار الدخان

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

using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Tools;
using FlaUI.UIA3;
using Xunit;

public class OrderSmokeTest
{
    [Fact]
    public void 受注登録_主要導線が通る()
    {
        using var app = Application.Launch(@"C:\App\OrderManager.exe");
        using var automation = new UIA3Automation();
        try
        {
            // メインウィンドウが出るまで待ってくれる
            var window = app.GetMainWindow(automation);

            // AutomationId で要素を探し、Button として操作する
            window.FindFirstDescendant(cf => cf.ByAutomationId("NewOrderButton"))
                  ?.AsButton().Invoke();

            // ダイアログは非同期に開くので「出るまで」条件待機する
            var dialog = Retry.WhileNull(
                () => window.FindFirstDescendant(
                          cf => cf.ByAutomationId("OrderDialog"))?.AsWindow(),
                timeout: TimeSpan.FromSeconds(5)).Result;
            Assert.NotNull(dialog);

            // Value パターン経由で入力(キーボードエミュレーションではない)
            dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox"))
                  .AsTextBox().Text = "شركة الاختبار";
            dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton"))
                  .AsButton().Invoke();

            // 保存完了もステータス表示を条件待機で検証する
            var saved = Retry.WhileFalse(
                () => window.FindFirstDescendant(
                          cf => cf.ByAutomationId("StatusLabel"))
                          ?.Name.Contains("تمّ الحفظ") == true,
                timeout: TimeSpan.FromSeconds(10));
            Assert.True(saved.Success);
        }
        finally
        {
            app.Close();
        }
    }
}

تتكوّن الشيفرة من 4 عناصر فقط.

  • التشغيل: تُشغَّل العملية عبر Application.Launch (يمكن أيضاً الاتّصال بتطبيق مشغَّل بالفعل عبر Application.Attach). تنتظر GetMainWindow حتى يمكن الحصول على النافذة الرئيسية.
  • البحث: FindFirstDescendant(cf => cf.ByAutomationId(...)) هو بحث على الشجرة. cf هو مصنع الشروط، ويمكن أيضاً تركيب شروط ByName / ByControlType / And. كما ذُكر، مفتاح البحث المرشّح الأول هو AutomationId.
  • التحكّم: يُحوَّل العنصر الذي عُثر عليه إلى غلاف مصنَّف بنوع مثل AsButton() / AsTextBox() / AsComboBox() ثم يُتحكَّم به. Invoke() هو نمط Invoke، وخاصية Text هي نمط Value، وأنماط التحكّم من القسم 2.2 كامنة خلفهما مباشرة.
  • التحقّق: تُختبر النتيجة الظاهرة على واجهة المستخدم (تسمية، عدد أسطر قائمة، عنوان نافذة…) بـ assert. وإن أردت التحقّق من محتوى قاعدة البيانات، يمكنك قراءتها مباشرة من شيفرة الاختبار.

4.2 الانتظار عبر Retry ── كتابة Sleep تعني الخسارة

أكبر مصدر لعدم استقرار اختبارات واجهة المستخدم (ما يُعرف بـ flaky test) هو التوقيت. حتى يُفتح مربّع الحوار، وحتى تُحمَّل البيانات، وحتى يُفعَّل الزر ── واجهة المستخدم تتغيّر دائماً بشكل غير متزامن، وإن افترضت شيفرة الاختبار أن «العنصر ظهر بالفعل» بشكل جازم، ينتج اختبار يفشل فقط على الأجهزة البطيئة.

ومع ذلك، فإدراج Thread.Sleep(3000) هو أسوأ معالجة. فعلى الأجهزة السريعة ينتظر بلا داعٍ، وعلى الأجهزة البطيئة لا يكفي فيفشل، ولا يتراكم إلا زمن تنفيذ الاختبارات كاملاً. الجواب هو الانتظار الشرطي، أي «الاستقصاء حتى يتحقّق الشرط، مع القطع عند انتهاء المهلة»، ولدى FlaUI صنف مخصَّص لذلك هو Retry.4

// null でなくなる(=要素が現れる)まで最大 5 秒待つ
var element = Retry.WhileNull(
    () => window.FindFirstDescendant(cf => cf.ByAutomationId("ResultGrid")),
    timeout: TimeSpan.FromSeconds(5),
    interval: TimeSpan.FromMilliseconds(200),
    throwOnTimeout: true).Result;

// 条件が true になる(=ボタンが有効になる)まで待つ
Retry.WhileFalse(
    () => saveButton.IsEnabled,
    timeout: TimeSpan.FromSeconds(5),
    throwOnTimeout: true);

تتوفّر Retry.WhileNull / WhileFalse / WhileTrue / WhileException وغيرها، ويمكن تحديد المهلة، وفاصل الاستقصاء، وما إذا كان يُرمى استثناء عند انتهاء المهلة.4 نقطة تنبيه: تخلّت FlaUI منذ الإصدار 2.0 عن إعادة المحاولة الضمنية في دوال Find، واتّبعت سياسة «شيفرة الاختبار هي التي تحدّد أين ننتظر بوضوح».4 بما أن FindFirstDescendant لا يرى إلا «الشجرة في هذه اللحظة بالذات»، اجعل من القاعدة إحاطة أي بحث عن عنصر يظهر بشكل غير متزامن بـ Retry دائماً. والفكرة نفسها القائلة «ضع شرط انتظار بدل الاعتماد على Sleep» ليست خاصّة باختبار واجهة المستخدم، بل هي قاعدة أساسية في برمجة Windows عموماً (راجع «لماذا يُفضَّل انتظار الأحداث على Sleep(1) في Windows»).

5. تصميم مقاوم للانكسار ── قواعد لطرفي التطبيق والاختبار

أسباب التخلّي عن اختبارات واجهة المستخدم بسبب «العجز عن تحمّل الصيانة» تنحصر تقريباً في هذه الأربعة: البحث المعتمد على النص المعروض، والنقر بالإحداثيات، وSleep، وضعف مشاركة البنية. نتعامل مع كل منها بقاعدة.

5.1 تخصيص AutomationId من جانب التطوير دائماً

هذا هو الأهم على الإطلاق. فمقاومة الاختبار للانكسار تتقرّر، قبل طريقة كتابة شيفرة الاختبار، بـما إذا كان التطبيق يكشف معرّفات ثابتة أم لا. وAutomationId خاصية مخصَّصة لهذا الغرض بالذات، وفق مواصفات تشترط عدم اعتمادها على اللغة وكونها فريدة بين العناصر الشقيقة.6

في WPF، يُستخدم x:Name الذي وُضع على عنصر كمعرّف من جانب UIA، لذا فعناصر التحكّم المسمّاة بالفعل قابلة للاختبار دون عمل إضافي. أما إن أردت تحديد معرّف صراحة، كما في قوالب البيانات مثلاً، فاضبط الخاصية المرفقة AutomationProperties.AutomationId.67

<!-- x:Name がそのまま識別子になる -->
<Button x:Name="SaveButton" Content="حفظ" Click="OnSave" />

<!-- テンプレート内などは AutomationProperties.AutomationId を明示 -->
<Button AutomationProperties.AutomationId="DeleteRowButton"
        Content="حذف"
        Command="{Binding DeleteCommand}" />

في WinForms، يُستخدم Control.Name الذي يُخصَّص في المصمّم (وهو الاسم نفسه الذي تغيّره من button1 إلى saveButton) في التمييز من جانب UIA. وبحسب خبرتنا، معظم النماذج التي خُصّص فيها Name وفق القاعدة يمكن البحث فيها مباشرة بـ AutomationId، لكن بسبب اختلاف طريقة الظهور باختلاف جيل الإطار وعنصر التحكّم، تحقّق دائماً من AutomationId الفعلي بأدوات inspect قبل استخدامه كمفتاح بحث في الاختبار. كذلك، خاصية Name في UIA التي تصبح الاسم المنطوق لقارئات الشاشة، تُستعار في معظم عناصر التحكّم من خاصية Text، لكن في أنواع لا تُستعار فيها مثل TextBox وListView يلزم تعيين AccessibleName صراحة.11 ونقطة أن تجهيز AutomationId ليس لأجل الاختبار فحسب، بل هو نفس عمل دعم إمكانية الوصول تماماً، مادّة جيّدة عند إقناع الإدارة بتخصيص ميزانية له.

تكفي قاعدة بسيطة، ونحن نطلب من عملائنا إضافة السطرين التاليين إلى معايير الترميز:

  • امنح عناصر التحكّم الموضوعة على الشاشة اسماً دالاً، Name (في WinForms) أو x:Name أو AutomationProperties.AutomationId (في WPF)، ما دام يُحتمل أن تكون هدفاً للتحكّم أو التحقّق
  • إذا أُشير إلى معرّف من الاختبار مرّة واحدة، فأعد تسميته بالتزامن مع الاختبار (اعتبر المعرّف واجهة برمجية عامة)

5.2 حظر النقر بالإحداثيات

أي عملية على شكل «النقر على إحداثيات الشاشة (830, 412)» تنكسر عند تغيّر أي من موضع النافذة، أو الدقة، أو مقياس DPI، أو السمة، أو إعدادات الخط. ويُنتج DPI بالذات، عند اختلاف البيئات كأن يكون جهاز التطوير 100% وجهاز CI ‏150%، حالات كثيرة من نوع «يعمل محلياً لكن يفشل في CI» (آلية DPI مشروحة في «دعم DPI العالي في WinForms»). وكما رأينا في الفصل 2، التحكّم عبر أنماط التحكّم في UIA لا يعتمد على الإحداثيات. تملك FlaUI أيضاً واجهة برمجية للتحكّم المباشر بالماوس، لكن حدّد استخدامها فقط لعمليات لا يمكن التعبير عنها بنمط، مثل السحب والإفلات أو لوحة الرسم. وحتى في هذه الحالة، احسب الموضع النسبي من BoundingRectangle الخاص بالعنصر، لا من إحداثيات الشاشة.

5.3 تجميع البنية في مكان واحد بنمط Page Object

إذا كُتبت شيفرة بحث مثل FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")) مباشرة داخل متن الاختبار، فستضطر لتعديل كل الاختبارات عند تغيّر تكوين الشاشة. المعالجة المعتادة هي نمط Page Object: تُنشأ «فئة واحدة لكل شاشة (أو مربّع حوار)»، ويُحصر بحث العناصر والتحكّم بها داخلها.

public sealed class OrderDialogPage
{
    private readonly Window _dialog;
    public OrderDialogPage(Window dialog) => _dialog = dialog;

    // 要素の検索はこのクラスの中だけに書く
    private TextBox CustomerName =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("CustomerNameBox")).AsTextBox();
    private Button Save =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("SaveButton")).AsButton();
    private Label Status =>
        _dialog.FindFirstDescendant(cf => cf.ByAutomationId("StatusLabel")).AsLabel();

    // ما يظهر من الاختبار هو عمليات «لغة العمل» فقط
    public void Register(string customerName)
    {
        CustomerName.Text = customerName;
        Save.Invoke();
        // ابحث عن العنصر وتحقّق من null داخل إعادة المحاولة. في الشاشات التي
        // تُنشأ فيها تسمية الحالة أو تُعاد رسمها بعد الحفظ، يصبح البحث لحظةً
        // null أو استثناء وهو المسار الطبيعي
        // (بدون ignoreException يفشل الاختبار من أوّل استثناء)
        Retry.WhileFalse(() => Status?.Name.Contains("تمّ الحفظ") == true,
            timeout: TimeSpan.FromSeconds(10), throwOnTimeout: true,
            ignoreException: true);
    }
}

يصبح متن الاختبار قابلاً للكتابة بلغة العمل مثل new OrderDialogPage(dialog).Register("شركة الاختبار")، ويقتصر أثر تغيير الشاشة على تعديل مكان واحد فقط في Page Object. في التطبيقات التي تتجاوز شاشاتها 10 وتحتاج إلى تشغيل اختبار واجهة مستخدم بشكل مستمر، يصبح هذا النمط ضرورياً عملياً. نقطة تنبيه واحدة: احرص على أن يُعاد البحث عن العنصر عبر الخاصية في كل مرّة، كما في Status أعلاه، عند الرجوع إليه من شرط الانتظار. فإن خُزّنت نتيجة البحث في حقل، سيستمر الانتظار حتى انتهاء المهلة وهو ممسك بعنصر قديم اختفى بعد إعادة الرسم.

5.4 استقلالية الاختبار ── عدم تمرير الحالة بين الاختبارات

اجعل كل اختبار من اختبارات واجهة المستخدم مستقلاً بذاته. فإذا خلقت اعتماداً على الترتيب مثل «الاختبار 3 يفترض بيانات أنشأها الاختبار 2»، سيتسلسل فشل اختبار واحد وتفقد إمكانية إعادة الترتيب. المبادئ هي التالية.

  • كل اختبار (أو فئة اختبار) يشغّل التطبيق بنفسه ويغلقه بشكل مؤكَّد عند الانتهاء (عبر finally أو تركيب من نوع IDisposable)
  • البيانات المفترضة يجهّزها طرف الاختبار. تجهيز خيارات تشغيل في التطبيق تتيح استبدال ملف الإعدادات وقاعدة البيانات لأغراض الاختبار يجعل الأمر أسهل بكثير
  • لا تنسَ الترتيب بعد الفشل. بقايا عمليات لم تُغلق أو مربّعات حوار نمطية متبقّية تصبح سبباً لفشل الاختبار التالي (الفصل 7)

6. إلى أين نصل بحماية اختبار واجهة المستخدم ── الحد المرتكز على اختبارات الدخان

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

يأخذ الأمر شكلاً كهذا في جدول القرار.

ما نريد حمايته الطبقة المناسبة السبب
الحسابات والتحويلات وقواعد العمل الاختبار الوحدوي سريع ومستقر. تغطيتها عبر واجهة المستخدم أمر غير منطقي أصلاً
الوصول لقاعدة البيانات، وإدخال/إخراج الملفات، والتكامل الخارجي الاختبار التكاملي يستخدم الأشياء الحقيقية دون حاجة لواجهة المستخدم (كيفية رسم الحدود)
ViewModel ومنطق العرض الاختبار الوحدوي قابل للاختبار دون واجهة مستخدم في نمط MVVM
إمكانية التشغيل، ونجاح المسارات الرئيسية، وإمكانية الحفظ اختبار دخان لواجهة المستخدم هذا هو ميدان اختبار واجهة المستخدم الرئيسي
مسارات حرجة فاتت التحقّق اليدوي سابقاً اختبار واجهة المستخدم (انحدار) يُضاف فقط للمواضع التي وقع فيها ضرر فعلي
تخطيط الشاشة وتشوّه المظهر المعاينة البصرية/مقارنة لقطات الشاشة كتابتها بـ assert جحيم صيانة. اقصر العدد
الحالات غير الطبيعية كالانهيار وتسرّب المقابض بنية أخرى خارج نطاق اختبار واجهة المستخدم (Application Verifier)

طريقة البدء التي نوصي بها هي «نحو 10 اختبارات دخان». «التشغيل وظهور الشاشة الرئيسية»، «إمكانية فتح الجداول الرئيسية»، «تسجيل مستند نمطي واحد والبحث عنه ومعاينة طباعته»، «عدم ظهور خطأ عند الإنهاء» ── تؤتمت مباشرة أعلى 10 مسارات كانت تُراجع يدوياً بالضرورة قبل كل إصدار. بهذا الحجم، تستغرق الكتابة أسبوعاً إلى أسبوعين، ويستقر التشغيل الليلي عند 20 إلى 30 دقيقة، ويصبح عبء الصيانة واقعياً. بعد إحساسك بالأثر، أضف تدريجياً اختبارات منع تكرار أخطاء انحدار وقع منها ضرر فعلي، واحداً تلو الآخر. وبالمقابل، أي خطة تستهدف «كل الشاشات وكل الحقول» شبه مضمونة الانهيار في منتصف الطريق.

الأمر المهم الآخر هو الاستثمار في اتّجاه انتزاع المنطق من واجهة المستخدم بدل زيادة اختباراتها. الشاشات التي كُتب فيها منطق العمل داخل معالجات الأحداث لا يمكن حمايتها إلا باختبار واجهة المستخدم، لكن إن نُقل المنطق إلى ViewModel أو فئة خدمة، أصبح قابلاً للحماية بالاختبار الوحدوي، ويكفي لاختبار واجهة المستخدم أن يتحقّق من «صحّة التوصيل» فقط. شعورنا هو أنه إن أحسست أن عدد اختبارات واجهة المستخدم اللازمة كبير، فذلك غالباً مشكلة تصميم لا مشكلة اختبار. راجع أيضاً «كيفية رسم الحد بين الاختبار الوحدوي والاختبار التكاملي» لتقسيم الاختبارات إلى طبقات، و«الحد الأدنى لمتطلّبات المسجّل الذاتي وقائمة تحقّق الاختبار التكاملي» للممارسة العملية للطبقات السفلى.

7. فخاخ CI والتشغيل غير المراقَب ── لا يعمل اختبار واجهة المستخدم دون سطح مكتب

ما دام اختبار واجهة المستخدم يُشغَّل يدوياً على جهاز المطوّر، فكل شيء هادئ. تتركّز الفخاخ في مرحلة «التشغيل الليلي غير المراقَب في CI»، وخلافاً لاختبار واجهة مستخدم الويب (الذي يكتمل بمتصفّح بلا رأس)، فإن الجذر هو أن اختبار تطبيق سطح المكتب يتطلّب جلسة سطح مكتب تفاعلية حقيقية.

7.1 الجلسة التفاعلية ضرورية ── لا تعمل مع عميل مشغَّل كخدمة

عملاء CI (مثل عميل Azure Pipelines أو مشغّل GitHub Actions ذاتي الاستضافة) يُبقَون عادة مقيمين كخدمة Windows. لكن الخدمة لا تملك سطح مكتب للمستخدم، فلا يمكن التحكّم بنافذة تطبيق يُشغَّل من داخلها. توثيق Azure Pipelines الرسمي نفسه ينص صراحة على أن العميل الذي يشغّل اختبار واجهة مستخدم لتطبيق سطح مكتب يجب أن يهيَّأ كعملية تفاعلية مفعَّل فيها تسجيل الدخول التلقائي (autologon)، لا كخدمة.8 كذلك، لا يدعم العميل المستضاف من Microsoft (المشغّل المشترك المجهَّز في السحابة) اختبارات واجهة المستخدم المرئية، ولا يعمل معه سوى اختبارات المتصفّح بلا رأس.8 أي أن جهازاً ذاتي الاستضافة (سواء كان فعلياً أو آلة افتراضية) ضروري عملياً لاختبار واجهة المستخدم في تطبيقات سطح المكتب.

التوثيق الرسمي نفسه ينبّه إلى مخاطرة أمنية في تهيئة autologon، وهي أن «أي شخص لديه وصول فعلي إلى ذلك الجهاز يمكنه استخدام الحساب المسجَّل دخوله تلقائياً».8 الأساس المفترض هو استخدام حساب مخصَّص للاختبار وجهاز (VM) مخصَّص، وعدم وضع بيانات اعتماد بيئة الإنتاج فيه.

7.2 قفل الشاشة وانقطاع RDP والدقة ── طرق الفشل المعتادة

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

الفخ العرض الحل
العميل يعمل كخدمة لا يُعثر على أي عنصر إطلاقاً، ولا يبدأ تشغيل التطبيق أعد التهيئة كعملية تفاعلية + autologon 8
قفل الشاشة/شاشة التوقّف عمليات الإدخال لا تصل فتفشل تعطيل شاشة التوقّف ضمن تهيئة autologon، وطلب استثناء من سياسات GPO التي تسبّب القفل 8
قطع اتّصال RDP بزر «×» تُقفل الجلسة لحظة الانقطاع، وتفشل كل الاختبارات بعدها استخدم tscon <SessionID> /dest:console لإعادة الجلسة إلى الكونسول قبل قطع الاتّصال 8
اختلاف بيئات الدقة/DPI اختبار يعمل محلياً لكن يفشل فقط في CI ثبّت الدقة (توجد مهمّة إعداد لذلك في Azure Pipelines). وحّد مقياس التحجيم عند 100% 8
التنفيذ المتوازي للاختبارات تدمير متبادل بسبب التنافس على الماوس ولوحة المفاتيح والتركيز نفّذ اختبارات واجهة المستخدم تسلسلياً، اختباراً واحداً في كل جهاز. حقّق التوازي بزيادة عدد الأجهزة (VM)
بقايا فشل سابق عمليات متبقّية أو مربّعات حوار نمطية تعيق التشغيل التالي نظّف العمليات المستهدفة قبل بدء الاختبار، ونفّذ معالجة الإنهاء دائماً عبر finally

فخ RDP سهل الوقوع فيه بشكل خاص، فنوضّحه بإضافة. إذا دخلت إلى جهاز الاختبار عبر سطح المكتب البعيد لإجراء تعديل، ثم أغلقت النافذة وخرجت، تصبح تلك الجلسة مقفلة، وتستمر اختبارات واجهة المستخدم بعدها في الفشل. الحل الذي توضّحه الوثائق الرسمية هو تنفيذ %windir%\System32\tscon.exe <ID> /dest:console من سطر أوامر بصلاحيات المسؤول قبل قطع الاتّصال، لإعادة الجلسة إلى الكونسول.8 احرص على إدراجه في دليل تشغيل جهاز الاختبار.

الدقة وDPI أيضاً بحاجة لانتباه. ليس نادراً أن تبقى آلة VM الخاصة بـ CI عند دقة 1024×768 ومقياس تحجيم افتراضي، فيتغيّر التخطيط ويظهر فرق مثل «زر كان مرئياً على جهاز التطوير لا يظهر إلا بعد التمرير». يمكن استيعاب معظم هذا إن استُبعد النقر بالإحداثيات، لكن تثبيت البيئة يبقى الأفضل. زوايا التحقّق المكتوبة في «دعم DPI العالي في WinForms» صالحة للاستخدام هنا كما هي، بما يشمل حالة دعم DPI من جانب التطبيق.

7.3 حفظ الأدلّة عند الفشل ── لقطات الشاشة والسجلات

عند فشل اختبار واجهة مستخدم يعمل بلا مراقبة، لا يتّضح السبب حتى لو بقي في السجل عبارة «العنصر غير موجود» فقط. جهّز منذ البداية حفظ لقطة شاشة عند الفشل. تملك FlaUI ميزة لالتقاط الشاشة أو عنصر، فاستدعها من خطّاف الفشل في إطار الاختبار، واحفظها كمنتج في CI.

// 失敗時フックなどから呼ぶ。CI のアーティファクト保存先へ出力する
FlaUI.Core.Capturing.Capture.Screen()
    .ToFile(Path.Combine(artifactDir, $"{testName}_{DateTime.Now:HHmmss}.png"));

بالإضافة إلى لقطة الشاشة، إن أمكن مقارنة سجل التطبيق نفسه (إلى أين وصلت المعالجة) وسجل الاختبار (حتى أي تحكّم نجح) بالطابع الزمني، يصبح تحديد «هل هو خطأ في التطبيق، أم في الاختبار، أم في البيئة» أسرع بكثير. نقاط الانتباه عند تشغيل الاختبار ليلاً عبر جدولة المهام (نوع الجلسة، مشكلة الانتهاء برمز 0x1، وغيرها) مشروحة في «مهام جدولة المهام لا تُنفَّذ وتنتهي بـ 0x1».

7.4 مثال أدنى لإعداد CI ── مشغّل GitHub Actions ذاتي الاستضافة

إن أسقطت شروط 7.1 إلى 7.3 في سير العمل، يصبح كالتالي. الفرضية الكبرى: المشغّل يُهيَّأ كعملية تفاعلية لا كخدمة Windows، ويعمل على جهاز مخصّص للاختبار مفعَّل فيه تسجيل الدخول التلقائي (القسم 7.1). إن سقطت هذه، فمهما صح YAML فلن يجد الاختبار عنصراً واحداً.

name: nightly-ui-smoke

on:
  schedule:
    # cron の指定は UTC。JST の平日 0:00 に回したいので UTC では日〜木の 15:00
    - cron: "0 15 * * 0-4"
  workflow_dispatch:   # 調査用に手動実行も残す

jobs:
  ui-smoke:
    # 対話型セッションで動かしているセルフホストランナーに付けたラベル。
    # GitHub ホステッドランナーでは可視 UI のテストは動かない(7.1 節)
    runs-on: [self-hosted, windows, ui-test]
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: 前回の残骸を掃除する
        shell: powershell
        continue-on-error: true
        run: Get-Process OrderManager -ErrorAction SilentlyContinue | Stop-Process -Force

      - name: アプリをビルドする
        run: dotnet publish src/OrderManager -c Release -o artifacts/app

      - name: UIスモークテストを実行する
        run: dotnet test tests/OrderManager.UiTests -c Release --logger "trx;LogFileName=ui.trx"

      - name: 証拠を回収する
        if: always()   # 落ちたときこそ必要なので always
        uses: actions/upload-artifact@v4
        with:
          name: ui-test-evidence
          path: |
            **/TestResults/**
            artifacts/screenshots/**

نقاط الضبط أربع.

  • اجعل runs-on تسمية المشغّل ذاتي الاستضافة. كما في القسم 7.1، المشغّل المستضاف المشترك لا يدعم اختبارات الواجهة المرئية.8
  • ضع timeout-minutes حتماً. اختبار الواجهة المتوقّف عند مربّع حوار نمطي لا يعود بنفسه، فاقطعه من جانب المهمّة.
  • اكتب التنظيف كخطوة. بقايا الفشل السابق (عمليات لم تُغلق) تعيق التشغيل التالي (القسمان 5.4 و7.2).
  • أبقِ الأدلّة بـ if: always(). إن لم تُرفع وجهة لقطات القسم 7.3 (في المثال artifacts/screenshots) وملف trx لنتائج الاختبار كمنتجات، لا يمكن التحقيق في فشل التشغيل غير المراقَب.

وكتابة وقت schedule بـ UTC ليست خاصّة بـ GitHub Actions. كتابتها على أنها توقيت اليابان فتنزاح يوماً حادث نمطي (معالجة التاريخ والمناطق الزمنية مجمَّعة في «التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال»).

8. الخلاصة

الاختبار الآلي لواجهة المستخدم ليس شيئاً «يعمل بمجرّد تثبيت أداة»، بل يستمر فقط عندما تجتمع كلّها معاً: فهم الآلية، وتعاون طرف التطبيق، ورسم حدود النطاق، وتصميم بيئة التنفيذ. نلخّص النقاط الأساسية.

  • الأساس هو UI Automation. يُبحث على الشجرة عبر AutomationId، ويُتحكَّم بها عبر أنماط التحكّم. اجرد أولاً كيفية ظهور التطبيق المستهدف بـ inspect / Accessibility Insights
  • الأداة هي FlaUI + xUnit / NUnit. لم تصدر نسخة مستقرّة من WinAppDriver منذ 2020، فتجنَّب اعتماده في مشاريع جديدة
  • تُبنى مقاومة الانكسار بالقواعد. تخصيص AutomationId من جانب التطوير، وحظر النقر بالإحداثيات، وحظر Sleep واستخدام الانتظار الشرطي عبر Retry، وتجميع البنية في مكان واحد عبر Page Object
  • ابدأ النطاق بـنحو 10 اختبارات دخان. وجّه المنطق نحو الاختبارات الوحدوية والتكاملية، واقتصر اختبار واجهة المستخدم على التحقّق من «نجاح المسارات الرئيسية»
  • الشكل الأساسي لـ CI هو جهاز ذاتي الاستضافة + جلسة تفاعلية + autologon. اقضِ على فخاخ قفل الشاشة وانقطاع RDP واختلاف الدقة عبر إجراءات التشغيل، واحفظ لقطة شاشة عند الفشل دائماً

«التحقّق اليدوي ليومين قبل الإصدار» يمكن استبداله بتكلفة واقعية عبر اختبار آلي لواجهة المستخدم محدَّد النطاق بشكل صحيح. وبالمقابل، إن كان التطبيق الحالي لا يملك أي AutomationId إطلاقاً، أو كان المنطق مكتوباً مباشرة في أحداث الشاشة، فالبدء بتعديل صغير على التطبيق قبل كتابة الاختبار هو الطريق الأقصر في النهاية. يمكننا مساعدتك بدءاً من مسح الوضع الحالي للتطبيق، سواء في تقييم من أين تبدأ، أو في إطلاق مجموعة اختبارات دخان كاملة.

مقالات ذات صلة

مجالات الاستشارة ذات الصلة

تتعامل شركة كومورا سوفت ذ.م.م. مع إدخال الاختبار الآلي لواجهة المستخدم في تطبيقات WinForms / WPF (مسح الوضع الحالي، وتجهيز AutomationId، وبناء مجموعة اختبارات دخان كاملة، وتصميم بيئة CI)، وترحيل أصول الاختبار القائمة إلى FlaUI، ومراجعة تصميم استراتيجية الاختبار ككل.

روابط مرجعية

  1. Microsoft Learn, UI Automation Overview. عن شجرة الأتمتة التي جذرها سطح المكتب، والعروض raw / control / content، وخصائص العناصر وأنماط التحكّم، واستخدام تقنيات المساعدة والاختبار الآلي للبنية الأساس نفسها.  2 3

  2. Microsoft Learn, UI Automation Control Patterns Overview. عن تصميم أنماط التحكّم (بالتماثل مع واجهات COM)، وأنماط مثل Invoke / Value / SelectionItem، وإمكانية تنفيذ عنصر تحكّم واحد لعدّة أنماط.  2

  3. Microsoft Learn, Accessibility tools - Inspect. عن كون inspect.exe أداة مرفقة مع Windows SDK (bin\<version>\<platform>) تتيح التحقّق من خصائص UIA وأنماطه، وتصنيفها كأداة قديمة مع التوصية بـ Accessibility Insights، وتكوّن النافذة من عرض شجرة وعرض بيانات مع تتبّع التركيز افتراضياً، وقائمة Options (UI Automation Mode / Raw View / Control View / Content View / Watch Focus / Watch Cursor / Show Highlight Rectangle / Settings)، وإمكانية تنفيذ أنماط التحكّم للعنصر المحدَّد من قائمة Action.  2 3 4 5 6 7

  4. GitHub, FlaUI/FlaUI. عن كونها مغلّفاً لـ UIA يدعم Win32 / WinForms / WPF / تطبيقات المتجر، وتكوين الحزم FlaUI.Core / UIA2 / UIA3، والفرق في الاستخدام بين UIA2 وUIA3 (الأسئلة الشائعة)، ورخصة MIT، وإصدار v5.0.0 (فبراير 2025) واستمرار التطوير، وأداة Retry، وإلغاء إعادة المحاولة الضمنية في دوال Find منذ الإصدار 2.0.  2 3 4 5 6 7 8 9

  5. GitHub, microsoft/WinAppDriver. عن صدور النسخة المستقرّة الأخيرة v1.2.1 في نوفمبر 2020، وبقاء v1.3 عند مرحلة Release Candidate من يوليو 2020 دون تحديث، وتجاوز عدد المشكلات غير المحلولة 1,100 مشكلة، وكون المستودع مرتكزاً على التوثيق والأمثلة دون نشر الشيفرة المصدرية للخادم نفسه.  2 3

  6. Microsoft Learn, Use the AutomationID Property. عن كون AutomationId معرّفاً لا يعتمد على اللغة، ووجوب كونه فريداً بين العناصر الشقيقة، وسيناريو استخدامه كمفتاح بحث في سكربتات الاختبار، وعدم دعم AutomationId في WPF لعناصر التحكّم التي لا تملك ID (x:Name) أو x:Uid.  2 3 4 5

  7. Microsoft Learn, AutomationProperties.AutomationId Attached Property. عن تعريف الخاصية المرفقة التي تضبط سلسلة نصية تميّز العنصر بشكل فريد في WPF (فضاء الأسماء System.Windows.Automation).  2

  8. Microsoft Learn, UI testing considerations (Azure Pipelines). عن ضرورة تهيئة العميل كعملية تفاعلية مفعَّل فيها autologon لاختبار واجهة مستخدم تطبيقات سطح المكتب، وعدم دعم العميل المستضاف من Microsoft لاختبارات واجهة المستخدم المرئية، والقفل الناتج عن انقطاع RDP وتفاديه بـ tscon، ومهمّة ضبط دقة الشاشة، وجمع لقطات الشاشة والفيديو عند الفشل.  2 3 4 5 6 7 8 9 10

  9. GitHub, appium/appium-windows-driver. عن كون Appium Windows Driver واجهة إلى WinAppDriver المقدَّم من Microsoft، وتنبيه ملف README إلى أن خادم WinAppDriver لم يُصَن منذ فترة طويلة.  2

  10. Microsoft Learn, Use Coded UI tests to test your code. عن إهمال اختبارات Coded UI وكون Visual Studio 2019 آخر نسخة مدعومة بالكامل، وإرشاد تطبيقات سطح المكتب / UWP كوجهة انتقال إلى Appium + WinAppDriver (حذفها في VS 2026 مذكور في دليل الانتقال ضمن نفس Learn).  2

  11. Microsoft Learn, WinForms: Setting the accessible name on a control. عن استعارة خاصية UIA Name لعناصر تحكّم WinForms من خاصية Text في معظم الأنواع، وضرورة تعيين AccessibleName صراحة في أنواع مثل TextBox / ListBox. 

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الأسئلة الشائعة

أسئلة شائعة حول موضوع هذه المقالة.

بماذا ينبغي الاستعانة للاختبار الآلي لواجهة تطبيقات WinForms/WPF؟
توصيتنا هي FlaUI + xUnit/NUnit. فـFlaUI مكتبة OSS برخصة MIT تغلّف Windows UI Automation (UIA) بشكل خفيف، وتدعم كلاً من UIA2 وUIA3، وما زالت تُصان بنشاط حتى الآن، إذ صدرت النسخة v5.0.0 في فبراير 2025. معيار الاختيار بينهما هو تجربة UIA3 مع WPF وUIA2 مع WinForms. ولأنها لا تملك إطار اختبار خاصاً بها، يمكن وضعها على نفس مشغّل الاختبارات وخط أنابيب CI المستخدم للاختبارات الوحدوية. أول عمل قبل كتابة أي شيفرة هو التحقّق من كيفية ظهور عناصر التطبيق المستهدف عبر inspect.exe أو Accessibility Insights for Windows.
هل يجوز اعتماد WinAppDriver من الآن؟
لا ننصح باعتماده في مشاريع جديدة. فـWinAppDriver من إنتاج Microsoft توقّف عند نسخته المستقرّة الأخيرة v1.2.1 الصادرة في نوفمبر 2020، وبقيت v1.3 عند مرحلة Release Candidate من يوليو 2020 دون أن تصل إلى إصدار رسمي، وتجاوز عدد المشكلات غير المحلولة 1,100 مشكلة. والأدهى أن مستودع GitHub لا يحتوي إلا على التوثيق والأمثلة، أما الشيفرة المصدرية للخادم نفسه فغير منشورة، ما يمنع المجتمع من إصلاح الأخطاء بنفسه. كما أن Windows Driver الخاص بـAppium يستخدم WinAppDriver داخلياً فيرث القيود ذاتها. إن كانت لديك أصول اختبار قائمة على WinAppDriver فلا حاجة للتخلّي عنها فوراً، لكن يُنصح بتوجيه اختبارات المسارات الجديدة نحو FlaUI.
كيف نمنع سهولة انكسار الاختبار الآلي لواجهة المستخدم؟
80% من مقاومة الانكسار يتقرّر بقاعدة يضعها فريق التطوير لتخصيص AutomationId. في WPF يصبح المعرّف هو x:Name أو AutomationProperties.AutomationId، وفي WinForms يصبح Control.Name. فوق ذلك، امنع البحث المعتمد على النص المعروض (Name) والنقر بالإحداثيات، واستخدم الانتظار الشرطي عبر صنف Retry في FlaUI بدل Thread.Sleep، واجمع البنية في مكان واحد عبر نمط Page Object الذي يحصر بحث العناصر والتعامل معها لكل شاشة. تخلّت FlaUI منذ الإصدار 2.0 عن إعادة المحاولة الضمنية في دوال البحث (Find)، لذا اجعل من القاعدة إحاطة أي بحث عن عنصر يظهر بشكل غير متزامن بـRetry دائماً.
إلى أي مدى ينبغي كتابة الاختبار الآلي لواجهة المستخدم؟
يُنصح بالبدء بنحو 10 اختبارات دخان (smoke tests). لأن اختبارات واجهة المستخدم أبطأ وأسهل انكساراً بفارق كبير مقارنة بالاختبارات الوحدوية، احمِ الحسابات وقواعد العمل بالاختبارات الوحدوية، والوصول إلى قاعدة البيانات بالاختبارات التكاملية، واقصر اختبارات واجهة المستخدم على التأكّد من «تشغيل التطبيق ونجاح المسارات الرئيسية». إذا أُتمتِم أعلى 10 مسارات كانت تُراجع يدوياً قبل كل إصدار، فستستغرق الكتابة أسبوعاً إلى أسبوعين، ويستقر التشغيل الليلي عند 20 إلى 30 دقيقة. وبالمقابل، أي خطة تستهدف «كل الشاشات وكل الحقول» شبه مضمونة الفشل. إن شعرت بأن عدد اختبارات واجهة المستخدم اللازمة كبير جداً، فتلك إشارة إلى ضرورة تحسين التصميم بنقل المنطق إلى ViewModel ليصبح قابلاً للحماية بالاختبارات الوحدوية.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

العودة إلى المدونة