تخطَّ إلى المحتوى

ملاحظات الإصدار

تعرف على أحدث الميزات والتحسينات في كل إصدار من Astur.

اختبار موقع الويب داخل متصفح الجهاز نفسه. تتيح لك device.browser قيادة متصفح Chrome على Android ومتصفح Safari على iOS، وذلك على نفس المحاكي (Emulator) أو المُحاكي (Simulator) أو الجهاز الحقيقي الذي تعمل عليه اختباراتك الأصلية (Native) — وبذلك يمكن تغطية موقع متجاوب وتطبيق أصلي في تشغيلة واحدة وتقرير واحد.

const page = await device.browser.open('https://example.com/pricing');
await expect(page.getByRole('heading', { name: 'Pricing' })).toBeVisible();
await page.getByTestId('plan-pro').tap();

تشمل الواجهة: open، navigate، reload، back، forward، url، و capabilities — وهذه الأخيرة تُجيب على كل المنصات، ما يسمح للاختبار بأن يتخطى نفسه عبر test.skip() مع ذكر السبب، تماماً كما تفعل device.network حالياً. كل عملية تنقل تُعيد كائن WebContext الحيّ، وهو ذات الكائن الذي تُرجعه device.webContext()، لذا تعمل جميع المُحدِّدات (Locators) والإجراءات دون أي تعديل.

اضبط browser بدلاً من app لتتحول الجلسة إلى جلسة متصفح فقط (Browser-only): لا تثبيت للتطبيق، ويصبح الوكيل الأصلي (Native Agent) اختيارياً بدلاً من كونه إلزامياً. وعلى iOS تحديداً، هذا هو الفرق بين فتح صفحة ويب وبين الحاجة إلى هوية توقيع (Signing Identity) أولاً.

يجدر بالذكر أن Playwright يختبر ويب الجوال بكفاءة عبر محاكاة الأجهزة (Device Emulation)، وهو أسرع في بيئات الـ CI. أما هذه الميزة فهي مخصصة للحالة التي لا تكفي فيها المحاكاة: متصفح جوال حقيقي، على نفس مجموعة الأجهزة، وضمن نفس التقرير الذي يضم اختباراتك الأصلية.

ثلاث معضلات جرى حلّها لتحقيق ذلك، بما يتجاوز ناقل الـ DOM القائم:

  • تبويب (Tab) لكل اختبار، يُغلق عند انتهائه، على غرار ما يفعله Playwright حين يمنح كل اختبار صفحته الخاصة. يقوم Android بإنشاء التبويبات وإغلاقها عبر مقبس التنقيح (Debugging Socket). أما WebKit فلا يكشف دورة حياة التبويب، لذا يعيد iOS استخدام تبويب واحد ويحدّثه — ولهذا فإن open() تُحمّل الصفحة دائماً حتى لو كان التبويب يعرض العنوان ذاته، لأن التبويب المُعاد استخدامه يحمل بقايا الـ DOM من الاختبار السابق.
  • الاستقرار على المستند (Document) لا على العنوان (URL). عملية إعادة التحميل تُبقي العنوان كما هو، وبالتالي فإن أي انتظار مبني على العنوان سيعود فوراً ويسلّمك الصفحة القديمة. لذا يزرع Astur رمزاً (Token) على window وينتظر اختفاءه، وهي اللحظة الدقيقة التي يُستبدل فيها المستند فعلياً.
  • اكتشاف شاشة التشغيل الأول. لا يُنشئ Chrome أي مقبس تنقيح قبل إتمام شاشة الترحيب، ما يعني أن محاكياً جديداً سينتظر إلى ما لا نهاية صفحةً لن تظهر أبداً. يُبلَّغ عن هذه الحالة الآن باسم BROWSER_FIRST_RUN_PENDING بدلاً من انتهاء المهلة.

من المهم معرفتها قبل بناء مجموعة اختبارات على هذه الميزة: التبويب ليس سياق متصفح (Browser Context) بمفهوم Playwright — فملفات الارتباط (Cookies) و localStorage تعود لملف تعريف المتصفح وتُشارَك بين التبويبات. كما أن واجهة المتصفح نفسها هي عناصر أصلية (Native) وليست جزءاً من الصفحة. وأخيراً، مسار أجهزة iOS الحقيقية مكتوب برمجياً لكن لم يجرِ التحقق منه على جهاز فعلي بعد. تجد القائمة الكاملة في صفحة الويب على الجوال.

