هل هناك أي اقتراحات لاختبار كود extjs في المتصفح، ويفضل أن يكون ذلك باستخدام السيلينيوم؟

StackOverflow https://stackoverflow.com/questions/107314

سؤال

لقد استخدمنا السيلينيوم بنجاح كبير للتعامل مع اختبارات مواقع الويب عالية المستوى (بالإضافة إلى اختبارات مستندات بايثون الشاملة على مستوى الوحدة).ولكننا الآن نستخدم extjs للعديد من الصفحات، ومن الصعب دمج اختبارات السيلينيوم للمكونات المعقدة مثل الشبكات.

هل نجح أي شخص في كتابة اختبارات آلية لصفحات الويب المستندة إلى extjs؟الكثير من البحث على جوجل يجد أشخاصًا يعانون من مشكلات مماثلة، ولكن القليل من الإجابات.شكرًا!

هل كانت مفيدة؟

المحلول

أكبر عقبة في اختبار ExtJS مع السيلينيوم هي أن ExtJS لا يعرض عناصر HTML القياسية وأن Selenium IDE سيقوم بسذاجة (وبحق) بإنشاء أوامر تستهدف العناصر التي تعمل فقط كديكور - وهي عناصر زائدة عن الحاجة تساعد ExtJS في سطح المكتب بالكامل. انظر و اشعر.فيما يلي بعض النصائح والحيل التي جمعتها أثناء كتابة اختبار السيلينيوم الآلي مقابل تطبيق ExtJS.

نصائح عامة

تحديد موقع العناصر

عند إنشاء حالات اختبار السيلينيوم عن طريق تسجيل إجراءات المستخدم باستخدام Selenium IDE على Firefox، سيبني السيلينيوم الإجراءات المسجلة على معرفات عناصر HTML.ومع ذلك، بالنسبة لمعظم العناصر القابلة للنقر، يستخدم ExtJS معرفات تم إنشاؤها مثل "ext-gen-345" والتي من المحتمل أن تتغير في زيارة لاحقة لنفس الصفحة، حتى لو لم يتم إجراء أي تغييرات في التعليمات البرمجية.بعد تسجيل إجراءات المستخدم للاختبار، يجب أن يكون هناك جهد يدوي لتنفيذ جميع هذه الإجراءات التي تعتمد على المعرفات التي تم إنشاؤها واستبدالها.هناك نوعان من الاستبدالات التي يمكن إجراؤها:

استبدال محدد موقع الهوية بمحدد موقع CSS أو XPath

تبدأ محددات مواقع CSS بـ "css=" وتبدأ محددات مواقع XPath بـ "//" (البادئة "xpath =" اختيارية).تعتبر محددات مواقع CSS أقل تفصيلاً وأسهل في القراءة ويجب تفضيلها على محددات مواقع XPath.ومع ذلك، قد تكون هناك حالات يلزم فيها استخدام محددات مواقع XPath لأن محدد مواقع CSS ببساطة لا يمكنه قطعها.

تنفيذ جافا سكريبت

تتطلب بعض العناصر أكثر من مجرد تفاعلات بسيطة بين الماوس ولوحة المفاتيح نظرًا للعرض المعقد الذي تقوم به ExtJS.على سبيل المثال، Ext.form.CombBox ليس في الحقيقة ملف <select> عنصر ولكنه إدخال نص بقائمة منسدلة منفصلة موجودة في مكان ما أسفل شجرة المستندات.لمحاكاة تحديد ComboBox بشكل صحيح، من الممكن أولاً محاكاة النقر على السهم المنسدل ثم النقر على القائمة التي تظهر.ومع ذلك، قد يكون تحديد موقع هذه العناصر من خلال محددات مواقع CSS أو XPath أمرًا مرهقًا.البديل هو تحديد موقع مكون ComoBox نفسه واستدعاء الأساليب عليه لمحاكاة التحديد:

var combo = Ext.getCmp('genderComboBox'); // returns the ComboBox components
combo.setValue('female'); // set the value
combo.fireEvent('select'); // because setValue() doesn't trigger the event

في السيلينيوم runScript يمكن استخدام الأمر لتنفيذ العملية المذكورة أعلاه بشكل أكثر إيجازًا:

with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

التعامل مع AJAX والعرض البطيء

يحتوي السيلينيوم على نكهات "*AndWait" لجميع أوامر انتظار تحميل الصفحة عندما يؤدي إجراء المستخدم إلى انتقالات الصفحة أو إعادة تحميلها.ومع ذلك، نظرًا لأن عمليات جلب AJAX لا تتضمن تحميلًا فعليًا للصفحة، فلا يمكن استخدام هذه الأوامر للمزامنة.الحل هو الاستفادة من القرائن المرئية مثل وجود/غياب مؤشر تقدم AJAX أو ظهور الصفوف في الشبكة والمكونات الإضافية والروابط وما إلى ذلك.على سبيل المثال:

Command: waitForElementNotPresent
Target: css=div:contains('Loading...')

في بعض الأحيان، لن يظهر العنصر إلا بعد فترة زمنية معينة، اعتمادًا على مدى سرعة عرض ExtJS للمكونات بعد أن يؤدي إجراء المستخدم إلى تغيير طريقة العرض.بدلاً من استخدام التأخير التعسفي مع pause الأمر، فإن الطريقة المثالية هي الانتظار حتى يصبح عنصر الاهتمام في متناول أيدينا.على سبيل المثال، للنقر على عنصر بعد انتظار ظهوره:

Command: waitForElementPresent
Target: css=span:contains('Do the funky thing')
Command: click
Target: css=span:contains('Do the funky thing')

إن الاعتماد على الإيقاف المؤقت التعسفي ليس فكرة جيدة لأن اختلافات التوقيت التي تنتج عن تشغيل الاختبارات في متصفحات مختلفة أو على أجهزة مختلفة ستجعل حالات الاختبار غير مستقرة.

العناصر غير القابلة للنقر

لا يمكن تشغيل بعض العناصر بواسطة click يأمر.ذلك لأن مستمع الحدث موجود فعليًا في الحاوية، ويراقب أحداث الماوس على عناصره الفرعية، والتي تصل في النهاية إلى الأصل.عنصر التحكم في علامة التبويب هو أحد الأمثلة.للنقر على علامة التبويب، عليك محاكاة ملف mouseDown الحدث في علامة التبويب:

Command: mouseDownAt
Target: css=.x-tab-strip-text:contains('Options')
Value: 0,0

التحقق من صحة المجال

حقول النموذج (مكونات Ext.form.*) التي ترتبط بالتعبيرات العادية أو أنواع vtypes للتحقق من الصحة ستؤدي إلى تشغيل التحقق من الصحة مع تأخير معين (راجع validationDelay الخاصية التي تم ضبطها على 250 مللي ثانية افتراضيًا)، بعد قيام المستخدم بإدخال النص أو فورًا عندما يفقد الحقل التركيز - أو يصبح غير واضح (راجع validateOnDelay ملكية).من أجل تفعيل التحقق من صحة الحقل بعد إصدار أمر نوع السيلينيوم لإدخال بعض النص داخل الحقل، عليك القيام بأي مما يلي:

  • تشغيل التحقق المؤجل

    يقوم ExtJS بإطلاق مؤقت تأخير التحقق من الصحة عندما يتلقى الحقل أحداث keyup.لتشغيل هذا المؤقت، ما عليك سوى إصدار حدث مفتاح وهمي (لا يهم المفتاح الذي تستخدمه عندما يتجاهله ExtJS)، متبوعًا بإيقاف مؤقت قصير أطول من validationDelay:

    Command: keyUp
    Target: someTextArea
    Value: x
    Command: pause
    Target: 500
    
  • تفعيل التحقق الفوري

    يمكنك إدخال حدث تمويه في الحقل لبدء التحقق الفوري:

    Command: runScript
    Target: someComponent.nameTextField.fireEvent("blur")
    

