> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# اختبار تطبيقات الجوال مع Appium وMaestro
- URL: https://arabroot.io/articles/اختبار-تطبيقات-الجوال-مع-appium-و-maestro/
- Published: 2026-10-13T01:00:00.000Z
- Updated: 2026-10-12T20:32:47.000Z
- Description: كيف تختار أداة اختبار تطبيقات الجوال، وأين تشغل الاختبارات، ولماذا تنكسر بعض الاختبارات عندما تصبح الواجهة عربية.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, DevOps والتشغيل, DevOps وCI/CD, الاستضافة الذاتية

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

وقد شرحنا في مقال [أنواع الاختبارات للمبتدئين](https://arabroot.io/articles/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-unit-%D9%88-integration-%D9%88-e2e/) أنواع الاختبارات وهرم الاختبارات Test Pyramid والاختبارات المتقلبة Flaky Tests، وكل ذلك ينطبق على الجوال كما هو، فاختبارات الوحدة Unit Tests لمنطق التطبيق تبقى الأكثر عدداً والأسرع، وهذا المقال يركز على الطبقة العليا التي تختلف في الجوال: اختبار واجهة التطبيق UI Test على جهاز حقيقي أو محاكي Emulator، وأدواته، وأين تشغله.

وسوف نناقش في هذا المقال ما يلي:

- كيف تتحكم أدوات اختبار الجوال في التطبيق، ولماذا يحدد ذلك سرعتها وما تستطيع فعله.
- أداة Appium وأداة Maestro وأداتا المنصة Espresso وXCUITest، ورخصة كل منها، ومتى تناسب كل واحدة.
- المحاكي Emulator مقابل الجهاز الحقيقي، ومزارع الأجهزة Device Farms وأسعارها.
- تجربة عملية كاملة: محاكي Android رسمي داخل Docker، ثم نفس السيناريو بأداة Appium 3 وأداة Maestro، مع إدخال نص عربي، ثم نكسر اختباراً مبنياً على الإحداثيات بتحويل الواجهة إلى اتجاه اليمين إلى اليسار RTL.

## ما الذي يجعل اختبار الجوال مختلفاً؟

في اختبار الويب أنت تتحكم في المتصفح، وهو برنامج واحد تعرف إصداره وتشغله في CI بأمر واحد، أما في الجوال فهناك ثلاثة أشياء إضافية يجب أن تفكر فيها قبل أن تكتب أول اختبار:

- **الجهاز نفسه:** التطبيق يحتاج نظام Android أو iOS يعمل، إما محاكي Emulator على جهازك أو في CI، أو جهاز حقيقي موصول بكابل USB أو عبر الشبكة، وتشغيل المحاكي يحتاج تسريع العتاد Hardware Acceleration، وهذا ما سوف تراه في التجربة.
- **تثبيت التطبيق:** الاختبار يعمل على ملف APK أو AAB في Android، وملف IPA أو حزمة App في iOS، أي على نسخة مبنية من التطبيق، وليس على رابط كما في الويب.
- **حالة النظام:** الأذونات Permissions، ونوافذ النظام، ولغة الجهاز واتجاه الكتابة، والوضع الليلي، ودوران الشاشة، والتطبيق الذي ينتقل إلى الخلفية ثم يعود، وكلها أشياء يمكن أن تفشل فيها الواجهة حتى لو كان منطق التطبيق سليماً.

ولذلك فالسؤال الأول ليس «أي أداة؟» وإنما «أين يعمل الاختبار بالنسبة إلى التطبيق؟»، لأن هذا يحدد كل شيء آخر.

## كيف تتحكم الأدوات في التطبيق؟

الصورة التالية تبين الطرق الثلاث التي تعمل بها أدوات اختبار واجهات الجوال:

![مخطط من ثلاثة صفوف: في Appium يرسل الاختبار أوامر WebDriver إلى سيرفر Appium الذي يمررها إلى مشغل UiAutomator2 أو XCUITest على الجهاز ثم إلى التطبيق، وفي Maestro يقرأ سطر الأوامر ملف flow.yaml ويتعامل مع شجرة الوصول عبر تطبيق مساعد صغير على الجهاز، وفي Espresso وXCUITest يبنى كود الاختبار مع التطبيق نفسه ويعمل بجانبه على الجهاز](https://arabroot.io/content/images/2026/10/mobile-testing-01-how-tools-drive.webp)

كلما اقتربت الأداة من التطبيق عرفت حالته أكثر، وكلما ابتعدت عنه صارت تعمل مع أي تطبيق دون الحاجة إلى الكود

في الإعداد أعلاه لاحظ التالي:

- **Appium:** الاختبار يرسل أوامر بروتوكول WebDriver، وهو نفس بروتوكول Selenium، إلى سيرفر Appium، والسيرفر يمررها إلى مشغل Driver خاص بالمنصة، ففي Android المشغل الشائع هو UiAutomator2 الذي يثبت تطبيقاً صغيراً على الجهاز يستخدم إطار UiAutomator من Google، وفي iOS المشغل هو XCUITest الذي يبني على إطار Apple نفسه. والنتيجة أن الاختبار يكتب بأي لغة لها مكتبة WebDriver، مثل JavaScript وJava وPython وC#، ولا يحتاج كود التطبيق.
- **Maestro:** الاختبار ملف YAML فيه قائمة أوامر مثل `tapOn` و`inputText` و`assertVisible`، وأداة سطر الأوامر تتعامل مع التطبيق عبر طبقة الوصول Accessibility Layer للنظام، أي نفس المعلومات التي يقرؤها قارئ الشاشة، وتنتظر تلقائياً حتى تستقر الواجهة. ولا تحتاج كود التطبيق أيضاً.
- **Espresso وXCUITest:** هذه أدوات المنصة نفسها، فالاختبار يكتب بلغة التطبيق، Kotlin أو Java في Android وSwift في iOS، ويبنى مع التطبيق بأداة Gradle أو Xcode، ويعمل بجانبه على الجهاز، وبالتالي يعرف متى انتهى التطبيق من عمله ويكون أسرع وأكثر ثباتاً، لكنه يحتاج كود التطبيق ويكتبه عادة المطور نفسه.

## الأدوات واحدة واحدة

### أداة Appium

[Appium](https://github.com/appium/appium?ref=arabroot.io) مشروع مفتوح المصدر برخصة Apache-2.0، وهو الأقدم والأوسع في هذا المجال، وفكرته أن تختبر تطبيقات الجوال بنفس الطريقة التي تختبر بها المتصفح عبر Selenium. وقد صدر Appium 2 في يوليو 2023 وفصل المشغلات Drivers عن السيرفر، فأصبحت تثبت السيرفر ثم تثبت المشغل الذي تحتاجه بأمر `appium driver install`، ثم صدر Appium 3 في أغسطس 2025، وأهم تغييراته بحسب [دليل الانتقال من 2 إلى 3](https://appium.io/docs/en/latest/guides/migrating-2-to-3/?ref=arabroot.io) أنه يحتاج Node.js 20.19 أو أحدث، وحذف نقاط نهاية Endpoints قديمة كانت مهجورة، وأصبحت الميزات غير الآمنة Insecure Features تحتاج اسم المشغل قبلها. وفي التجربة استخدمنا Appium 3.8.0 مع مشغل UiAutomator2 بالإصدار 8.7.0.

- **نقاط القوة:** يعمل مع Android وiOS وأنظمة أخرى عبر مشغلات مختلفة، وتكتب الاختبار بلغتك المفضلة، ويختبر أي تطبيق مبني دون الحاجة إلى كوده، ويدعمه أغلب مزودي مزارع الأجهزة، ومعه أداة [Appium Inspector](https://github.com/appium/appium-inspector?ref=arabroot.io) لاستكشاف عناصر الشاشة.
- **نقاط الضعف:** طبقات كثيرة بين الاختبار والتطبيق، فالجلسة تبدأ ببطء نسبياً والإعداد أطول، والانتظار مسؤوليتك أنت، فإذا لم تكتب انتظاراً صحيحاً حصلت على اختبارات متقلبة. واختبار iOS يحتاج جهاز macOS عليه Xcode بحسب [صفحة مشغل XCUITest](https://github.com/appium/appium-xcuitest-driver?ref=arabroot.io).

### أداة Maestro

[Maestro](https://github.com/mobile-dev-inc/maestro?ref=arabroot.io) أداة أحدث برخصة Apache-2.0، وتقول صفحة المشروع إنها بنيت على ما تعلمه أصحابها من Appium وEspresso وXCTest وغيرها، وإنها اتخذت ثلاثة قرارات من البداية: التحكم في التطبيق عبر طبقة الوصول في النظام، والتثبيت كملف واحد دون مشغلات، وأن يكون الاختبار قائمة أوامر YAML مسطحة. وتعمل الاختبارات بحسب الصفحة نفسها على المحاكيات وعلى أجهزة Android الحقيقية، أما أجهزة iOS الحقيقية فليست مدعومة بعد، ويحتاج سطر الأوامر Java 17 أو أحدث.

- **نقاط القوة:** اختبار مقروء يكتبه شخص من فريق الجودة QA دون خبرة برمجية كبيرة، وانتظار تلقائي حتى تستقر الواجهة، وتثبيت بسيط، ويعمل مع التطبيقات الأصلية Native وReact Native وFlutter بنفس الأوامر.
- **نقاط الضعف:** لغة YAML محدودة عندما يحتاج الاختبار منطقاً معقداً، فتلجأ إلى ملفات JavaScript مساعدة، ولا يدعم أجهزة iOS الحقيقية حتى الآن، وأداة Maestro Studio وخدمة Maestro Cloud ليستا مفتوحتي المصدر. ويرسل سطر الأوامر إحصاءات استخدام مجهولة إلا إذا عرفت المتغير `MAESTRO_CLI_NO_ANALYTICS`، وقد عرفناه في التجربة.

### أداتا المنصة Espresso وXCUITest

[Espresso](https://developer.android.com/training/testing/espresso?ref=arabroot.io) إطار اختبار الواجهة من Google لنظام Android، وهو جزء من مكتبات AndroidX Test المفتوحة برخصة Apache-2.0، وأهم ما فيه التزامن التلقائي Synchronization، فبحسب وثائقه ينتظر Espresso قبل كل فعل حتى تفرغ قائمة رسائل الواجهة ولا يوجد عمل خلفي جار، وبالتالي لا تحتاج إلى `sleep` في الاختبار. وتقول الوثائق نفسها إن قوته الكاملة تظهر مع من يعرف كود التطبيق، أي أنه أداة المطور. وفي iOS المقابل هو XCUITest، أي اختبارات الواجهة في إطار XCTest من Apple، وهو جزء من Xcode ومجاني لكنه ليس مفتوح المصدر، ويحتاج جهاز Mac.

- **نقاط القوة:** الأسرع والأكثر ثباتاً، ويعمل داخل أدوات المطور نفسها، ويصل إلى حالة التطبيق الداخلية لتجهيز الاختبار.
- **نقاط الضعف:** كل منصة لها اختباراتها بلغتها، فإذا كان لديك تطبيق Android وتطبيق iOS كتبت الاختبار مرتين، ويحتاج كود التطبيق، فلا يناسب فريق اختبار مستقل يستلم التطبيق مبنياً.

### الأدوات في جدول واحد

| المعيار                   | Appium                     | Maestro                  | Espresso / XCUITest                               |
| ------------------------- | -------------------------- | ------------------------ | ------------------------------------------------- |
| الرخصة                    | Apache-2.0                 | Apache-2.0 (سطر الأوامر) | Espresso برخصة Apache-2.0، وXCUITest جزء من Xcode |
| لغة الاختبار              | أي لغة لها مكتبة WebDriver | ملفات YAML               | Kotlin أو Java، وSwift                            |
| يحتاج كود التطبيق؟        | لا                         | لا                       | نعم                                               |
| الانتظار                  | تكتبه أنت                  | تلقائي                   | تلقائي عبر التزامن مع الواجهة                     |
| Android وiOS باختبار واحد | نعم، مع اختلاف المحددات    | نعم                      | لا، اختبار لكل منصة                               |
| أجهزة iOS الحقيقية        | نعم، من جهاز Mac           | غير مدعومة بعد           | نعم، من جهاز Mac                                  |
| من يكتبه عادة             | مهندس أتمتة الاختبار       | فريق الجودة QA أو المطور | المطور                                            |

## المحاكي أم الجهاز الحقيقي؟

المحاكي Emulator في Android برنامج يشغل نظام Android كاملاً على جهازك كآلة افتراضية، والمقابل في iOS هو Simulator الذي يأتي مع Xcode ويعمل على جهاز Mac فقط. وكلاهما مجاني وسريع البدء ويمكن تشغيله في CI بإعداد ثابت كل مرة، لكنه لا يشبه هاتف المستخدم في كل شيء، فالمعالج والذاكرة مختلفان، ولا توجد كاميرا حقيقية ولا مستشعرات حقيقية، ولا توجد التعديلات التي تضيفها الشركات المصنعة على Android، ولا ضعف الشبكة الحقيقي.

وقد يتساءل البعض: إذا كان الجهاز الحقيقي أدق، فلماذا لا نختبر عليه دائماً؟ والإجابة أن الجهاز الحقيقي بطيء في التجهيز ومكلف في الشراء والصيانة، ولا يمكن تشغيل عشرة منه بالتوازي في CI بسهولة، لذلك فالتقسيم المعقول هو أن تعمل الاختبارات الآلية على المحاكي في كل طلب دمج Pull Request، ثم على عدد قليل من الأجهزة الحقيقية قبل كل إصدار، كما تبين الصورة التالية:

![ثلاثة أعمدة: الأول محاكي في CI يعمل مع كل طلب دمج وهو سريع ومجاني لكنه لا يكشف مشاكل المعالج الرسومي والكاميرا والبطارية وتعديلات الشركات المصنعة، والثاني بضعة أجهزة حقيقية قبل كل إصدار لاختبار الأجهزة التي يملكها المستخدمون فعلاً وللفحص اليدوي، والثالث مزرعة أجهزة في السحابة تعمل ليلاً أو قبل الإصدار بمئات الأجهزة المستأجرة مقابل سعر للدقيقة أو للجهاز ويخرج فيها التطبيق وبياناته من شبكتك](https://arabroot.io/content/images/2026/10/mobile-testing-02-where-tests-run.webp)

نفس الاختبارات تعمل في الأماكن الثلاثة، والفرق في عدد المرات والتكلفة وما يكشفه كل مكان

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

### مزارع الأجهزة Device Farms

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

| الخدمة                                                                                              | طريقة التسعير                          | السعر المعلن                                                                                                                                                                |
| --------------------------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [AWS Device Farm](https://aws.amazon.com/device-farm/pricing/?ref=arabroot.io)                      | بالدقيقة لكل جهاز، أو جهاز مخصص بالشهر | 0.17 دولار للدقيقة، وأول 1000 دقيقة مجانية، وخانة جهاز Device Slot بلا حد للاستخدام تبدأ من 250 دولاراً شهرياً                                                              |
| [Firebase Test Lab](https://firebase.google.com/docs/test-lab/usage-quotas-pricing?ref=arabroot.io) | بالساعة بعد حصة يومية مجانية           | في خطة Blaze أول 60 دقيقة يومياً على المحاكيات و30 دقيقة على الأجهزة الحقيقية مجانية، ثم دولار واحد للساعة على المحاكي و5 دولارات للساعة على الجهاز الحقيقي، وتحسب بالدقيقة |
| [BrowserStack](https://www.browserstack.com/pricing?ref=arabroot.io) وخدمات مشابهة                  | اشتراك شهري لكل اختبار متواز Parallel  | الأسعار حسب الخطة وعدد الاختبارات المتوازية، فراجع صفحة التسعير                                                                                                             |

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

## كيف تختار؟

- **فريق تطوير صغير يملك كود تطبيق Android أو iOS واحد:** ابدأ باختبارات الوحدة ثم Espresso أو XCUITest لأهم ثلاثة أو أربعة مسارات، والسبب أنها الأسرع والأكثر ثباتاً ويكتبها المطور بجانب الكود.
- **تطبيق React Native أو Flutter، أو فريق جودة QA يكتب الاختبارات:** Maestro غالباً أسرع طريق إلى أول اختبار ثابت، لأن الانتظار تلقائي والملف مقروء، وتستطيع تشغيل نفس الملف على Android وiOS.
- **فريق أتمتة لديه اختبارات Selenium، أو تحتاج أجهزة iOS حقيقية في مزرعة، أو منطقاً معقداً في الاختبار:** Appium هو الخيار، لأنه معيار تدعمه كل المزارع، وتكتب فيه بلغة برمجة كاملة.
- **موقع ويب يفتح على الجوال وليس تطبيقاً:** لا تحتاج أياً من هذه الأدوات في البداية، فمحاكاة الأجهزة في Playwright التي ذكرناها في [أنواع الاختبارات](https://arabroot.io/articles/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-unit-%D9%88-integration-%D9%88-e2e/#playwright) تحاكي حجم الشاشة ووكيل المستخدم User Agent واللمس بحسب [وثائق المحاكاة Emulation](https://playwright.dev/docs/emulation?ref=arabroot.io)، لكنها تبقى متصفحاً يعمل على جهازك، وليست متصفح الهاتف الحقيقي.

## تجربة عملية: محاكي Android في Docker مع Appium وMaestro

سوف نشغل محاكي Android الرسمي من Google داخل Container، ثم نكتب نفس السيناريو مرتين، مرة بأداة Appium 3 ومرة بأداة Maestro، على تطبيق [ApiDemos](https://github.com/appium/android-apidemos?ref=arabroot.io)، وهو نسخة من تطبيق أمثلة Android يستخدمها مشروع Appium في اختباراته وترخيصه Apache-2.0\. والسيناريو: افتح Views ثم Controls ثم 1\. Light Theme، واكتب اسماً بالإنجليزية ثم نصاً عربياً في حقل النص، ثم علم على المربع Checkbox 1 وتأكد من النتيجة.

📌

التجربة تعمل على Windows 11 مع Docker Desktop وWSL2، ولم نغير أي إعداد في Windows أو WSL، والسبب أن الآلة الافتراضية التي يشغلها Docker Desktop كانت تعرض الجهاز `/dev/kvm` أصلاً، فتأكد من ذلك على جهازك بأمر `docker run --rm --privileged alpine ls -l /dev/kvm` قبل أن تبدأ، فإذا لم يظهر الجهاز فالمحاكي سوف يعمل دون تسريع، أي ببطء شديد لا يصلح للاختبار، كما تشرح صفحة [تسريع المحاكي](https://developer.android.com/studio/run/emulator-acceleration?ref=arabroot.io).

### Docker Image واحدة فيها المحاكي والأداتان

هناك مشاريع جاهزة تشغل المحاكي في Docker، وأشهرها [docker-android](https://github.com/budtmo/docker-android?ref=arabroot.io)، لكن رخصته Apache-2.0 مع تعديلات خاصة، والنسخة المجانية ترسل بحسب ملف الرخصة إحصاءات استخدام إلى Google Forms، منها المدينة والدولة المستنتجة من عنوان IP، إلا إذا عرفت المتغير `USER_BEHAVIOR_ANALYTICS=false`، وإصدارات Android الأحدث فيه للرعاة فقط. لذلك بنينا Docker Image بأنفسنا من أدوات Google الرسمية، فكل شيء فيها مثبت الإصدار ومعروف المصدر:

```dockerfile
# One image for the whole lab: the official Android emulator (headless, needs /dev/kvm),
# Appium 3 with the UiAutomator2 driver, and the Maestro CLI. Everything pinned.
FROM eclipse-temurin:21.0.12.1_1-jdk-noble

ARG CMDLINE_TOOLS=15859902
ARG NODE_VERSION=22.23.3
ARG APPIUM_VERSION=3.8.0
ARG UIA2_VERSION=8.7.0
ARG MAESTRO_VERSION=2.11.0
ARG SYSTEM_IMAGE="system-images;android-35;google_apis;x86_64"

ENV ANDROID_HOME=/opt/android \
    ANDROID_SDK_ROOT=/opt/android \
    APPIUM_HOME=/opt/appium \
    MAESTRO_CLI_NO_ANALYTICS=1 \
    MAESTRO_CLI_ANALYSIS_NOTIFICATION_DISABLED=true
ENV PATH=/opt/node/bin:/opt/maestro/bin:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH

RUN apt-get update && apt-get install -y --no-install-recommends \
      unzip curl xz-utils libpulse0 libgl1 libnss3 libxcomposite1 libxcursor1 libxdamage1 \
      libxi6 libxtst6 libasound2t64 libxkbfile1 libdbus-1-3 \
    && rm -rf /var/lib/apt/lists/*

# Android SDK command-line tools, then platform-tools, build-tools (Appium needs apksigner), the emulator and one system image
RUN mkdir -p $ANDROID_HOME/cmdline-tools && cd /tmp \
    && curl -fsSLo tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS}_latest.zip \
    && unzip -q tools.zip && mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest && rm tools.zip \
    && yes | sdkmanager --licenses > /dev/null \
    && sdkmanager --install "platform-tools" "emulator" "platforms;android-35" "build-tools;35.0.0" "$SYSTEM_IMAGE" > /dev/null \
    && echo no | avdmanager create avd -n lab -k "$SYSTEM_IMAGE" -d pixel_6

# Node.js, Appium 3 and the UiAutomator2 driver
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | tar -xJ -C /opt \
    && mv /opt/node-v${NODE_VERSION}-linux-x64 /opt/node \
    && npm install -g appium@${APPIUM_VERSION} \
    && appium driver install uiautomator2@${UIA2_VERSION}

# Maestro CLI
RUN curl -fsSLo /tmp/maestro.zip https://github.com/mobile-dev-inc/Maestro/releases/download/cli-${MAESTRO_VERSION}/maestro.zip \
    && unzip -q /tmp/maestro.zip -d /opt && rm /tmp/maestro.zip

WORKDIR /lab
```

في الإعداد أعلاه لاحظ التالي:

- صورة النظام `google_apis;x86_64` للإصدار Android 15 (API 35)، وهي تسمح بصلاحيات root في المحاكي، على عكس صور Google Play.
- حزمة `build-tools` ضرورية لأداة Appium، ففي أول تشغيل بدونها رفض Appium بدء الجلسة بالخطأ التالي، ثم ثبتناها وأضفناها إلى الملف.
- المتغير `MAESTRO_CLI_NO_ANALYTICS` يوقف إحصاءات Maestro، وقد تحققنا من اسمه في كود الأداة نفسها.
- عند تثبيت الحزم طبع `sdkmanager` تحذيراً بأنه أصبح مهجوراً Deprecated وأن البديل أداة سطر أوامر جديدة اسمها `android`، والأمر ما زال يعمل.

```text
WebDriverError: Cannot verify the signature of '/apk/ApiDemos-debug.apk'. Original error: Could not find 'apksigner.jar' in [...]. Do you have Android Build Tools installed at '/opt/android'?
```

والآن نبني ونشغل الـ Container، مع تمرير جهاز `/dev/kvm` وحده بدلاً من وضع `--privileged`، ومجلد فيه ملف التطبيق:

```bash
docker build -t lab-qa3-mob-android:1 .
docker run -d --name lab-qa3-mob-android --device /dev/kvm \
  -v "$PWD/apk:/apk:ro" -v "$PWD:/lab" lab-qa3-mob-android:1 sleep infinity
docker exec lab-qa3-mob-android sh -c 'appium -v; maestro --version; emulator -version | head -1'
```

والمخرج سوف يكون كما يلي:

```text
3.8.0
2.11.0
Android emulator version 37.2.12.0 (build_id 16428233) (CL:N/A)
```

واستغرق البناء عندنا قرابة 30 دقيقة، أغلبها تنزيل صورة النظام وفك طبقات Docker Image التي بلغ حجمها على القرص 9.7 GB، لذلك في CI احفظ هذه Docker Image في سجل خاص Registry ولا تقم ببنائها في كل تشغيل.

### تشغيل المحاكي دون شاشة

السكربت التالي يشغل المحاكي دون نافذة، وينتظر حتى ينتهي Android من الإقلاع، ثم يطفئ الحركات Animations ويثبت التطبيق:

```bash
#!/usr/bin/env bash
# Starts the emulator headless inside the container and waits until Android has finished booting.
set -e
start=$(date +%s)
emulator -avd lab -no-window -no-audio -no-boot-anim -no-snapshot -gpu swiftshader_indirect \
  -memory 3072 -cores 4 > /tmp/emulator.log 2>&1 &
adb wait-for-device
until [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = "1" ]; do sleep 2; done
echo "boot completed in $(( $(date +%s) - start ))s"
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
adb shell settings put global animator_duration_scale 0
adb install -r /apk/ApiDemos-debug.apk
adb shell getprop ro.build.version.release
```

والمخرج سوف يكون كما يلي:

```text
boot completed in 117s
Performing Streamed Install
Success
15
```

أي أن الإقلاع البارد استغرق قرابة دقيقتين، وأثناء الاختبارات استهلك الـ Container قرابة 6.4 GB من الذاكرة، وهذا رقم يجب أن تحسبه عند اختيار سيرفر CI، فلا تقم بتشغيل أكثر من محاكي أو اثنين على سيرفر بذاكرة 16 GB.

### السيناريو بأداة Appium

نشغل سيرفر Appium داخل الـ Container على `127.0.0.1:4723`، ونكتب الاختبار بمكتبة WebdriverIO 10.0.2:

```javascript
// Appium 3 + UiAutomator2: Views > Controls > 1. Light Theme, type English then Arabic, tick a checkbox.
//   node appium-controls.mjs        (Appium server on 127.0.0.1:4723 in the same container)
import { remote } from 'webdriverio';

const t0 = Date.now();
const driver = await remote({
  hostname: '127.0.0.1', port: 4723, logLevel: 'error',
  capabilities: {
    platformName: 'Android',
    'appium:automationName': 'UiAutomator2',
    'appium:app': '/apk/ApiDemos-debug.apk',
    'appium:appPackage': 'io.appium.android.apis',
    'appium:appActivity': '.ApiDemos',
    'appium:newCommandTimeout': 120,
  },
});
console.log(`session ready in ${((Date.now() - t0) / 1000).toFixed(1)}s`);
try {
  const t1 = Date.now();
  await driver.$('~Views').click();                       // accessibility id = content-desc
  await driver.$('~Controls').click();
  await driver.$('~1. Light Theme').click();
  const edit = driver.$('id=io.appium.android.apis:id/edit');
  await edit.setValue('Wajdy');
  console.log('english :', await edit.getText());
  await edit.setValue('مرحبا بك');
  console.log('arabic  :', await edit.getText());
  const box = driver.$('id=io.appium.android.apis:id/check1');
  await box.click();
  console.log('checked :', await box.getAttribute('checked'));
  console.log(`steps took ${((Date.now() - t1) / 1000).toFixed(1)}s`);
} finally {
  await driver.deleteSession();
}
```

والمخرج سوف يكون كما يلي:

```text
session ready in 9.7s
english : Wajdy
arabic  : مرحبا بك
checked : true
steps took 18.9s
```

في الإعداد أعلاه لاحظ التالي:

- المحدد `~Views` يبحث بمعرف الوصول Accessibility ID، وهو في Android الخاصية `content-desc`، والمحدد `id=…` يبحث بمعرف المورد Resource ID، وكلاهما لا يتغير مع اللغة أو حجم الشاشة.
- النص العربي وصل إلى الحقل كما هو.
- الوقت يتغير بين تشغيل وآخر، ففي تشغيل ثان بلغ بدء الجلسة 34.3 ثانية والخطوات 16.9 ثانية، والجهاز كان يشغل تجارب أخرى في الوقت نفسه، فاعتبر الأرقام توضيحية.

### السيناريو نفسه بأداة Maestro

الملف التالي يكتب نفس الخطوات بلغة Maestro:

```yaml
# Same scenario as appium-controls.mjs: open Views > Controls > 1. Light Theme,
# type a name in English and in Arabic, tick the first checkbox and check the result.
appId: io.appium.android.apis
---
- launchApp:
    clearState: true
- tapOn: "Views"
- tapOn: "Controls"
- tapOn: "1. Light Theme"
- tapOn:
    id: "io.appium.android.apis:id/edit"
- inputText: "Wajdy"
- assertVisible: "Wajdy"
- eraseText
- inputText: "مرحبا بك"
- assertVisible: "مرحبا بك"
- tapOn:
    id: "io.appium.android.apis:id/check1"
- assertVisible:
    id: "io.appium.android.apis:id/check1"
    checked: true
```

والمخرج سوف يكون كما يلي:

```text
Assert that "Wajdy" is visible... COMPLETED
Erase text... COMPLETED
Input text مرحبا بك... COMPLETED
Assert that "مرحبا بك" is visible... FAILED

Assertion is false: "مرحبا بك" is visible
```

لاحظ أن أمر الكتابة نفسه انتهى بحالة COMPLETED، لكن الحقل بقي فارغاً، فقد قرأنا شجرة الواجهة بعد الفشل فوجدنا الحقل يعرض النص الإرشادي `hint text` فقط. والسبب مكتوب في [وثائق الأمر inputText](https://docs.maestro.dev/reference/commands-available/inputtext?ref=arabroot.io)، فالأحرف خارج ASCII، أي Unicode، غير مدعومة في Android، مع إحالة إلى [التذكرة رقم 146](https://github.com/mobile-dev-inc/maestro/issues/146?ref=arabroot.io). وهذه نقطة مهمة لأي تطبيق عربي، فلو لم يكن في الملف أمر `assertVisible` بعد الكتابة لنجح الاختبار وهو لم يكتب شيئاً. والحل الذي نجح معنا هو تمرير النص عبر الحافظة Clipboard بأمري `setClipboard` و`pasteText`:

```yaml
# Arabic text through the clipboard instead of inputText (inputText does not type Unicode on Android).
appId: io.appium.android.apis
---
- launchApp:
    clearState: true
- tapOn: "Views"
- tapOn: "Controls"
- tapOn: "1. Light Theme"
- setClipboard: "مرحبا بك"
- tapOn:
    id: "io.appium.android.apis:id/edit"
- pasteText
- assertVisible: "مرحبا بك"
- tapOn:
    id: "io.appium.android.apis:id/check1"
- assertVisible:
    id: "io.appium.android.apis:id/check1"
    checked: true
```

والمخرج سوف يكون كما يلي:

```text
Set Maestro clipboard to مرحبا بك... COMPLETED
Tap on id: io.appium.android.apis:id/edit... COMPLETED
Paste text... COMPLETED
Assert that "مرحبا بك" is visible... COMPLETED
Tap on id: io.appium.android.apis:id/check1... COMPLETED
Assert that id: io.appium.android.apis:id/check1 is visible... COMPLETED

real	1m57.042s
```

وقد يتساءل البعض: لماذا استغرق Maestro قرابة دقيقتين بينما استغرق Appium نصف دقيقة؟ والإجابة في سجل Maestro، فقرابة دقيقة ذهبت بين تشغيل الأداة وأول أمر، أي في تشغيل Java وتجهيز الاتصال بالجهاز، ثم أخذ كل نقر بين 5 و15 ثانية، لأن Maestro ينتظر بعد كل فعل حتى تستقر الشاشة، والمحاكي هنا يرسم بالمعالج الرئيسي عبر `swiftshader_indirect` دون معالج رسومي GPU، والجهاز مشغول بتجارب أخرى. وهذا الانتظار التلقائي هو نفسه ما يجعل Maestro ثابتاً دون `sleep`، فأنت تدفع ثباته من وقته، وعلى جهاز أسرع سوف يقل الفرق.

### نكسر الاختبار: النقر بالإحداثيات مع واجهة عربية

بعض الفرق تسجل الاختبار بأداة تحفظ مكان النقر على الشاشة بدلاً من العنصر، والاختبار التالي يفعل الشيء نفسه عمداً، فينقر على النقطة `159,537` وهي منتصف المربع Checkbox 1 في الواجهة الإنجليزية، ثم يكرر النقر عبر معرف العنصر، ونشغله مرة بلغة الجهاز الإنجليزية ومرة بالعربية، حيث يغير Appium لغة الجهاز بالخاصيتين `appium:language` و`appium:locale`:

```javascript
// Break-it: the same check done two ways, by screen coordinates and by resource id.
//   node appium-coords.mjs en      → device in English (LTR)
//   node appium-coords.mjs ar      → device in Arabic (RTL), changed by Appium through the Appium Settings app
import { remote } from 'webdriverio';

const lang = process.argv[2] || 'en';
const driver = await remote({
  hostname: '127.0.0.1', port: 4723, logLevel: 'error',
  capabilities: {
    platformName: 'Android',
    'appium:automationName': 'UiAutomator2',
    'appium:app': '/apk/ApiDemos-debug.apk',
    'appium:appPackage': 'io.appium.android.apis',
    'appium:appActivity': '.view.Controls1',
    'appium:language': lang,
    'appium:locale': lang === 'ar' ? 'SA' : 'US',
  },
});
const ID = 'id=io.appium.android.apis:id/check1';
try {
  await driver.$(ID).waitForDisplayed({ timeout: 20000 });
  const r = { ...(await driver.$(ID).getLocation()), ...(await driver.$(ID).getSize()) };
  console.log(`[${lang}] Checkbox 1 is at x=${r.x}..${r.x + r.width}, y=${r.y}..${r.y + r.height}`);

  // 1) the recorded tap: x=159, y=537 is the centre of Checkbox 1 on the English screen
  await driver.action('pointer', { parameters: { pointerType: 'touch' } })
    .move({ x: 159, y: 537 }).down().up().perform();
  console.log(`[${lang}] tap by coordinates  -> checked=${await driver.$(ID).getAttribute('checked')}`);

  // 2) reset the screen, then the same tap through the element itself
  await driver.execute('mobile: startActivity', { intent: 'io.appium.android.apis/.view.Controls1', stop: true });
  await driver.$(ID).waitForDisplayed({ timeout: 20000 });
  await driver.$(ID).click();
  console.log(`[${lang}] tap by resource id  -> checked=${await driver.$(ID).getAttribute('checked')}`);
} finally {
  await driver.deleteSession();
}
```

والمخرج سوف يكون كما يلي:

```text
[en] Checkbox 1 is at x=0..318, y=446..629
[en] tap by coordinates  -> checked=true
[en] tap by resource id  -> checked=true

[ar] Checkbox 1 is at x=762..1080, y=446..629
[ar] tap by coordinates  -> checked=false
[ar] tap by resource id  -> checked=true
```

التطبيق يعلن في ملف Manifest أنه يدعم الاتجاه من اليمين إلى اليسار `supportsRtl=true`، فعندما أصبحت لغة الجهاز عربية انعكست الواجهة كاملة، وانتقل المربع من يسار الشاشة إلى يمينها، كما تبين الصورة التالية من المحاكي نفسه:

![لقطة شاشة من المحاكي بلغة عربية: شاشة Light Theme في تطبيق ApiDemos وقد انعكست، فالمربعات وأزرار الاختيار على يمين الشاشة، وزرا التبديل يعرضان كلمة إيقاف بالعربية، والساعة بالأرقام العربية](https://arabroot.io/content/images/2026/10/mobile-testing-03-rtl-screen.webp)

نفس الشاشة بعد تحويل لغة الجهاز إلى العربية: العناصر انتقلت إلى اليمين، فالنقرة المسجلة على اليسار تقع على مساحة فارغة

فالنقرة بالإحداثيات وقعت على مساحة فارغة دون أي رسالة خطأ، ولم يعرف الاختبار أنه نقر على لا شيء إلا عند التحقق. وقد كررنا الشيء نفسه في Maestro بالأمر `tapOn` مع `point`، فكانت النتيجة نفسها:

```yaml
# Break-it in Maestro: the recorded tap on Checkbox 1 by its pixel position on the English screen.
appId: io.appium.android.apis
---
- launchApp
- tapOn: "Views"
- tapOn: "Controls"
- tapOn: "1. Light Theme"
- tapOn:
    point: "159,537"
- assertVisible:
    id: "io.appium.android.apis:id/check1"
    checked: true
```

```text
Tap on point (159,537)... COMPLETED
Assert that id: io.appium.android.apis:id/check1 is visible... FAILED
```

والدرس هنا أن الإحداثيات تربط الاختبار بشاشة واحدة ولغة واحدة، والمعرف يربطه بالعنصر نفسه، فإذا كان جمهورك عربياً فشغل مساراً واحداً على الأقل بلغة الجهاز العربية، لأنه يكشف الاختبارات الهشة ويكشف معها مشاكل الواجهة نفسها، مثل النصوص المقطوعة والأيقونات التي لم تنعكس، وهذا ما نفصله في مقال [اختبار سهولة الوصول لموقع عربي](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-accessibility-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%B9%D8%B1%D8%A8%D9%8A/).

### ما الذي لم نشغله

لم نشغل Espresso لأنه يحتاج كود التطبيق ومشروع Gradle، ولم نشغل XCUITest ولا محاكي iOS لأنهما يحتاجان جهاز Mac، ولم نستخدم جهازاً حقيقياً ولا مزرعة أجهزة لأننا لم ننشئ أي حساب خارجي، فأرقام المزارع في هذا المقال من صفحات الأسعار فقط.

### إزالة التجربة

```bash
docker rm -f lab-qa3-mob-android
docker rmi lab-qa3-mob-android:1
```

## أخطاء شائعة

- **الضغط على إحداثيات الشاشة:** اختبار يضغط على نقطة ثابتة يعمل على شاشة واحدة واتجاه واحد فقط، وينكسر مع جهاز آخر أو مع العربية، كما رأيت في التجربة، فاستخدم المعرف Resource ID أو معرف الوصول Accessibility ID.
- **البحث عن العناصر بالنص المعروض:** النص يتغير مع الترجمة ومع تعديل صياغة الواجهة، فإذا كان تطبيقك بالعربية والإنجليزية فالنص محدد هش، والمعرف أثبت منه.
- **الانتظار بـ `sleep` ثابت:** أبطأ من اللازم على الجهاز السريع وأقل من اللازم على المحاكي البطيء في CI، فاستخدم الانتظار حتى يظهر العنصر، أو أداة تنتظر تلقائياً.
- **ترك الحركات Animations مفعلة في CI:** الحركات تبطئ الاختبار وتسبب نقرات قبل أن يستقر العنصر، لذلك أطفأناها في التجربة بثلاثة أوامر `adb`.
- **اختبار التطبيق بلغة واحدة فقط:** إذا كان جمهورك عربياً فشغل مسار الاختبار الأساسي مرة باللغة العربية واتجاه RTL، فهناك تظهر النصوص المقطوعة والأيقونات غير المعكوسة.
- **كتابة كل شيء كاختبار واجهة:** اختبار الواجهة على الجوال بطيء، فالمنطق مكانه اختبارات الوحدة، واختبار الواجهة لأهم المسارات فقط، مثل التسجيل والدفع.
- **رفع نسخة فيها مفاتيح الإنتاج إلى مزرعة أجهزة:** استخدم نسخة اختبار Build Variant تتصل ببيئة اختبار وبيانات وهمية.

## قائمة تحقق

- اختبارات الوحدة تغطي منطق التطبيق، واختبارات الواجهة لأهم المسارات فقط.
- كل عنصر يحتاجه الاختبار له معرف ثابت، Resource ID في Android أو Accessibility Identifier في iOS، وهذا يساعد قارئ الشاشة أيضاً.
- الاختبارات تعمل على محاكي في كل طلب دمج، بإصدار نظام ثابت ومعروف، والحركات مطفأة.
- مسار أساسي واحد على الأقل يعمل باللغة العربية واتجاه RTL.
- قائمة الأجهزة الحقيقية مبنية على بيانات المستخدمين، وفيها جهاز ضعيف واحد على الأقل.
- إصدارات الأدوات مثبتة في CI، Appium والمشغل وMaestro وصورة النظام، حتى لا يتغير شيء دون أن تعرف.
- نسخة الاختبار لا تحتوي مفاتيح الإنتاج ولا بيانات عملاء حقيقية.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- اختبار واجهة الجوال طبقة فوق اختبارات الوحدة وليس بديلاً عنها، فاكتبه لأهم المسارات فقط.
- Espresso وXCUITest أسرع وأثبت لكنهما يحتاجان كود التطبيق ويكتبان مرة لكل منصة، وAppium يعمل مع أي تطبيق مبني وبأي لغة وتدعمه المزارع، وMaestro أسهل كتابة وينتظر تلقائياً، لكنه لا يكتب Unicode في Android بأمر inputText، فاستخدم الحافظة أو تحقق بعد كل كتابة.
- محاكي Android الرسمي يعمل في Docker إذا كان `/dev/kvm` متاحاً، وأقلع عندنا في قرابة دقيقتين واستهلك قرابة 6.4 GB من الذاكرة، فاحسب ذلك في سيرفر CI.
- المحاكي لكل طلب دمج، وبضعة أجهزة حقيقية مختارة من بيانات المستخدمين قبل كل إصدار، ومزرعة أجهزة عندما تحتاج طرازات كثيرة، مع الانتباه إلى أن التطبيق وبياناته يخرجان من شبكتك.
- الاختبار المبني على الإحداثيات ينكسر بصمت عند تحويل الواجهة إلى العربية، والمعرف الثابت لكل عنصر يحل ذلك ويساعد قارئ الشاشة أيضاً.

[![](https://arabroot.io/content/images/2026/10/testing-types-cover-cover.webp)دليلأنواع الاختبارات للمبتدئين: من Unit إلى E2E مع Playwrightما الذي يكشفه كل نوع من الاختبارات وما الذي يفوته، وكم اختباراً تكتب من كل نوع، وأين يعمل كل منها في خط CI.](https://arabroot.io/articles/%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86-unit-%D9%88-integration-%D9%88-e2e/)[![](https://arabroot.io/content/images/2026/10/accessibility-testing-cover-cover.webp)دليلاختبار سهولة الوصول Accessibility لموقع عربيما الذي تجده أدوات فحص سهولة الوصول وما الذي يفوتها، وما الذي يخص المواقع العربية، وكيف تمنع عودة المشاكل في خط CI.](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-accessibility-%D9%84%D9%85%D9%88%D9%82%D8%B9-%D8%B9%D8%B1%D8%A8%D9%8A/)[![](https://arabroot.io/content/images/2026/10/contract-testing-cover-cover.webp)دليلاختبار العقود Contract Testing بين الخدمات مع Pactكيف تمنع خدمة من كسر خدمة أخرى تعتمد عليها قبل النشر، بعقد يكتبه المستهلك ويتحقق منه المزود ويحفظه Pact Broker.](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D8%B9%D9%82%D9%88%D8%AF-contract-testing-%D9%85%D8%B9-pact/)[![](https://arabroot.io/content/images/2026/10/mutation-testing-cover-cover.webp)دليلاختبار الطفرات Mutation Testing: هل تكشف اختباراتك الأخطاء؟لماذا تمر الأخطاء من اختبارات تغطيتها 100%، وكيف يقيس اختبار الطفرات قوتها فعلاً ويعمل في خط CI دون أن يبطئه.](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1-%D8%A7%D9%84%D8%B7%D9%81%D8%B1%D8%A7%D8%AA-mutation-testing/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ التجربة على Docker Engine 29.8.0 مع Docker Desktop وWSL2، بمحاكي Android 37.2.12 وصورة النظام Android 15 (API 35، google\_apis x86\_64 الإصدار 9) وplatform-tools 37.0.1 وbuild-tools 35.0.0، وAppium 3.8.0 مع مشغل UiAutomator2 8.7.0 وWebdriverIO 10.0.2 على Node.js 22.23.3، وMaestro CLI 2.11.0 على Java 21.0.12.1 (eclipse-temurin:21.0.12.1\_1-jdk-noble)، وتطبيق ApiDemos 6.0.18.