التوثيق متوفر الآن باللغة العربية، عبر /ar/ — جميع الصفحات، مع الإبقاء على أسماء الواجهات البرمجية والبروتوكولات بالإنجليزية كما هي.

  • كان بالإمكان قيادة WebView الخاص بالتطبيق عبر المتصفح بالخطأ. يسمّي Android المقابس بالصيغتين webview_devtools_remote_<pid> و chrome_devtools_remote، وهي تُرتَّب أبجدياً — لذا كانت device.webContext() ترتبط بالمتصفح بدلاً من التطبيق قيد الاختبار عند تشغيل Chrome. أصبحت المقابس الآن تُرتَّب وفقاً لما طلبه المُستدعي.

للاطلاع على التفاصيل الكاملة، راجع سجل التغييرات.

مقارنة لقطات الشاشة بذكاء. تُتيح لك الدالة toHaveScreenshot() فحص مكون معين أو الشاشة بأكملها ومطابقتها مع صورة مرجعية (Baseline) محفوظة مسبقاً. وبذلك، سيفشل الاختبار تلقائياً عند اكتشاف أي تغيير بصري غير متوقع بدلاً من تجاوزه بصمت. وعلى غرار مطابِق Playwright الذي يعتمد على كائن الـ Page، توفر هذه الدالة النسخة الأصلية (Native) والمكافئة له:

await expect(app.home.heroCard).toHaveScreenshot('hero-card.png');
// أو فحص الشاشة مع استثناء أجزاء متغيرة:
await expect(device).toHaveScreenshot('home.png', { mask: [device.getById('clock')] });

يتم حفظ الصور المرجعية بشكل مُنظم لكل منصة، محرك عرض (Engine)، ومقاس شاشة.

  • استخدم mask لتغطية الأجزاء المعرضة للتغيير الطبيعي (كالساعات أو العدادات).
  • تحكم في مستوى التسامح البصري عبر خيارات: threshold، maxDiffPixels، و maxDiffPixelRatio.
  • عند حدوث فشل، سيُظهر تقرير Playwright (بصيغة HTML) الفروق بشكل دقيق من خلال تبويبات: Diff / Actual / Expected / Slider.

مراقبة الشبكة لتطبيقات React Native (على Android و iOS). أصبحت الأداة device.network قادرة على قراءة نطاق الـ Network من خلال بروتوكول CDP (وهو ذات النطاق الذي تعتمد عليه React Native DevTools). ولأن المُبلغ البرمجي يقع في نواة ReactCommon المشتركة، فإن هذا التحديث يغطي كلا المنصتين في آنٍ واحد.

const capabilities = await device.network.capabilities();
test.skip(!capabilities.observe, capabilities.coverage);

يُشترط لنجاح هذه المراقبة أمران أساسيان:

  1. استخدام بناء Debug مرتبط بـ Metro (حيث يتم استبعاد المُبلغ في البناء النهائي Release).
  2. أن تكون الطلبات صادرة عبر XMLHttpRequest (والتي تشمل الـ fetch المُدعم داخل React Native وكذلك axios). (ملاحظة: لا يشمل الرصد استخدام fetch الأصلي في إطار Expo لأنه يتجاوز طبقة الشبكة المراقبة كلياً).

مراقبة الشبكة لمحاكيات Flutter و iOS. بنفس مستوى الكفاءة المتوفر لـ Flutter على Android، دون أي متطلبات إضافية لتثبيت أو تشغيل التطبيق.

  • سلوك الخيار --update-snapshots: في السابق، كان الخيار المُبسط -u يُطبق كأنه all دون مبرر. تم إصلاح ذلك ليعمل كـ changed، مما يجعله أكثر كفاءة ودقة عند قبول التغييرات المقصودة.
  • الفصل الدقيق بين صور Flutter و React Native على iOS: كان النظام يخلط بين صور إطاري العمل على نظام iOS. تم تحديث النظام ليُحدد محرك العرض بشكل دقيق بناءً على حزمة التطبيق. وقد تم إعادة تسجيل الصور المرجعية التي طالتها هذه المشكلة في الإصدار السابق (beta.4).

للاطلاع على السجل الكامل، راجع سجل التغييرات الشامل.

الكتابة المباشرة في الحقول صعبة التحديد. تواجه بعض الحقول تعقيدات تجعل استهدافها مستحيلاً (كحقول الـ OTP المكونة من عدة صناديق منفصلة والتي تُبقي الحقل الفعلي خارج نطاق إمكانية الوصول). الآن، يمكنك إرسال النصوص مباشرة إلى العنصر المفتوح الذي يمتلك التركيز (Focus) عبر لوحة المفاتيح:

