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

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

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

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

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

  • الاختبار الآليّ لواجهة المستخدم هو أعلى طبقة في هرم الاختبار. فهو بطيء وسهل الانكسار ويستغرق تحديد سبب الفشل وقتاً طويلاً، لذا لا يحلّ محلّ الاختبارات الوحدويّة والتكامليّة. احمِ المنطق في الطبقات السفلى، واقصر اختبار واجهة المستخدم على اختبارات دخان تتحقّق من «تشغيل التطبيق ونجاح المسارات الرئيسيّة» (الفصل 6).
  • أساس الآليّة هو Windows UI Automation (UIA). يُبحَث عن العناصر انطلاقاً من شجرة الأتمتة (automation tree) التي جذرها سطح المكتب، وتُحدَّد عبر خصائص مثل AutomationId / Name، وتُشغَّل عبر أنماط التحكّم (control patterns) (مثل Invoke وValue وSelectionItem).12
  • تحقّق من كيفيّة ظهور عناصر التطبيق المستهدَف قبل كتابة أيّ شيفرة، عبر inspect.exe أو Accessibility Insights for Windows. أداة inspect أداة قديمة (legacy) مرفَقة مع 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 يتطلّب جلسة سطح مكتب تفاعليّة (interactive) بالضرورة. لا يعمل مع عميل (agent) مُهيَّأ كخدمة، ويتعطّل أيضاً مع قفل الشاشة أو انقطاع اتّصال RDP. الشكل الأساسيّ هو تهيئة مُشغِّل (runner) ذاتيّ الاستضافة مع تسجيل دخول تلقائيّ (autologon) (الفصل 7).8

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

2.1 شجرة الأتمتة (automation tree)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

يمكن استدعاء UIA مباشرةً عبر COM، لكنّ الممارسة العمليّة تستخدم مكتبات تغليف (wrapper). نرتّب هنا الخيارات ووضعها الحاليّ اعتباراً من عام 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 موضَّح في الأسئلة الشائعة (FAQ) لـFlaUI. UIA2 تنفيذ مُدار (managed) فقط، لا يدعم الميزات الجديدة كاللمس، وتوافقه مع 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 دون أن تتحوّل إلى نسخة رسميّة، وتجاوز عدد المشكلات (issues) غير المحلولة 1,100 مشكلة.5 والأكثر إزعاجاً أنّ مستودع GitHub لا يحتوي إلا على التوثيق والأمثلة ومتتبِّع المشكلات، أمّا الشيفرة المصدريّة للخادم نفسه فغير منشورة. أي أنّ المجتمع لا يستطيع إصلاح الأخطاء بنفسه. تقديرنا هو أنّه ليس هدفاً للاعتماد الجديد في 2026 لمجرّد أنّه «رسميّ فيُعَدّ آمناً». إن كانت لديك أصول اختبار قائمة على WinAppDriver بالفعل فلا حاجة للتخلّي عنها فوراً، لكن أوقف التوسّع فيها ووجِّه اختبارات المسارات الجديدة نحو FlaUI.

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

Appium Windows Driver (appium-windows-driver) مشغِّل للتحكّم بتطبيقات Windows من Appium، لكنّ حقيقته هي «واجهة (interface) إلى 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 ── التشغيل والبحث والتحكّم والتحقّق

نكتفي بهذا القدر من الشرح النظريّ، ولننظر إلى شيفرة تعمل فعليّاً. إنّه مشروع اختبار عاديّ، مثبَّت فيه من NuGet كلّ من FlaUI.UIA3 (أو FlaUI.UIA2 إن كان غير مستقرّ مع WinForms) وxUnit.

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 هو مصنع الشروط (condition factory)، ويمكن أيضاً تركيب شروط ByName / ByControlType / And. كما ذُكر، مفتاح البحث المرشَّح الأوّل هو AutomationId.
  • التحكّم: يُحوَّل العنصر الذي عُثر عليه إلى غلاف مُصنَّف بنوع مثل AsButton() / AsTextBox() / AsComboBox() ثمّ يُتحكَّم به. Invoke() هو نمط Invoke، وخاصيّة Text هي نمط Value، وأنماط التحكّم من القسم 2.2 كامنة خلفهما مباشرةً.
  • التحقّق: تُختبَر النتيجة الظاهرة على واجهة المستخدم (تسمية، عدد أسطر قائمة، عنوان نافذة…) بـassert. وإن أردتَ التحقّق من محتوى قاعدة البيانات، يمكنك قراءتها مباشرةً من شيفرة الاختبار.

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

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

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

