مرحبا بالجميع! اسمي
أرتيوم سوكوفيتس . أرغب في مشاركة ترجمة مقالتي حول Atlas: تجسيد إطار عمل عناصر HTML ، والذي يقدم طريقة مختلفة تمامًا للعمل مع كائن الصفحة.
قبل الدخول في التفاصيل ، أريد أن أسأل: كم عدد الأغلفة الخاصة بكائن الصفحة التي تعرفها؟ عنصر الصفحة ، تشغيل الشاشة ، المكون القابل للتحميل ، سلسلة من الدعوات ...
وماذا سيحدث إذا أخذنا كائن صفحة مع تطبيق على الواجهة ، وربط نمط البروكسي وإضافة القليل من وظائف Java 8؟
إذا كانت مهتمة ، أقترح التحول إلى القط.

مقدمة
عند استخدام نمط تصميم PageObject القياسي ، تنشأ عدد من المشكلات:
الازدواجية في العناصرpublic class MainPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } public class AnyOtherPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; }
هنا ، يتم استخدام كتلة رأس في فئات PageObject المختلفة.
عدم وجود معلمات للعناصر public class EditUserPage { @FindBy(xpath = "//div[text()='Text_1']") private TextBlock lastActivity; @FindBy(xpath = "//div[text()='Text_2']") private TextBlock blockReason; }
يصف هذا المثال عناصر صفحة تحرير إعدادات المستخدم. يحتوي عنصرا TextBlock على محدد موقع متطابق تقريبًا مع اختلاف فقط في قيمة النص ("Text_1" و "Text_2").
نفس النوع من الكود public class UserPage { @FindBy(xpath = "//div[text()='']/input") private UfsTextInput innerPhone; @FindBy(xpath = "//div[text()='Email']/input") private UfsTextInput email; @FindBy(xpath = "//button[text()='']") private UfsButton save; @FindBy(xpath = "//button[text()='']") private UfsButton toUsersList; }
في العمل اليومي ، يمكنك العثور على "كائن الصفحة" ، والذي يتكون من العديد من أسطر التعليمات البرمجية بنفس العناصر. في المستقبل ، هذه الفئات هي "غير مريح" للحفاظ عليها.
فئة كبيرة مع الخطوات public class MainSteps { public void hasText(HtmlElement e, Matcher m) public void hasValue(HtmlElement e, Matcher m) public void linkContains(HtmlElement e, String s) public void hasSize(List<HtmlElement> e, Matcher m) public void hasItem(List<HtmlElement> e, Matcher m)
بمرور الوقت ، تنمو فئة خطوات العمل مع العناصر. مطلوب مزيد من الاهتمام بحيث لا توجد طرق مكررة.
دليلك إلى عالم كائن الصفحة
يهدف إعادة دمج إطار عمل عناصر HTML إلى حل المشكلات المذكورة أعلاه ، وتقليل عدد سطور التعليمات البرمجية لمشروع اختبار ، وعمل أكثر تفكيرًا مع القوائم والتوقعات ، بالإضافة إلى صقل الأداة بنفسك بفضل نظام الإضافات.
يعد
Atlas بمثابة إطار جديد لجافا لتطوير اختبارات واجهة المستخدم التلقائية مع تطبيق نمط كائن الصفحة من خلال الواجهات. يوفر هذا النهج إمكانية توارث متعددة في بناء شجرة العناصر ، والتي توفر في نهاية المطاف رمزًا موجزًا لاختباراتك الذاتية.
الابتكار الرئيسي للإطار هو استخدام واجهات بدلاً من الفئات القياسية.
هذا ما يبدو عليه وصف صفحة github.com الرئيسية:
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free)]") AtlasWebElement trial(); }
يصف الرمز أعلاه الصفحة الرئيسية لموقع GitHub بعنصر واحد ووراثة متعددة من طبقات WebPage و WithHeader (يتم تقديم المثال للأغراض التعليمية فقط ، وبالتالي يتم حذف معظم عناصر الويب).
هيكل الإطار
يتكون أطلس حاليًا من ثلاث وحدات:
- أطلس النواة
- أطلس-webdriver
- أطلس-appium
يصف
atlas-core الوظيفة الأساسية لمعالجة كائنات الصفحة باستخدام الواجهات. تم أخذ فكرة استخدام الواجهات من أداة التعديل التحديثي الشهيرة.

