المحددات
يوفر بروتوكول WebDriver عدة استراتيجيات محددات للاستعلام عن عنصر. يبسط WebdriverIO هذه الاستراتيجيات للحفاظ على بساطة اختيار العناصر. يرجى ملاحظة أنه على الرغم من أن أمر الاستعلام عن العناصر يسمى $
و $$
، إلا أنه ليس له علاقة بـ jQuery أو محرك محدد Sizzle.
على الرغم من وجود العديد من المحددات المختلفة المتاحة، فإن عددًا قليلاً منها فقط يوفر طريقة مرنة للعثور على العنصر المناسب. على سبيل المثال، بالنظر إلى الزر التالي:
<button
id="main"
class="btn btn-large"
name="submission"
role="button"
data-testid="submit"
>
Submit
</button>
نحن نوصي و__لا نوصي__ بالمحددات التالية:
المحدد | توصية | ملاحظات |
---|---|---|
$('button') | 🚨 أبدًا | الأسوأ - عام جدًا، بدون سياق. |
$('.btn.btn-large') | 🚨 أبدًا | سيء. مرتبط بالتنسيق. عرضة للتغيير بشكل كبير. |
$('#main') | ⚠️ نادرًا | أفضل. لكنه لا يزال مرتبطًا بالتنسيق أو مستمعي أحداث JS. |
$(() => document.queryElement('button')) | ⚠️ نادرًا | استعلام فعال، معقد للكتابة. |
$('button[name="submission"]') | ⚠️ نادرًا | مرتبط بسمة name التي لها دلالات HTML. |
$('button[data-testid="submit"]') | ✅ جيد | يتطلب سمة إضافية، غير متصل بإمكانية الوصول. |
$('aria/Submit') أو $('button=Submit') | ✅ دائمًا | الأفضل. يشبه كيفية تفاعل المستخدم مع الصفحة. يوصى باستخدام ملفات الترجمة الخاصة بواجهتك الأمامية حتى لا تفشل اختباراتك أبدًا عند تحديث الترجمات |
محدد استعلام CSS
إذا لم يتم الإشارة إلى خلاف ذلك، سيستعلم WebdriverIO عن العناصر باستخدام نمط محدد CSS، على سبيل المثال:
loading...
نص الرابط
للحصول على عنصر رابط بنص محدد فيه، استعلم عن النص الذي يبدأ بعلامة يساوي (=
).
على سبيل المثال:
loading...
يمكنك الاستعلام عن هذا العنصر عن طريق الاتصال:
loading...
نص رابط جزئي
للعثور على عنصر رابط يتطابق نصه المرئي جزئيًا مع قيمة البحث الخاصة بك،
استعلم عنه باستخدام *=
في مقدمة سلسلة الاستعلام (مثل *=driver
).
يمكنك الاستعلام عن العنصر من المثال أعلاه أيضًا بالاتصال:
loading...
ملاحظة: لا يمكنك مزج استراتيجيات محدد متعددة في محدد واحد. استخدم استعلامات عناصر متسلسلة متعددة للوصول إلى نفس الهدف، على سبيل المثال:
const elem = await $('header h1*=Welcome') // لا يعمل!!!
// استخدم بدلاً من ذلك
const elem = await $('header').$('*=driver')
عنصر بنص معين
يمكن تطبيق نفس التقنية على العناصر أيضًا. بالإضافة إلى ذلك، من الممكن أيضًا إجراء مطابقة غير حساسة لحالة الأحرف باستخدام .=
أو .*=
داخل الاستعلام.
على سبيل المثال، إليك استعلام لعنوان من المستوى 1 مع النص "Welcome to my Page":
loading...
يمكنك الاستعلام عن هذا العنصر عن طريق الاتصال:
loading...
أو استخدام استعلام نص جزئي:
loading...
نفس الشيء يعمل لأسماء id
و class
:
loading...
يمكنك الاستعلام عن هذا العنصر عن طريق الاتصال:
loading...
ملاحظة: لا يمكنك مزج استراتيجيات محدد متعددة في محدد واحد. استخدم استعلامات عناصر متسلسلة متعددة للوصول إلى نفس الهدف، على سبيل المثال:
const elem = await $('header h1*=Welcome') // لا يعمل!!!
// استخدم بدلاً من ذلك
const elem = await $('header').$('h1*=Welcome')
اسم العلامة
للاستعلام عن عنصر بعلامة محددة، استخدم <tag>
أو <tag />
.
loading...
يمكنك الاستعلام عن هذا العنصر عن طريق الاتصال:
loading...
سمة الاسم
للاستعلام عن العناصر ذات سمة اسم محددة، يمكنك إما استخدام محدد CSS3 عادي أو استراتيجية الاسم المقدمة من JSONWireProtocol عن طريق تمرير شيء مثل [name="some-name"] كمعامل محدد:
loading...
loading...
ملاحظة: استراتيجية المحدد هذه مهملة وتعمل فقط في المتصفحات القديمة التي تعمل بواسطة بروتوكول JSONWireProtocol أو باستخدام Appium.
xPath
من الممكن أيضًا الاستعلام عن العناصر عبر xPath محدد.
محدد xPath لديه تنسيق مثل //body/div[6]/div[1]/span[1]
.
loading...
يمكنك الاستعلام عن الفقرة الثانية عن طريق الاتصال:
loading...
يمكنك استخدام xPath أيضًا للتنقل لأعلى ولأسفل في شجرة DOM:
loading...
محدد اسم إمكانية الوصول
الاستعلام عن العناصر بواسطة اسمها المتاح. الاسم المتاح هو ما يتم إعلانه بواسطة قارئ الشاشة عندما يتلقى هذا العنصر التركيز. يمكن أن تكون قيمة الاسم المتاح كلاً من المحتوى المرئي أو بدائل النص المخفية.
الجلب بواسطة aria-label
loading...
loading...
الجلب بواسطة aria-labelledby
loading...
loading...
الجلب بواسطة المحتوى
loading...
loading...
الجلب بواسطة العنوان
loading...
loading...
الجلب بواسطة خاصية alt
loading...
loading...
ARIA - سمة الدور
للاستعلام عن العناصر استنادًا إلى أدوار ARIA، يمكنك تحديد دور العنصر مباشرة مثل [role=button]
كمعامل محدد:
loading...
loading...
سمة ID
استراتيجية المحدد "id" غير مدعومة في بروتوكول WebDriver، يجب على المرء استخدام استراتيجيات محدد CSS أو xPath بدلاً من ذلك للعثور على العناصر باستخدام المعرف.
ومع ذلك، قد تظل بعض برامج التشغيل (مثل محرك Appium You.i) تدعم هذا المحدد.
صيغ المحدد المدعومة حاليًا لـ ID هي:
//css locator
const button = await $('#someid')
//xpath locator
const button = await $('//*[@id="someid"]')
//id strategy
// Note: works only in Appium or similar frameworks which supports locator strategy "ID"
const button = await $('id=resource-id/iosname')
دالة JS
يمكنك أيضًا استخدام دوال JavaScript لجلب العناصر باستخدام واجهات برمجة التطبيقات الأصلية للويب. بالطبع، يمكنك فعل ذلك فقط داخل سياق الويب (على سبيل المثال، browser
، أو سياق الويب في الجوال).
بالنظر إلى بنية HTML التالية:
loading...
يمكنك الاستعلام عن العنصر الشقيق لـ #elem
على النحو التالي:
loading...
المحددات العميقة
ابتداءً من v9
من WebdriverIO، لا حاجة لهذا المحدد الخاص حيث يخترق WebdriverIO تلقائيًا DOM الظل. يوصى بالهجرة بعيدًا عن هذا المحدد عن طريق إزالة >>>
من أمامه.
تعتمد العديد من تطبيقات الواجهة الأمامية بشكل كبير على العناصر ذات DOM الظل. من الناحية التقنية، من المستحيل الاستعلام عن العناصر داخل DOM الظل بدون حلول بديلة. كانت shadow$
وshadow$$
من هذه الحلول البديلة التي كانت لها قيود. مع المحدد العميق، يمكنك الآن الاستعلام عن جميع العناصر داخل أي DOM ظل باستخدام أمر الاستعلام الشائع.
بفرض أن لدينا تطبيق ذو البنية التالية:
باستخدام هذا المحدد، يمكنك الاستعلام عن عنصر <button />
المتداخل في DOM ظل آخر، على سبيل المثال:
loading...
محددات الجوال
بالنسبة لاختبار الجوال الهجين، من المهم أن يكون خادم الأتمتة في السياق الصحيح قبل تنفيذ الأوامر. لأتمتة الإيماءات، يجب تعيين برنامج التشغيل بشكل مثالي إلى السياق الأصلي. ولكن لاختيار العناصر من DOM، سيحتاج برنامج التشغيل إلى تعيين إلى سياق webview للمنصة. فقط بعد ذلك يمكن استخدام الطرق المذكورة أعلاه.
بالنسبة لاختبار الجوال الأصلي، لا يوجد تبديل بين السياقات، حيث يجب عليك استخدام استراتيجيات الجوال واستخدام تقنية أتمتة الجهاز الأساسية مباشرة. هذا مفيد بشكل خاص عندما يحتاج الاختبار إلى بعض التحكم الدقيق في العثور على العناصر.
Android UiAutomator
يوفر إطار عمل UI Automator في Android عددًا من الطرق للعثور على العناصر. يمكنك استخدام واجهة برمجة تطبيقات UI Automator، وخاصة فئة UiSelector لتحديد مواقع العناصر. في Appium، ترسل كود Java، كسلسلة، إلى الخادم، الذي ينفذه في بيئة التطبيق، مما يؤدي إلى إرجاع العنصر أو العناصر.
const selector = 'new UiSelector().text("Cancel").className("android.widget.Button")'
const button = await $(`android=${selector}`)
await button.click()
Android DataMatcher و ViewMatcher (Espresso فقط)
توفر استراتيجية DataMatcher في Android طريقة للعثور على العناصر بواسطة Data Matcher
const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"]
})
await menuItem.click()
وبالمثل View Matcher
const menuItem = await $({
"name": "hasEntry",
"args": ["title", "ViewTitle"],
"class": "androidx.test.espresso.matcher.ViewMatchers"
})
await menuItem.click()
Android View Tag (Espresso فقط)
توفر استراتيجية علامة العرض طريقة مناسبة للعثور على العناصر من خلال علامة الخاصة بها.
const elem = await $('-android viewtag:tag_identifier')
await elem.click()
iOS UIAutomation
عند أتمتة تطبيق iOS، يمكن استخدام إطار عمل أتمتة واجهة المستخدم من Apple للعثور على العناصر.
تحتوي واجهة برمجة التطبيقات JavaScript هذه على أساليب للوصول إلى العرض وكل شيء عليه.
const selector = 'UIATarget.localTarget().frontMostApp().mainWindow().buttons()[0]'
const button = await $(`ios=${selector}`)
await button.click()
يمكنك أيضًا استخدام البحث بالمسند ضمن أتمتة واجهة مستخدم iOS في Appium لتحسين اختيار العنصر بشكل أكبر. راجع هنا للحصول على التفاصيل.
سلاسل المسند و سلاسل الفئة لـ iOS XCUITest
مع iOS 10 وما فوق (باستخدام برنامج تشغيل XCUITest
)، يمكنك استخدام سلاسل المسند:
const selector = `type == 'XCUIElementTypeSwitch' && name CONTAINS 'Allow'`
const switch = await $(`-ios predicate string:${selector}`)
await switch.click()
و سلاسل الفئة:
const selector = '**/XCUIElementTypeCell[`name BEGINSWITH "D"`]/**/XCUIElementTypeButton'
const button = await $(`-ios class chain:${selector}`)
await button.click()
معرف إمكانية الوصول
تم تصميم استراتيجية محدد accessibility id
لقراءة معرف فريد لعنصر واجهة المستخدم. هذا له ميزة عدم التغيير أثناء الترجمة أو أي عملية أخرى قد تغير النص. بالإضافة إلى ذلك، يمكن أن تكون مساعدة في إنشاء اختبارات عبر الأنظمة الأساسية، إذا كانت العناصر التي هي وظيفيًا نفسها لها نفس معرف إمكانية الوصول.
- بالنسبة لـ iOS، هذا هو
accessibility identifier
الذي وضعته Apple هنا. - بالنسبة لـ Android، يتم تعيين
accessibility id
إلىcontent-description
للعنصر، كما هو موضح هنا.
بالنسبة لكلا المنصتين، فإن الحصول على عنصر (أو عناصر متعددة) بواسطة accessibility id
هو عادة الطريقة الأفضل. إنها أيضًا الطريقة المفضلة على استراتيجية name
المهملة.
const elem = await $('~my_accessibility_identifier')
await elem.click()
اسم الفئة
استراتيجية class name
هي سلسلة
تمثل عنصر واجهة المستخدم في العرض الحالي.
- بالنسبة لـ iOS، إنه الاسم الكامل لفئة UIAutomation، وسيبدأ بـ
UIA-
، مثلUIATextField
لحقل نص. يمكن العثور على مرجع كامل هنا. - بالنسبة لـ Android، إنه الاسم المؤهل بالكامل لفئة UI Automator class، مثل
android.widget.EditText
لحقل نص. يمكن العثور على مرجع كامل هنا. - بالنسبة لـ Youi.tv، إنه الاسم الكامل لفئة Youi.tv، وسيبدأ بـ
CYI-
، مثلCYIPushButtonView
لعنصر زر الدفع. يمكن العثور على مرجع كامل في صفحة GitHub الخاصة بمحرك You.i
// مثال iOS
await $('UIATextField').click()
// مثال Android
await $('android.widget.DatePicker').click()
// مثال Youi.tv
await $('CYIPushButtonView').click()
سلسلة المحددات
إذا كنت تريد أن تكون أكثر تحديدًا في استعلامك، يمكنك تسلسل المحددات حتى تجد العنصر الصحيح. إذا استدعيت element
قبل الأمر الفعلي، يبدأ WebdriverIO الاستعلام من ذلك العنصر.
على سبيل المثال، إذا كان لديك بنية DOM مثل:
<div class="row">
<div class="entry">
<label>Product A</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
<div class="entry">
<label>Product B</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
<div class="entry">
<label>Product C</label>
<button>Add to cart</button>
<button>More Information</button>
</div>
</div>
وتريد إضافة المنتج B إلى عربة التسوق، سيكون من الصعب القيام بذلك باستخدام محدد CSS فقط.
مع تسلسل المحدد، الأمر أسهل بكثير. ببساطة حدد العنصر المطلوب خطوة بخطوة:
await $('.row .entry:nth-child(2)').$('button*=Add').click()
محدد صورة Appium
باستخدام استراتيجية محدد -image
، من الممكن إرسال Appium ملف صورة يمثل عنصرًا تريد الوصول إليه.
تنسيقات الملفات المدعومة jpg,png,gif,bmp,svg
يمكن العثور على المرجع الكامل هنا
const elem = await $('./file/path/of/image/test.jpg')
await elem.click()
ملاحظة: الطريقة التي يعمل بها Appium مع هذا المحدد هي أنه سيقوم داخليًا بأخذ لقطة شاشة (للتطبيق) ويستخدم محدد الصورة المقدم للتحقق مما إذا كان يمكن العثور على العنصر في تلك اللقطة (للتطبيق).
كن على دراية بحقيقة أن Appium قد يغير حجم لقطة الشاشة (للتطبيق) المأخوذة لجعلها تتطابق مع حجم CSS لشاشة (التطبيق) الخاصة بك (سيحدث هذا على أجهزة iPhone ولكن أيضًا على أجهزة Mac بشاشة Retina لأن DPR أكبر من 1). سيؤدي هذا إلى عدم العثور على تطابق لأن محدد الصورة المقدم قد يكون مأخوذًا من لقطة الشاشة الأصلية. يمكنك إصلاح ذلك عن طريق تحديث إعدادات خادم Appium، راجع وثائق Appium للإعدادات وهذا التعليق للحصول على شرح مفصل.
محددات React
يوفر WebdriverIO طريقة لاختيار مكونات React استنادًا إلى اسم المكون. للقيام بذلك، لديك خيار من أمرين: react$
و react$$
.
تسمح لك هذه الأوامر باختيار المكونات من React VirtualDOM وإعادة إما عنصر WebdriverIO واحد أو مصفوفة من العناصر (اعتمادًا على الدالة المستخدمة).
ملاحظة: الأوامر react$
و react$$
متشابهة في الوظائف، باستثناء أن react$$
سترجع جميع الحالات المطابقة كمصفوفة من عناصر WebdriverIO، وسترجع react$
أول حالة تم العثور عليها.
مثال أساسي
// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
function MyComponent() {
return (
<div>
MyComponent
</div>
)
}
function App() {
return (<MyComponent />)
}
ReactDOM.render(<App />, document.querySelector('#root'))
في الكود أعلاه، هناك حالة MyComponent
بسيطة داخل التطبيق، الذي يقوم React بعرضه داخل عنصر HTML مع id="root"
.
باستخدام أمر browser.react$
، يمكنك اختيار حالة من MyComponent
:
const myCmp = await browser.react$('MyComponent')
الآن بعد أن لديك عنصر WebdriverIO مخزن في متغير myCmp
، يمكنك تنفيذ أوامر العنصر عليه.
تصفية المكونات
تسمح المكتبة التي يستخدمها WebdriverIO داخليًا بتصفية اختيارك حسب الخصائص و/أو حالة المكون. للقيام بذلك، تحتاج إلى تمرير وسيطة ثانية للخصائص و/أو وسيطة ثالثة للحالة إلى أمر المتصفح.
// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
function MyComponent(props) {
return (
<div>
Hello { props.name || 'World' }!
</div>
)
}
function App() {
return (
<div>
<MyComponent name="WebdriverIO" />
<MyComponent />
</div>
)
}
ReactDOM.render(<App />, document.querySelector('#root'))
إذا كنت تريد اختيار حالة MyComponent
التي لها خاصية name
كـ WebdriverIO
، يمكنك تنفيذ الأمر على النحو التالي:
const myCmp = await browser.react$('MyComponent', {
props: { name: 'WebdriverIO' }
})
إذا كنت تريد تصفية اختيارنا حسب الحالة، فإن أمر browser
سيبدو كما يلي:
const myCmp = await browser.react$('MyComponent', {
state: { myState: 'some value' }
})
التعامل مع React.Fragment
عند استخدام أمر react$
لاختيار شظايا React، سيعيد WebdriverIO أول عنصر فرعي لهذا المكون كعقدة المكون. إذا كنت تستخدم react$$
، ستتلقى مصفوفة تحتوي على جميع عقد HTML داخل الشظايا التي تطابق المحدد.
// index.jsx
import React from 'react'
import ReactDOM from 'react-dom'
function MyComponent() {
return (
<React.Fragment>
<div>
MyComponent
</div>
<div>
MyComponent
</div>
</React.Fragment>
)
}
function App() {
return (<MyComponent />)
}
ReactDOM.render(<App />, document.querySelector('#root'))
بالنظر إلى المثال أعلاه، هذه هي الطريقة التي ستعمل بها الأوامر:
await browser.react$('MyComponent') // يعيد عنصر WebdriverIO للـ <div /> الأول
await browser.react$$('MyComponent') // يعيد عناصر WebdriverIO للمصفوفة [<div />, <div />]
ملاحظة: إذا كان لديك حالات متعددة من MyComponent
وكنت تستخدم react$$
لاختيار مكونات الشظايا هذه، فسيتم إرجاعك مصفوفة أحادية البعد لجميع العقد. بمعنى آخر، إذا كان لديك 3 حالات <MyComponent />
، فسيتم إرجاع مصفوفة تحتوي على ستة عناصر WebdriverIO.
استراتيجيات المحدد المخصصة
إذا كان تطبيقك يتطلب طريقة محددة لجلب العناصر، يمكنك تحديد استراتيجية محدد مخصصة بنفسك يمكنك استخدامها مع custom$
و custom$$
. لذلك قم بتسجيل استراتيجيتك مرة واحدة في بداية الاختبار، على سبيل المثال في خطاف before
:
loading...
بالنظر إلى مقتطف HTML التالي:
loading...
ثم استخدمه عن طريق الاتصال:
loading...
ملاحظة: هذا يعمل فقط في بيئة ويب يمكن فيها تشغيل الأمر execute
.