التحقق من نتائج التحقق

بعد التحقق من الصحة، يمكنك التحقق من وجود أو عدم وجود حقل خطأ:

Command: verifyElementNotPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

Command: verifyElementPresent   
Target: //*[@id="nameTextField"]/../*[@class="x-form-invalid-msg" and not(contains(@style, "display: none"))]

لاحظ أن "العرض:"لا شيء" ضروري لأنه بمجرد ظهور حقل خطأ ومن ثم يجب إخفاؤه، سيقوم ExtJS ببساطة بإخفاء حقل الخطأ بدلاً من إزالته بالكامل من شجرة DOM.

نصائح خاصة بالعنصر

النقر فوق Ext.form.Button

  • الخيار 1

    يأمر:نقر هدف:CSS=زر:يحتوي على('حفظ')

    تحديد الزر حسب التسمية التوضيحية الخاصة به

  • الخيار 2

    يأمر:نقر هدف:css=زر #حفظ الخيارات

    تحديد الزر حسب معرفه

تحديد قيمة من Ext.form.ComboBox

Command: runScript
Target: with (Ext.getCmp('genderComboBox')) { setValue('female'); fireEvent('select'); }

يقوم أولاً بتعيين القيمة، ثم يطلق الحدث المحدد بشكل صريح في حالة وجود مراقبين.

نصائح أخرى

هذا مدونة ساعدني كثيرا.لقد كتب كثيرًا عن هذا الموضوع ويبدو أنه لا يزال نشطًا.يبدو أن الرجل أيضًا يقدر التصميم الجيد.

يتحدث بشكل أساسي عن استخدام إرسال جافا سكريبت لإجراء الاستعلامات واستخدام طريقة Ext.ComponentQuery.query لاسترداد الأشياء بنفس الطريقة التي تستخدمها في تطبيق ext داخليًا.وبهذه الطريقة يمكنك استخدام xtypes وitemIds ولا داعي للقلق بشأن محاولة تحليل أي من العناصر المجنونة التي يتم إنشاؤها تلقائيًا.

وجدت هذا المقال على وجه الخصوص مفيدة للغاية.

قد أنشر شيئًا أكثر تفصيلاً هنا قريبًا - ما زلت أحاول فهم كيفية القيام بذلك بشكل صحيح

لقد قمت باختبار تطبيق الويب ExtJs الخاص بي باستخدام السيلينيوم.كانت إحدى أكبر المشكلات هي اختيار عنصر في الشبكة للقيام بشيء ما به.

لهذا كتبت طريقة مساعدة (في فئة SeleniumExtJsUtils وهي عبارة عن مجموعة من الطرق المفيدة لتسهيل التفاعل مع ExtJs):

/**
 * Javascript needed to execute in order to select row in the grid
 * 
 * @param gridId Grid id
 * @param rowIndex Index of the row to select
 * @return Javascript to select row
 */
public static String selectGridRow(String gridId, int rowIndex) {
    return "Ext.getCmp('" + gridId + "').getSelectionModel().selectRow(" + rowIndex + ", true)";
}

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

selenium.runScript( SeleniumExtJsUtils.selectGridRow("<myGridId>", 5) );

لكي يعمل هذا، أحتاج إلى تعيين المعرف الخاص بي على الشبكة وعدم السماح لـ ExtJs بإنشاء هويته الخاصة.