يتم استخدام
وحدتي atlas-webdriver و
atlas-appium لتطوير شبكة UI الآلية ونصوص الجوال UI. نقطة الدخول الرئيسية لوصف صفحات الويب هي واجهة WebPage ولشاشات الجوّال - الشاشة. من الناحية النظرية ، يتم بناء atlas-webdriver و atlas-appium على الملحقات (الحزمة * .extension).
عناصر

تأتي الأداة مع
فئتين متخصصتين للعمل مع عناصر واجهة المستخدم (تمثيلية لفئة WebElement).
يُستكمل نظام AtlasWebElement و
AtlasMobileElement بأساليب ينبغي و
waitUntil . (النظر في هذه الأساليب سيكون أكثر في المادة).
توفر الأداة القدرة على إنشاء مكونات خاصة بك عن طريق توسيع الفئات المذكورة أعلاه ، والتي تتيح لك إنشاء عنصر عبر النظام الأساسي.
الميزات الرئيسية
دعنا نفكر بمزيد من التفصيل في وظيفة الأداة:
واجهات بدلا من الطبقاتعند وصف PageObjects القياسية ، يتم استخدام الواجهات بدلاً من الفئات.
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]") AtlasWebElement trial(); }
يصف هذا المثال الرابط في صفحة بدء جيثب.
معلمة العناصرتخيل أن لدينا نموذج مع الحقول:

لوصف ذلك ، تحتاج إلى إنشاء 11 متغيرًا مع تعليق توضيحيFindBy ، وإذا لزم الأمر ، قم بإعلان getter.
باستخدام Atlas ، تحتاج فقط إلى عنصر AtlasWebElement ذي معلمات
واحد .
public interface MainPage extends WebPage { @FindBy("//div[text()='{{ text }}']/input") AtlasWebElement input(@Param("text") String text); }
رمز الاختبار الآلي هو كما يلي:
@Test public void simpleTest() { onMainPage().input("First Name").sendKeys("*"); onMainPage().input("Postcode").sendKeys("*"); onMainPage().input("Email").sendKeys("*"); }
ننتقل إلى الصفحة المطلوبة ، استدعاء الأسلوب مع المعلمة وتنفيذ الإجراءات المطلوبة مع العنصر. طريقة مع المعلمة تصف حقل معين.
وراثة متعددةتم ذكر سابقًا أن الحظر (على سبيل المثال ، الرأس) ، والذي يتم استخدامه في كائنات صفحة مختلفة ، يمثل تكرارًا للرمز.
هناك github رأس.

وصفنا هذه الكتلة (تم حذف معظم عناصر الويب):
public interface Header extends AtlasWebElement { @FindBy(".//input[contains(@class,'header-search-input')]") AtlasWebElement searchInput(); }
بعد ذلك ، قم بإنشاء طبقة يمكن توصيلها بأي صفحة:
public interface WithHeader { @FindBy("//header[contains(@class,'Header')]") Header header(); }
نحن نوسع الصفحة الرئيسية مع كتلة الرأس.
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a)]") AtlasWebElement trial(); }
بشكل عام ، يمكنك إنشاء المزيد من الطبقات وتوصيلها بالصفحة المرغوبة. في المثال أدناه ، قم بتوصيل طبقات الرأس والتذييل والشريط الجانبي من الصفحة الرئيسية.
public interface MainPage extends WithHeader, WithFooter, WithSidebar {}
دعنا ننتقل. يحتوي الرأس على 4 أزرار و 3 قوائم منسدلة وحقل بحث واحد:

لنقم بإنشاء عنصر
زر خاص بنا ووصف أربعة أزرار مع عنصر واحد.
public interface Button extends AtlasWebElement { @FindBy(".//a[contains(., '{{ value }}')]") AtlasWebElement selectButton(@Param("value") String value); }
قم بتوصيل زر الزر بطبقة الرأس. وبالتالي ، نقوم بتوسيع وظيفة رأس جيثب.
public interface Header extends WithButton { … }
يمكن توصيل عنصر
زر منفصل
بعدة طبقات من موقع الويب والحصول بسرعة على العنصر المطلوب في الصفحة المطلوبة.
مثال:
@Test public void simpleTest() { onMainPage().open("https://github.com"); onMainPage().header().button("Priing").click(); }
في السطر الثاني من الاختبار ، يتم الوصول إلى رأس الموقع ، ثم ندعو الزر ذي المعلمات بالقيمة "التسعير" والنقر فوق.
يمكن أن يكون هناك عدد قليل من العناصر على الموقع الذي تم اختباره والذي يتكرر من صفحة إلى أخرى. من أجل عدم وصفها جميعًا بنهج كائن الصفحة القياسي ، يمكنك وصفها مرة واحدة وتوصيلها عند الضرورة. توفير الوقت وعدد أسطر التعليمات البرمجية واضح.
الأساليب الافتراضيةقدم Java 8 الأساليب الافتراضية ، والتي تُستخدم لتحديد الوظيفة المطلوبة مسبقًا.
افترض أن لدينا عنصرًا "ضارًا": على سبيل المثال ، مربع اختيار يعمل أو لا يعمل. العديد من السيناريوهات تمر عبره. مطلوب لتمكين مربع الاختيار إذا تم تعطيله:
if(onMainPage().rentFilter().checkbox("").getAttribute("class").contains("disabled")) { onMainPage().rentFilter().checkbox("").click(); }
من أجل عدم تخزين كل هذا الرمز في فئة الخطوة ، من الممكن وضعه بجوار العنصر كطريقة افتراضية.
public interface Checkbox extends AtlasWebElement { @FindBy("//...") AtlasWebElement checkBox((@Param("value") String value); default void selectCheckbox(String value) { if (checkBox(value).getAttribute("class").contains("disabled")) { checkBox(value).click(); } } }
الآن ستبدو الخطوة في الاختبار كما يلي:
onMainPage().rentFilter().selectCheckbox("");
مثال آخر تريد فيه الجمع بين تنظيف وكتابة الأحرف في حقل.
onMainPage().header().input("GitHub").clear(); onMainPage().header().input("GitHub").sendKeys("*");
حدد طريقة تقوم بمسح الحقل وإرجاع العنصر نفسه:
public interface Input extends AtlasWebElement { @FindBy("//xpath") AtlasWebElement input(@Param("value") String value); default AtlasWebElement withClearInput(String value) { input(value).clear(); return input(value); } }
في طريقة الاختبار ، تكون الخطوة كما يلي:
onMainPage().header().withClearInput("GitHub").sendKeys("Atlas");
بهذه الطريقة ، يمكن برمجة السلوك المطلوب في العنصر.
المحاولة (إعادة المحاولة)يحتوي Atlas على إعادة المحاولات المضمنة. لا داعي للقلق بشأن استثناءات مثل
NotFoundException و
StaleElementReferenceException و
WebDriverException ، ويمكنك أيضًا نسيان استخدام التوقعات الصريحة والضمنية لواجهة برمجة تطبيقات Selenium.
onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10));
في حالة اكتشاف استثناء في مرحلة ما من السلسلة ، تتكرر المرحلة من البداية.
من الممكن ضبط الفاصل الزمني الزمني بشكل مستقل والذي يمكنك من خلاله تكرار أو تكرار التكرار.
Atlas atlas = new Atlas(new WebDriverConfiguration(driver)) .context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class))));
نتوقع لمدة ثلاث ثوان بمعدل استطلاع واحد في الثانية.
يمكننا أيضًا ضبط انتظار عنصر معين باستخدام التعليق التوضيحي لإعادة
المحاولة . بالنسبة لجميع العناصر ، سيحدث البحث في غضون 3 ثوانٍ ، وفي حالة واحد سيكون 20.
@Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia();
العمل مع القوائممن خارج الصندوق ، توفر الأداة العمل مع القوائم. ماذا يعني هذا؟ يوجد حقل به علامة إدخال حيث ندخل النص ، ثم تظهر قائمة منسدلة ، لا تظهر العناصر على الفور.

