انتخابگرها
پروتکل WebDriver چندین استراتژی انتخابگر را برای پرس و جوی یک عنصر فراهم میکند. WebdriverIO آنها را ساده میکند تا انتخاب عناصر ساده باشد. لطفاً توجه داشته باشید که حتی اگر دستور برای پرس و جوی عناصر $
و $$
نامیده میشود، آنها هیچ ارتباطی با jQuery یا Sizzle Selector Engine ندارند.
در حالی که انتخابگرهای مختلفی در دسترس هستند، تنها تعداد کمی از آنها روشی انعطافپذیر برای یافتن عنصر مناسب ارائه میدهند. به عنوان مثال، با توجه به دکمه زیر:
<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"]') | ✅ خوب | نیاز به ویژگی اضافی دارد، به a11y متصل نیست. |
$('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 به جای آن برای یافتن عناصر با استفاده از ID استفاده کرد.
با این حال، برخی از درایورها (مانند Appium You.i Engine Driver) ممکن است هنوز از این انتخابگر پشتیبانی کنند.
نحوه نوشتار فعلی پشتیبانی شده برای ID عبارتند از:
//مکانیاب css
const button = await $('#someid')
//مکانیاب xpath
const button = await $('//*[@id="someid"]')
//استراتژی id
// نکته: فقط در Appium یا چارچوبهای مشابه که از استراتژی مکانیاب "ID" پشتیبانی میکنند، کار میکند
const button = await $('id=resource-id/iosname')
تابع JS
شما همچنین میتوانید از توابع JavaScript برای دریافت عناصر با استفاده از APIهای بومی وب استفاده کنید. البته، فقط میتوانید این کار را در یک زمینه وب (مثلاً browser
، یا زمینه وب در موبایل) انجام دهید.
با توجه به ساختار HTML زیر:
loading...
شما میتوانید عنصر همسایه #elem
را به صورت زیر پرس و جو کنید:
loading...
انتخابگرهای عمیق
از نسخه v9
WebdriverIO، نیازی به این انتخابگر خاص نیست زیرا WebdriverIO به طور خودکار از Shadow DOM عبور میکند. توصیه میشود با حذف >>>
از جلوی آن، از این انتخابگر مهاجرت کنید.
بسیاری از برنامههای فرانتاند به شدت به عناصر با shadow DOM متکی هستند. پرس و جوی عناصر درون shadow DOM بدون راهحلهای موقت از لحاظ فنی غیرممکن است. shadow$
و shadow$$
چنین راهحلهای موقتی بودند که محدودیتهای خود را داشتند. با انتخابگر عمیق، اکنون میتوانید همه عناصر درون هر shadow DOM را با استفاده از دستور پرس و جوی معمولی، پرس و جو کنید.
با فرض اینکه برنامهای با ساختار زیر داریم:
با این انتخابگر میتوانید عنصر <button />
را که در shadow DOM دیگری قرار دارد، پرس و جو کنید، مثلاً:
loading...
انتخابگرهای موبایل
برای آزمایش موبایل هیبریدی، مهم است که سرور اتوماسیون قبل از اجرای دستورات در زمینه صحیح باشد. برای خودکارسازی حرکات، درایور در حالت ایدهآل باید در زمینه بومی تنظیم شود. اما برای انتخاب عناصر از DOM، درایور باید در زمینه webview پلتفرم تنظیم شود. فقط سپس میتوان از روشهای ذکر شده در بالا استفاده کرد.
برای آزمایش موبایل بومی، تغییری بین زمینهها وجود ندارد، زیرا باید از استراتژیهای موبایل استفاده کنید و مستقیماً از فناوری خودکارسازی دستگاه اصلی استفاده کنید. این به ویژه زمانی مفید است که یک آزمون به کنترل دقیق در یافتن عناصر نیاز دارد.
Android UiAutomator
چارچوب UI Automator اندروید چندین روش برای یافتن عناصر ارائه میدهد. شما میتوانید از UI Automator API، به ویژه از کلاس UiSelector برای مکانیابی عناصر استفاده کنید. در Appium، شما کد جاوا را به عنوان یک رشته به سرور ارسال میکنید، که آن را در محیط برنامه اجرا میکند و عنصر یا عناصر را برمیگرداند.
const selector = 'new UiSelector().text("Cancel").className("android.widget.Button")'
const button = await $(`android=${selector}`)
await button.click()
Android DataMatcher و ViewMatcher (فقط Espresso)
استراتژی DataMatcher اندروید روشی برای یافتن عناصر با 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)
استراتژی view tag روشی مناسب برای یافتن عناصر با تگ آنها ارائه میدهد.
const elem = await $('-android viewtag:tag_identifier')
await elem.click()
iOS UIAutomation
هنگام خودکارسازی یک برنامه iOS، میتوان از چارچوب UI Automation اپل برای یافتن عناصر استفاده کرد.
این API جاوااسکریپت روشهایی برای دسترسی به نما و همه چیز روی آن ارائه میدهد.
const selector = 'UIATarget.localTarget().frontMostApp().mainWindow().buttons()[0]'
const button = await $(`ios=${selector}`)
await button.click()
همچنین میتوانید از جستجوی predicate در iOS UI Automation در Appium برای بهبود بیشتر انتخاب عناصر استفاده کنید. برای جزئیات بیشتر اینجا را ببینید.
رشتههای predicate و زنجیرههای کلاس iOS XCUITest
با iOS 10 و بالاتر (با استفاده از درایور XCUITest
)، میتوانید از رشتههای predicate استفاده کنید:
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
استراتژی مکانیاب accessibility id
برای خواندن یک شناسه منحصر به فرد برای یک عنصر UI طراحی شده است. این مزیت را دارد که در طول محلیسازی یا هر فرآیند دیگری که ممکن است متن را تغییر دهد، تغییر نمیکند. علاوه بر این، میتواند در ایجاد آزمونهای چند پلتفرمی کمک کند، اگر عناصری که از نظر عملکردی یکسان هستند، همان شناسه دسترسیپذیری را داشته باشند.
- برای iOS این
accessibility identifier
است که توسط اپل اینجا تعیین شده است. - برای اندروید
accessibility id
بهcontent-description
برای عنصر نگاشت میشود، همانطور که اینجا توضیح داده شده است.
برای هر دو پلتفرم، دریافت یک عنصر (یا چندین عنصر) با accessibility id
آنها معمولاً بهترین روش است. همچنین روش ترجیحی نسبت به استراتژی name
منسوخ شده است.
const elem = await $('~my_accessibility_identifier')
await elem.click()
نام کلاس
استراتژی class name
یک string
است که یک عنصر UI را در نمای فعلی نشان میدهد.
- برای iOS، نام کامل یک کلاس UIAutomation است و با
UIA-
شروع میشود، مانندUIATextField
برای یک فیلد متنی. مرجع کامل را میتوان اینجا یافت. - برای اندروید، نام کاملاً واجد شرایط یک UI Automator class است، مانند
android.widget.EditText
برای یک فیلد متنی. مرجع کامل را میتوان اینجا یافت. - برای Youi.tv، نام کامل یک کلاس Youi.tv است و با
CYI-
شروع میشود، مانندCYIPushButtonView
برای یک عنصر دکمه فشاری. مرجع کامل را میتوان در صفحه GitHub درایور You.i Engine یافت.
// مثال iOS
await $('UIATextField').click()
// مثال اندروید
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 با این انتخابگر این است که به طور داخلی یک (app)screenshot میگیرد و از انتخابگر تصویر ارائه شده برای تأیید اینکه آیا عنصر میتواند در آن (app)screenshot یافت شود، استفاده میکند.
توجه داشته باشید که Appium ممکن است (app)screenshot گرفته شده را تغییر اندازه دهد تا با اندازه CSS صفحه (app) شما مطابقت داشته باشد (این اتفاق در آیفونها و همچنین در دستگاههای Mac با نمایشگر Retina رخ میدهد زیرا DPR بزرگتر از 1 است). این باعث میشود که تطابقی پیدا نشود زیرا انتخابگر تصویر ارائه شده ممکن است از screenshot اصلی گرفته شده باشد. شما میتوانید این مشکل را با بهروزرسانی تنظیمات سرور 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 به صورت داخلی استفاده میکند، امکان فیلتر کردن انتخاب شما را بر اساس props و/یا state کامپوننت فراهم میکند. برای انجام این کار، باید یک آرگومان دوم برای props و/یا یک آرگومان سوم برای state به دستور مرورگر ارسال کنید.
// 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
را که دارای prop name
با مقدار WebdriverIO
است انتخاب کنید، میتوانید دستور را به صورت زیر اجرا کنید:
const myCmp = await browser.react$('MyComponent', {
props: { name: 'WebdriverIO' }
})
اگر میخواهید انتخاب خود را بر اساس state فیلتر کنید، دستور 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
قابل اجرا باشد.