لاكتشاف هذا العنصر مرئيًا، يمكنك استخدام العبارة:not(contains(@style, "display: none")

من الأفضل استخدام هذا:

visible_clause = "not(ancestor::*[contains(@style,'display: none')" +
    " or contains(@style, 'visibility: hidden') " + 
    " or contains(@class,'x-hide-display')])"

hidden_clause = "parent::*[contains(@style,'display: none')" + 
    " or contains(@style, 'visibility: hidden')" + 
    " or contains(@class,'x-hide-display')]"

هل يمكنك تقديم مزيد من المعلومات حول أنواع المشكلات التي تواجهها في اختبار extjs؟

أحد امتدادات السيلينيوم التي أجدها مفيدة هو waitForCondition.إذا كانت مشكلتك تبدو وكأنها مشكلة في أحداث Ajax، فيمكنك استخدام waitForCondition لانتظار حدوث الأحداث.

يمكن أن يكون اختبار صفحات الويب Ext JS أمرًا صعبًا، وذلك بسبب تعقيد HTML الذي ينتهي بهم الأمر إلى إنشاءه كما هو الحال مع شبكات Ext JS.

روبوت HTML5 يتعامل مع هذا باستخدام سلسلة من أفضل الممارسات لكيفية البحث بشكل موثوق والتفاعل مع المكونات بناءً على السمات والشروط غير الديناميكية.ثم يوفر اختصارات للقيام بذلك مع جميع مكونات HTML وExt JS وSencha Touch التي قد تحتاج إلى التفاعل معها.يأتي بنكهتين:

  1. جافا - واجهة برمجة التطبيقات المألوفة المستندة إلى السيلينيوم وJUnit والتي تتضمن دعمًا مدمجًا لبرنامج تشغيل الويب لجميع المتصفحات الحديثة.
  2. جوين - لغة ذات طابع إنساني لإنشاء اختبارات المتصفح وصيانتها بسرعة وسهولة، والتي تأتي مع بيئة التطوير المتكاملة الخاصة بها.وكل ذلك يعتمد على Java API.

على سبيل المثال، إذا كنت تريد العثور على صف شبكة Ext JS الذي يحتوي على النص "Foo"، فيمكنك القيام بما يلي في Java:

findExtJsGridRow("Foo");

... ويمكنك القيام بما يلي في جوين:

extjsgridrow by text "Foo"

هناك الكثير من الوثائق لكليهما جافا وجوين لكيفية العمل مع المكونات المحددة لـ Ext JS.توضح الوثائق أيضًا تفاصيل HTML الناتج لجميع مكونات Ext JS، والتي قد تجدها مفيدة أيضًا.

نصائح مفيدة لجلب الشبكة عبر معرف الشبكة على الصفحة:أعتقد أنه يمكنك توسيع وظيفة أكثر فائدة من واجهة برمجة التطبيقات هذه.

   sub get_grid_row {
        my ($browser, $grid, $row)  = @_;


        my $script = "var doc = this.browserbot.getCurrentWindow().document;\n" .
            "var grid = doc.getElementById('$grid');\n" .
            "var table = grid.getElementsByTagName('table');\n" .
            "var result = '';\n" .
            "var row = 0;\n" . 
            "for (var i = 0; i < table.length; i++) {\n" .
            "   if (table[i].className == 'x-grid3-row-table') {\n".
            "       row++;\n" . 
            "       if (row == $row) {\n" .
            "           var cols_len = table[i].rows[0].cells.length;\n" .
            "           for (var j = 0; j < cols_len; j++) {\n" .
            "               var cell = table[i].rows[0].cells[j];\n" .
            "               if (result.length == 0) {\n" .
            "                   result = getText(cell);\n" .
            "               } else { \n" .
            "                   result += '|' + getText(cell);\n" .
            "               }\n" .
            "           }\n" .
            "       }\n" .
            "   }\n" .
            "}\n" .
            "result;\n";

        my $result = $browser->get_eval($script);
        my @res = split('\|', $result);
        return @res;
    }

اختبار أسهل من خلال سمات بيانات HTML المخصصة

من وثائق سينشا:

يمكن استخدام itemId كطريقة بديلة للحصول على مرجع لمكون عند عدم توفر مرجع كائن.بدلاً من استخدام معرف مع Ext.getCmp، استخدم itemId مع Ext.container.Container.getComponent الذي سيقوم باسترداد معرف العنصر أو المعرف.نظرًا لأن itemId عبارة عن فهرس لـ MixedCollection الداخلي للحاوية، فسيتم تحديد نطاق itemId محليًا للحاوية - لتجنب التعارضات المحتملة مع Ext.ComponentManager الذي يتطلب معرفًا فريدًا.

تجاوز Ext.AbstractComponentonBoxReady الطريقة، قمت بتعيين سمة بيانات مخصصة (التي يأتي اسمها من custom testIdAttr خاصية كل مكون) إلى المكون itemId القيمة إذا كانت موجودة.أضف ال Testing.overrides.AbstractComponent فئة لك application.js الملف requires مجموعة مصفوفة.

/**
 * Overrides the Ext.AbstracComponent's onBoxReady
 * method to add custom data attributes to the
 * component's dom structure.
 *
 * @author Brian Wendt
 */
Ext.define('Testing.overrides.AbstractComponent', {
  override: 'Ext.AbstractComponent',


  onBoxReady: function () {
    var me = this,
      el = me.getEl();


    if (el && el.dom && me.itemId) {
      el.dom.setAttribute(me.testIdAttr || 'data-selenium-id', me.itemId);
    }


    me.callOverridden(arguments);
  }
});

توفر هذه الطريقة للمطورين طريقة لإعادة استخدام المعرف الوصفي داخل التعليمات البرمجية الخاصة بهم وإتاحة هذه المعرفات في كل مرة يتم فيها عرض الصفحة.لا مزيد من البحث من خلال المعرفات غير الوصفية التي تم إنشاؤها ديناميكيًا.

نحن نعمل على تطوير إطار اختبار يستخدم السيلينيوم وواجهنا مشكلات مع extjs (نظرًا لأنه يتم عرضه من جانب العميل).أجد أنه من المفيد البحث عن عنصر بمجرد أن يصبح DOM جاهزًا.

public static boolean waitUntilDOMIsReady(WebDriver driver) {
    def maxSeconds = DEFAULT_WAIT_SECONDS * 10
    for (count in 1..maxSeconds) {
        Thread.sleep(100)
        def ready = isDOMReady(driver);
        if (ready) {
            break;
        }
    }
}

public static boolean isDOMReady(WebDriver driver){
    return driver.executeScript("return document.readyState");
}

بالنسبة لواجهة المستخدم المعقدة التي ليست HTML رسمية، يعد xPath دائمًا شيئًا يمكنك الاعتماد عليه، ولكنه معقد بعض الشيء عندما يتعلق الأمر بتنفيذ واجهة مستخدم مختلفة باستخدام ExtJs.

يمكنك استخدام Firebug وFirexpath كامتدادات لمتصفح فايرفوكس لاختبار xpath لعنصر معين، وتمرير xpath الكامل كمعلمة للسيلينيوم.

على سبيل المثال في كود جافا:

String fullXpath = "xpath=//div[@id='mainDiv']//div[contains(@class,'x-grid-row')]//table/tbody/tr[1]/td[1]//button"

selenium.click(fullXpath);

عندما كنت أختبر تطبيق ExtJS باستخدام WebDriver استخدمت الطريقة التالية:لقد بحثت عن الحقل حسب نص الملصق وحصلت عليه @for السمة من التسمية.على سبيل المثال، لدينا تسمية

<label id="dynamic_id_label" class="TextboxLabel" for="textField_which_I_am_lloking_for">
Name Of Needed Label
<label/>

ونحن بحاجة إلى توجيه WebDriver بعض المدخلات: //input[@id=(//label[contains(text(),'Name Of Needed Label')]/@for)].

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top