// الانتظار حتى 5 ثوانٍ حتى لا تعود القيمة null (أي حتى يظهر العنصر)
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 خاصيّة مخصَّصة لهذا الغرض بالذات، وفق مواصفات تشترط عدم اعتمادها على اللغة (locale) وكونها فريدة بين العناصر الشقيقة.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. وبحسب خبرتنا، معظم النماذج (forms) التي خُصِّص فيها Name وفق القاعدة يمكن البحث فيها مباشرةً بـAutomationId، لكن بسبب اختلاف طريقة الظهور باختلاف جيل الإطار وعنصر التحكّم، تحقّق دائماً من AutomationId الفعليّ بأدوات inspect قبل استخدامه كمفتاح بحث في الاختبار. كذلك، خاصيّة Name في UIA التي تصبح الاسم المنطوق لقارئات الشاشة، تُستعار في معظم عناصر التحكّم من خاصيّة Text، لكن في أنواع لا تُستعار فيها مثل TextBox وListView يلزم تعيين AccessibleName صراحةً.11 ونقطة أنّ تجهيز AutomationId ليس لأجل الاختبار فحسب، بل هو نفس عمل دعم إمكانيّة الوصول (accessibility) تماماً، مادّة جيّدة عند إقناع الإدارة بتخصيص ميزانيّة له.

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

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

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

أيّ عمليّة على شكل «النقر على إحداثيّات الشاشة (830, 412)» تنكسر عند تغيّر أيّ من موضع النافذة، أو الدقّة (resolution)، أو مقياس DPI، أو السمة (theme)، أو إعدادات الخطّ. ويُنتج DPI بالذات، عند اختلاف البيئات كأن يكون جهاز التطوير 100% وجهاز CI ‏150%، حالات كثيرة من نوع «يعمل محلّيّاً لكن يفشل في CI» (آليّة DPI مشروحة في «الدعم عالي DPI في WinForms»). وكما رأينا في الفصل 2، التحكّم عبر أنماط التحكّم في UIA لا يعتمد على الإحداثيّات. تملك FlaUI أيضاً واجهة برمجيّة (API) للتحكّم المباشر بالماوس، لكن حدِّد استخدامها فقط لعمليّات لا يمكن التعبير عنها بنمط، مثل السحب والإفلات أو لوحة الرسم. وحتّى في هذه الحالة، احسب الموضع النسبيّ من 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 أعلاه، عند الرجوع إليه من شرط الانتظار. فإن خُزِّنت نتيجة البحث في حقل (field)، سيستمرّ الانتظار حتّى انتهاء المهلة وهو ممسك بعنصر قديم اختفى بعد إعادة الرسم.

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

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

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

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

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

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

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

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

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

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

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

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 ميزة لالتقاط الشاشة أو عنصر، فاستدعِها من خطّاف (hook) الفشل في إطار الاختبار، واحفظها كمُنتَج (artifact) في CI.

// يُستدعى من خطّاف الفشل ونحوه. يُخرج إلى مسار حفظ مُنتَجات CI
FlaUI.Core.Capturing.Capture.Screen()
    .ToFile(Path.Combine(artifactDir, $"{testName}_{DateTime.Now:HHmmss}.png"));

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

8. الخلاصة

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

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

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

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

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

تتعامل شركة Komura Soft LLC مع إدخال الاختبار الآليّ لواجهة المستخدم في تطبيقات 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 تتيح التحقّق من خصائص UIA وأنماطه، وتصنيفها كأداة قديمة (legacy) مع التوصية بـAccessibility Insights.  2 3

  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

  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 معرِّفاً لا يعتمد على اللغة (locale)، ووجوب كونه فريداً بين العناصر الشقيقة، وسيناريو استخدامه كمفتاح بحث في سكربتات الاختبار، وعدم دعم 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

  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. ولأنّها لا تملك إطار اختبار خاصّاً بها، يمكن وضعها على نفس مُشغِّل الاختبارات (test runner) وخطّ أنابيب CI المستخدَم للاختبارات الوحدويّة. أوّل عمل قبل كتابة أيّ شيفرة هو التحقّق من كيفيّة ظهور عناصر التطبيق المستهدَف عبر inspect.exe أو Accessibility Insights for Windows.
هل يجوز اعتماد WinAppDriver من الآن؟
لا ننصح باعتماده في مشاريع جديدة. فـWinAppDriver من إنتاج Microsoft توقّف عند نسخته المستقرّة الأخيرة v1.2.1 الصادرة في نوفمبر 2020، وبقيت v1.3 عند مرحلة Release Candidate من يوليو 2020 دون أن تصل إلى إصدار رسميّ، وتجاوز عدد المشكلات (issues) غير المحلولة 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 ليصبح قابلاً للحماية بالاختبارات الوحدويّة.

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

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

غو كومورا

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

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

روابط عامة

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