await device.getByTestId('otp-input').tap();
await device.keyboard.type('123456');
  • يعمل هذا الكود بتناغم على iOS و Android معاً في نفس ملف الاختبار؛ ففي iOS يقوم XCUITest بالكتابة للمُستجيب الحالي (First Responder)، وفي Android يُرسل المُدخل لعنصر المُركز.
  • أصبحت الدالة pressKey() تكتب حرفاً مطبوعاً منفرداً على المنصتين دون الحاجة لمعاملات خاصة، مما أصلح مشكلة قديمة في Android حيث كان الرقم 4 يُترجم بالخطأ كزر التراجع BACK.
  • في حال استدعاء الدالة دون وجود لوحة مفاتيح ظاهرة على الشاشة في iOS، سيُطلق النظام خطأ KEYBOARD_NOT_VISIBLE بشكل صريح بدلاً من التجاهل الصامت.

إصلاح مشكلة “لوحة المفاتيح الوهمية” على Android. كانت حالة اللوحة في Android تعتمد على مؤشرات غير دقيقة تُوهم النظام بوجود لوحة مفاتيح تغطي الشاشة، مما يُجبر Astur على محاولة إخفائها بمحاكاة النقر على زر التراجع (Back) والخروج من الشاشة بالخطأ. الآن، تم تحديث النظام ليعتمد على مصدر موثوق لأبعاد اللوحة (WindowInsets)، متجاهلاً الإطارات الفارغة (عديمة الارتفاع). (يتطلب هذا الإصلاح استخدام الوكيل المُحدث المرفق مع @astur-mobile/android).

نصيحة: لا يزال يُفضل استخدام الدالة fill() لملء الحقول القابلة للاستهداف متى ما أمكن، كونها تمسح الحقل وتتأكد من القيمة المدخلة بكفاءة. للمزيد، راجع التعامل مع لوحة المفاتيح والتعبئة.

مراقبة استجابات التطبيق في الوقت الفعلي. أضفنا دالة device.network الجديدة التي تكشف تفاصيل حركة الـ HTTP أثناء إجراء الاختبار، لتتمكن من فحص الطلبات الفعلية عوضاً عن الاعتماد على التأثيرات البصرية فقط.

const capabilities = await device.network.capabilities();
test.skip(!capabilities.observe, capabilities.coverage);
await device.network.clear();
await app.login.signIn('qa@astur.dev', 'Astur12345');
const [request] = await device.network.requests({ url: '/api/session' });
expect(request).toMatchObject({ method: 'POST', status: 201 });
  • الميزة مُتاحة لتطبيقات Flutter/Android من خلال مُحلل الـ HTTP الخاص بـ Dart VM، والذي يشمل مكتبات كـ package:http و Dio.
  • استعن بالدالة capabilities() للحصول على تقرير دقيق حول إمكانية الرصد للمنصة الحالية وتخطي الاختبار بشكل منظم في حال عدم الدعم.
  • يتم تصفية الترويسات الحساسة (Credentials) والحد من أحجام الردود بشكل افتراضي كإجراءات حماية، مع تفريغ سجل التخزين بين الاختبارات.
  • ميزة الاعتراض (Intercept) كالتأخير أو تزييف الاستجابات غير متوفرة بعد في هذا الإصدار لاعتمادها على محوّلات مدمجة (In-App Adapters).