لمثل هذه الحالات هناك كيان ElementsCollection. مع مساعدتها هناك عمل مع القوائم.
public interface ContributorsPage extends WebPage, WithHeader { @FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]") ElementsCollection<RepositoryCard> hovercards(); }
استخدام:
onSite().onContributorsPage().hovercards().waitUntil(hasSize(4));
من الممكن أيضًا تصفية العناصر وتحويلها إلى قائمة من نوع آخر.
تأكيدات ذكيةكما ذكرنا سابقًا ، تستخدم كلاً من AtlasWebElement و AtlasMobileElement طرق should ، waitUntil للعمل مع الشيكات (العبارات).
لماذا يتم ذلك؟ لتوفير الوقت عند تحليل تقارير التشغيل للبرامج النصية الآلية. يتم إجراء معظم الاختبارات الوظيفية في نهاية البرنامج النصي: فهي تهم أخصائي الاختبارات الوظيفية ، والشيكات الوسيطة لأخصائي الاختبار الآلي. لذلك ، إذا كانت وظيفة المنتج لا تعمل ، فمن المنطقي رمي استثناء AssumptionError ، وإلا RuntimeException.


سوف يعرض Allure على الفور ما نتعامل معه: إما أن يكون لدينا عيب في المنتج (يتولى أخصائي FT المهمة) ، أو ينهار الاختبار التلقائي (يفهم أخصائي AT).
نموذج التمديد
لدى المستخدم الفرصة لإعادة تعريف الوظيفة الأساسية للأداة أو تنفيذ وظيفته الخاصة. يشبه نموذج تمديد Atlas نموذج تمديد JUnit 5. تم بناء وحدات أطلس webdriver و atlas-appium على امتدادات. إذا كنت مهتمًا ، فتحقق من شفرة المصدر.
دعنا نتفحص مثالًا تجريديًا: يلزم تطوير اختبارات واجهة المستخدم لبرنامج Internet Explorer 11 (في بعض الأماكن لا يزال يستخدم). هناك أوقات لا تعمل فيها النقر القياسي على العناصر ، ثم يمكنك استخدام JS-click. عليك أن تقرر تجاوز النقرة على مشروع الاختبار بالكامل مؤقتًا.
onMainPage().header().button("en").click();
كيف نفعل ذلك؟
قم بإنشاء ملحق يقوم بتطبيق واجهة MethodExtension.
public class JSClickExt implements MethodExtension { @Override public Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) { final WebDriver driver = config.getContext(WebDriverContext.class) .orElseThrow(() -> new AtlasException("Context doesn't exist")).getValue(); final JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("arguments[0].click();", proxy); return proxy; } @Override public boolean test(Method method) { return method.getName().equals("click"); } }
نحن إعادة تعريف طريقتين. في طريقة الاختبار () ، نحدد أننا تجاوزنا طريقة النقرة. تطبق طريقة الاستدعاء المنطق المطلوب. الآن انقر فوق العنصر سيحدث من خلال JavaScript.
نربط التمديد على النحو التالي:
atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com")) .extension(new JSClickExt());
بمساعدة الملحقات ، من الممكن إنشاء بحث عن محددات العناصر الموجودة في قاعدة البيانات وتنفيذ ميزات أخرى مثيرة للاهتمام - كل هذا يتوقف على خيالك واحتياجاتك.
نقطة إدخال واحدة إلى PageObjects (موقع ويب)تتيح لك الأداة تخزين جميع صفحاتك في مكان واحد والعمل فقط من خلال كيان الموقع في المستقبل.
public interface GitHubSite extends WebSite { @Page MainPage onMainPage(); @Page(url = "search") SearchPage onSearchPage(@Query("q") String value); @Page(url = "{profile}/{project}/tree/master/") ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project); @Page ContributorsPage onContributorsPage(); }
بالإضافة إلى ذلك ، يمكن للصفحة تعيين عنوان url سريع ومعلمات الاستعلام وشرائح المسار.
onSite().onProjectPage("qameta", "atlas").contributors().click();
يحتوي السطر أعلاه على قسمين من المسار (qameta و atlas) ، والذي يترجم إلى
github.com/qameta/atlas/tree/master . الميزة الرئيسية لهذا النهج هو أنه من الممكن فتح الصفحة المطلوبة على الفور دون النقر عليها.
@Test public void usePathWebSiteTest() { onSite().onProjectPage("qameta", "atlas").contributors().click(); onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); }
العمل مع عنصر المحمولالعمل مع عنصر المحمول (AtlasMobileElement) يشبه العمل مع عنصر الويب AtlasWebElement. بالإضافة إلى ذلك ، تمت إضافة ثلاث طرق إلى AtlasMobileElement: التمرير على الشاشة لأعلى / لأسفل (scrollUp / scrollDown) والنقر فوق عنصر مع الاستمرار (longPress).
اسمح لي بتقديم مثال للشاشة الرئيسية لتطبيق Wikipedia. تم وصف عنصر واحد لكل من نظام التشغيل iOS و Android. يصفون أيضًا زرًا ذي معلمات.
public interface MainScreen extends Screen { @Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia(); @IOSFindBy(id = "{{ value }}") AtlasMobileElement button(@Param("value") String value); }
تبدو الاختبارات بنفس الطريقة:
public void simpleExample() { onMainScreen().searchWikipedia().click(); onSearchScreen().search().sendKeys("Atlas"); onSearchScreen().item("Atlas LV-3B").swipeDownOn().click(); onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B"))); }
في المثال أعلاه ، نفتح صفحة ويكيبيديا الرئيسية ، ونضغط على شريط البحث ، وأدخل نص أطلس ، ثم ننتقل إلى عنصر القائمة بقيمة Atlas LV-3B وننتقل إلى عرضه. يتحقق السطر الأخير من عرض العنوان ويحتوي على القيمة المطلوبة.
مستمعيمكن تنفيذ تسجيل الأحداث باستخدام مستمع خاص (واجهة المستمع). تحتوي كل طريقة على أربعة أحداث عند استدعائها:
قبل ،
مرر ،
فشل .
بعد .

باستخدام هذه الواجهة ، يمكنك تنظيم التقارير. يوجد أدناه مثال على مستمع Allure ، والذي يمكن العثور عليه على
الرابط .

بعد ذلك ، قم بتوصيل المستمع عند تهيئة فئة Atlas.
atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener());
بالطريقة أعلاه ، من الممكن إنشاء مستمع لأنظمة التقارير المختلفة (على سبيل المثال ، لـ ReportPortal).
صلة
لأتمتة واجهة مستخدم الويب ، يكفي تسجيل تبعية أطلس - webdriver والإشارة إلى أحدث إصدار حالي (الإصدار 1.6.0 مناسب وقت كتابة هذا النص).
مخضرم:
<dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-webdriver</artifactId> <version>${atlas.version}</version> </dependency>
Gradle:
dependencies { ompile 'io.qameta.atlas:atlas-webdriver:1.+' }
نحن نفعل الشيء نفسه إذا كنت تريد أتمتة UI Mobile.
مخضرم:
<dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-appium</artifactId> <version>${atlas.version}</version> </dependency>
Gradle:
dependencies { ompile 'io.qameta.atlas:atlas-appium:1.+' }
استخدام
بعد إضافة التبعية إلى مشروعك ، يجب تهيئة مثيل فئة Atlas.
@Before public void startDriver() { driver = new ChromeDriver(); atlas = new Atlas(new WebDriverConfiguration(driver)); }
نمرر لمنشئ أطلس مثيل التكوين ، وكذلك السائق.
يوجد حاليًا تكوينان: WebDriverConfiguration و AppiumDriverConfiguration. يحتوي كل تكوين على ملحقات افتراضية محددة.
بعد ذلك ، نحدد طريقة ستنشئ كل PageObject.
private <T extends WebPage> T onPage(Class<T> page) { return atlas.create(driver, page); }
مثال على حالة اختبار بسيطة:
@Test public void simpleTest() { onPage(MainPage.class).open("https://github.com"); onPage(MainPage.class).header().searchInput().sendKeys("Atlas"); onPage(MainPage.class).header().searchInput().submit(); }
نفتح الموقع وننتقل إلى طبقة الرأس ونبحث عن حقل نص (إدخال البحث) فيه ، وأدخل النص واضغط على إدخال.
النتائج
في الختام ، أود أن أشير إلى أن Atlas أداة مرنة تتميز بميزات رائعة. يمكن تخصيصه لمشروع اختبار معين بطريقة ملائمة لفريقك ولك. تطوير اختبارات المنصات ، إلخ.
تتوفر
مقاطع فيديو لتقارير من
Heisenbug و
Selenium Camp و
Nexign QA Meetup . هناك دردشة Telegram @ atlashelp.
باستخدام هذه الأداة ، يمكنك تقليل عدد كبير من أسطر التعليمات البرمجية (تم اختبارها في مشاريع بواسطة شركات مثل Yandex و SberTech و Tinkoff).