إصلاحات لأخطاء كانت تُعطل التشغيلات الكبيرة:

  • حُلت مشكلة تدخل وكيل UiAutomator الذي كان يستولي على التفاعلات في جلسات Flutter، مما كان يؤدي لإرسال التطبيق للخلفية وإيقاف الـ Dart VM بالخطأ.
  • فشل إجراء النقر على العنصر المُركب لم يُعد يُجهض الاختبار بالكامل، بل يتحول لنقرة إحداثية، مما أصلح مشاكل منتقي الوسائط في React Native على Android.
  • إيماءات التمرير (Scroll) أصبحت أكثر انضباطاً (بمعدل 1200 مللي ثانية) بعد أن كانت تتم كقفزات سريعة جداً.
  • تمت معالجة مشكلة التجميد (Hang) التي كانت تستمر لـ 7 دقائق متصلة بخدمة Flutter VM؛ أصبحت المحاولات الآن محدودة ومرتبطة بمهلة زمنية (Timeout) واضحة.
  • المُحدد الخام by.native({ ios, android }): مخرج طوارئ قوي جداً للوصول للعناصر التي تعجز المحددات الأساسية (كـ by.label و by.id) عن التقاطها. يمكنك استخدام استعلامات NSPredicate لـ iOS، ومحددات مبنية (كـ className و resourceId) لـ Android. (راجع دلائل Android و iOS).
  • تم معالجة انهيار الأمر astur test بالخطأ spawn EINVAL على Node 22/24؛ أصبح التشغيل الآن يمر عبر Playwright مباشرة لضمان استقرار الأداء.
  • مُفتش Android (Inspector) لم يعد يتأرجح برسالة “UI tree unavailable”؛ حيث تم تطوير دورة حياة الوكيل لتقوم بتنظيف أي بقايا من الجلسات السابقة (كالمنافذ المعلقة) قبل بدء الجلسة الجديدة.
  • تسجيل التوكيدات (Assertions) من الـ Inspector مباشرةً. يوفر الـ Inspector الآن أدوات للتحقق من حالة العنصر (مفعل، معطل، محدد، ومركز)، بالإضافة لوظيفة (عدد المطابقات يساوي) التي تولّد أمر toHaveCount(n).
  • إصلاح خطأ Error: spawn start ENOENT الذي كان يُسقط الـ Inspector في أجهزة Windows (تجاوزنا مشكلة الفتح التلقائي للمتصفح).
  • أمان توليد الكود: يرفض الـ Inspector تسجيل أي خطوات (كالتعبئة أو التأكيد) ما لم تكن مرتبطة بمُحدد صريح (Selector)، مانعاً بذلك إصدار أكواد معطوبة بصمت.
  • قراءة حالة العنصر المباشرة من المُحددات. إضافة خصائص كـ textContent()، inputValue()، bounds()، count()، isEnabled()، isDisabled()، isSelected()، isFocused()، و clear() للتوافق المثالي مع أسلوب Playwright.
  • التوكيد toHaveCount: استعلام يستمر بمحاولات إعادة الفحص (Polling) بشكل متسق: await expect(device.getByRole('menuitem')).toHaveCount(3).
  • الاستعلامات المتعددة أسرع على Android: الدوال كـ queryAll() و device.findMany() أصبحت تتنفذ داخلياً بشكل مباشر عبر وكيل UIAutomator بدلاً من الاعتماد المُكلف على سحب وتحليل الشجرة بالكامل.
  • تعبئة نصية أسرع لحقول iOS: القيم القصيرة تُكتب بشكل مباشر وآمن، بينما القيم الطويلة تُرسل كلصق مباشر لضمان السرعة.
  • تحكم أفضل بلوحة مفاتيح iOS: تقليل تكرار إخفاء/إظهار اللوحة عند الانتقال بين الحقول لتسريع التفاعل.
  • التعبئة في الـ WebViews معزولة: يضمن الـ Inspector بقاء التفاعلات داخل إطار الـ DOM عند التعامل مع المتصفحات المدمجة (WebViews)، متجنباً الإرسال الخاطئ لوكيل الـ iOS الأصلي.
  • دعم السحب والإفلات (Drag and Drop): الـ Inspector يدعم الآن تسجيل إجراءات السحب وتصديرها بوضوح.
  • تنظيف جلسات ios-webkit-debug-proxy: يتم إنهاء الاتصالات المعلقة السابقة بشكل نظيف لضمان استقرار الاتصالات اللاحقة.
  • دعم الـ DOM داخل الـ WebView على محاكي iOS: الدالة device.webContext() قادرة الآن على التعامل مع شجرة الـ WKWebView في محاكيات iOS تماماً كالأجهزة الفعلية، معتمدة على أداة ios-webkit-debug-proxy.
  • دعم أتمتة Flutter على iOS: الـ Inspector ودالة الـ codegen قادران على قراءة واجهات تطبيقات Flutter من خلال شجرة الـ XCUITest.
  • إعادة تنظيم الأمثلة والملفات الأولية: نُقلت الاختبارات لـ specs/، ووضعت إعدادات المنصات في مجلدي config/android/ و config/ios/ للحفاظ على هيكلة نقية ومرتبة.

تنويه للإصدارات التجريبية (Beta): قد تخضع واجهات الـ API لبعض التعديلات الدقيقة بين إصدار تجريبي وآخر بناءً على التغذية الراجعة والتحسينات المستمرة.