تنفيذ clojure النقي لبروتوكول WebDriver. استخدم تلك المكتبة لأتمتة المتصفح ، واختبار سلوك الواجهة الأمامية ، أو محاكاة الإجراءات البشرية أو أي شيء تريده.
تم تسميته على اسم Etaoin Shrdlu - وهي آلة كتابة أصبحت على قيد الحياة بعد أن تم إنتاج مذكرة أسرار عليها.
منذ 0.4.0
، يكون مثيل السائق خريطة ولكن ليس ذرة مثلها كانت. لقد كان حلاً صعبًا لاتخاذ قرار بشأنه ، ومع ذلك ، فقد تخلصنا من Atom لمتابعة طريقة Clojure في الكود الخاص بنا. بشكل عام ، أنت لا تضع سائقًا أو تخزين شيئًا بداخله. جميع الوظائف الداخلية المستخدمة لتعديل المثيل الآن فقط إرجاع إصدار جديد من الخريطة. إذا كان لديك swap!
أو شيء مشابه في الكود الخاص بك للسائق ، يرجى إعادة تشكيل الرمز الخاص بك قبل التحديث.
منذ 0.4.0
، تدعم المكتبة إجراءات WebDriver. الإجراءات هي الأوامر المرسلة إلى السائق في دفعة. انظر القسم المفصل ذي الصلة في TOC.
منذ 0.4.0
، يمكن لـ etaoin تشغيل ملفات البرنامج النصي التي تم إنشاؤها في IDE Selenium IDE التفاعلية. انظر القسم ذي الصلة أدناه.
Ct t
كالمعتاد.اهلا وسهلا بكم لتقديم شركتك في تلك القائمة.
هناك خطوتان للتثبيت:
etaoin
في رمز clojure الخاص بك أضف ما يلي إلى :dependencies
في ملف project.clj
:
[etaoin "0.4.6"]
يعمل مع Clojure 1.9 وما فوق.
توفر هذه الصفحة إرشادات حول كيفية تثبيت برامج التشغيل التي تحتاجها لأتمتة متصفحك.
قم بتثبيت متصفحات Chrome و Firefox لتنزيلها من المواقع الرسمية. لن تكون هناك مشكلة في جميع المنصات.
تثبيت برامج تشغيل محددة تحتاجها:
برنامج تشغيل Google Chrome:
brew cask install chromedriver
لمستخدمي Mac2.28
على الأقل. 2.27
وأسفل له خطأ يتعلق بزيادة النافذة (انظر [استكشاف الأخطاء وإصلاحها]]).Geckodriver ، سائق لـ Firefox:
brew install geckodriver
لمستخدمي Macمتصفح Phantom.js:
brew install phantomjs
لمستخدمي Macسائق Safari (لنظام التشغيل Mac فقط):
الآن ، تحقق من التثبيت الذي يطلق أيًا من هذه الأوامر. لكل أمر ، يجب أن تبدأ عملية لا نهاية لها مع خادم HTTP المحلي.
chromedriver
geckodriver
phantomjs --wd
safaridriver -p 0
يمكنك إجراء اختبارات لإطلاق هذه المكتبة:
lein test
سترى نوافذ المتصفح مفتوحة وتغلق في السلسلة. تستخدم الاختبارات ملف HTML محلي مع تصميم خاص للتحقق من صحة معظم الحالات.
انظر أدناه للحصول على قسم استكشاف الأخطاء وإصلاحها إذا كان لديك مشاكل
الأخبار الجيدة التي قد تقوم بأتمتة متصفحك مباشرة من الاستبدال:
( use 'etaoin.api)
( require '[etaoin.keys :as k])
( def driver ( firefox )) ; ; here, a Firefox window should appear
; ; let's perform a quick Wiki session
( go driver " https://en.wikipedia.org/ " )
( wait-visible driver [{ :id :simpleSearch } { :tag :input :name :search }])
; ; search for something
( fill driver { :tag :input :name :search } " Clojure programming language " )
( fill driver { :tag :input :name :search } k/enter)
( wait-visible driver { :class :mw-search-results })
; ; select an `option` in select-box by visible text
; ; <select id="country">
; ; <option value="rf">Russia</option>
; ; <option value="usa">United States</option>
; ; <option value="uk">United Kingdom</option>
; ; <option value="fr">France</option>
; ;</select>
( select driver :country " France " )
( get-element-value driver :country )
; ;=> "fr"
; ; I'm sure the first link is what I was looking for
( click driver [{ :class :mw-search-results } { :class :mw-search-result-heading } { :tag :a }])
( wait-visible driver { :id :firstHeading })
; ; let's ensure
( get-url driver) ; ; "https://en.wikipedia.org/wiki/Clojure"
( get-title driver) ; ; "Clojure - Wikipedia"
( has-text? driver " Clojure " ) ; ; true
; ; navigate on history
( back driver)
( forward driver)
( refresh driver)
( get-title driver) ; ; "Clojure - Wikipedia"
; ; stops Firefox and HTTP server
( quit driver)
كما ترى ، تتطلب أي وظيفة مثيل برنامج تشغيل كوسيطة أولى. لذلك يمكنك تبسيطه باستخدام وحدات الماكرو doto
:
( def driver ( firefox ))
( doto driver
( go " https://en.wikipedia.org/ " )
( wait-visible [{ :id :simpleSearch } { :tag :input :name :search }])
; ; ...
( fill { :tag :input :name :search } k/enter)
( wait-visible { :class :mw-search-results })
( click :some-button )
; ; ...
( wait-visible { :id :firstHeading })
; ; ...
( quit ))
في هذه الحالة ، يبدو رمزك وكأنه DSL مصمم فقط لمثل هذه الأغراض.
يمكنك استخدام fill-multi
لتقصير الرمز مثل:
( fill driver :login " login " )
( fill driver :password " pass " )
( fill driver :textarea " some text " )
داخل
( fill-multi driver { :login " login "
:password " pass "
:textarea " some text " })
في حالة حدوث أي استثناء أثناء جلسة المتصفح ، قد تتدلى العملية الخارجية إلى الأبد حتى تقتلها يدويًا. لمنع ذلك ، استخدم with-<browser>
وحدات الماكرو على النحو التالي:
( with-firefox {} ff ; ; additional options first, then bind name
( doto ff
( go " https://google.com " )
...))
مهما حدث خلال الجلسة ، سيتم إيقاف العملية على أي حال.
تتطلب معظم الوظائف مثل click
fill
، وما إلى ذلك ، مصطلح استعلام لاكتشاف عنصر على الصفحة. على سبيل المثال:
( click driver { :tag :button })
( fill driver { :id " searchInput " } " Clojure " )
تدعم المكتبة أنواع الاستعلام والقيم التالية.
:active
يرمز إلى العنصر النشط الحالي. عند فتح صفحة Google على سبيل المثال ، يركز المؤشر على إدخال البحث الرئيسي. لذلك ليست هناك حاجة للنقر عليها يدويًا. مثال:
( fill driver :active " Let's search for something " keys/enter)
أي كلمة رئيسية أخرى تشير إلى معرف العنصر. بالنسبة إلى صفحة Google ، فهي :lst-ib
أو "lst-ib"
(يتم دعم السلاسل أيضًا). مسائل السجل. مثال:
( fill driver :lst-ib " What is the Matrix? " keys/enter)
سلسلة مع تعبير xPath. كن حذرًا عند كتابتها يدويًا. تحقق من قسم Troubleshooting
أدناه. مثال:
( fill driver " .//input[@id='lst-ib'][@name='q'] " " XPath in action! " keys/enter)
خريطة مع إما :xpath
أو :css
مع تعبير سلسلة من بناء الجملة المقابل. مثال:
( fill driver { :xpath " .//input[@id='lst-ib'] " } " XPath selector " keys/enter)
( fill driver { :css " input#lst-ib[name='q'] " } " CSS selector " keys/enter)
انظر دليل CSS Selector لمزيد من المعلومات.
قد يكون الاستعلام أي خريطة أخرى تمثل تعبير XPath كبيانات. القواعد هي:
:tag
يمثل اسم العلامة. يصبح *
عندما لا يتم تمريره.:index
يتوسع إلى شرط الخلاف [x]
. مفيد عندما تحتاج إلى تحديد صف ثالث من جدول على سبيل المثال.:fn/
الاسم ويتوسع إلى شيء محدد.أمثلة:
ابحث عن علامة div
الأولى
( query driver { :tag :div })
; ; expands into .//div
ابحث عن علامة N-Th div
( query driver { :tag :div :index 1 })
; ; expands into .//div[1]
ابحث عن العلامة a
مع سمة الفئة تساوي active
( query driver { :tag :a :class " active " })
; ; ".//a[@class="active"]"
ابحث عن نموذج بسماته:
( query driver { :tag :form :method :GET :class :message })
; ; expands into .//form[@method="GET"][@class="message"]
ابحث عن زر من نصه (مطابقة بالضبط):
( query driver { :tag :button :fn/text " Press Me " })
; ; .//button[text()="Press Me"]
ابحث عن عنصر nth ( p
، a
، أيا كان) مع نص "تنزيل":
( query driver { :fn/has-text " download " :index 3 })
; ; .//*[contains(text(), "download")][3]
ابحث عن عنصر يحتوي على الفصل التالي:
( query driver { :tag :div :fn/has-class " overlay " })
; ; .//div[contains(@class, "overlay")]
ابحث عن عنصر يحتوي على المجال التالي في HREF:
( query driver { :tag :a :fn/link " google.com " })
; ; .//a[contains(@href, "google.com")]
ابحث عن عنصر يحتوي على الفصول التالية في وقت واحد:
( query driver { :fn/has-classes [ :active :sticky :marked ]})
; ; .//*[contains(@class, "active")][contains(@class, "sticky")][contains(@class, "marked")]
ابحث عن واجهة مستخدم الإدخال الممكّن/المعوقة:
; ; first input
( query driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
( query driver { :tag :input :fn/enabled true })
; ; .//input[@enabled=true()]
; ; all inputs
( query-all driver { :tag :input :fn/disabled true })
; ; .//input[@disabled=true()]
قد يكون الاستعلام متجهًا يتكون من أي تعبيرات مذكورة أعلاه. في مثل هذا الاستعلام ، كل مصطلح يبحث من فترة سابقة بشكل متكرر.
مثال بسيط:
( click driver [{ :tag :html } { :tag :body } { :tag :a }])
يمكنك الجمع بين تعبيرات XPath و CSS أيضًا (انتبه في نقطة رائدة في تعبير XPath:
( click driver [{ :tag :html } { :css " div.class " } " .//a[@class='download'] " ])
قد تحتاج أحيانًا إلى التفاعل مع العنصر التاسع في الاستعلام ، على سبيل المثال عند الرغبة في النقر على الرابط الثاني في هذا المثال:
< ul >
< li class =" search-result " >
< a href =" a " > a </ a >
</ li >
< li class =" search-result " >
< a href =" b " > b </ a >
</ li >
< li class =" search-result " >
< a href =" c " > c </ a >
</ li >
</ ul >
في هذه الحالة ، يمكنك إما استخدام توجيه :index
المدعوم لتعبيرات XPath مثل هذا:
( click driver [{ :tag :li :class :search-result :index 2 } { :tag :a }])
أو يمكنك استخدام خدعة Nth-Child مع تعبير CSS مثل هذا:
( click driver { :css " li.search-result:nth-child(2) a " })
أخيرًا ، من الممكن أيضًا الحصول على العنصر التاسع مباشرة باستخدام query-all
:
( click-el driver ( nth ( query-all driver { :css " li.search-result a " }) 2 ))
لاحظ استخدام click-el
هنا ، حيث يقوم query-all
بإرجاع عنصر ، وليس محددًا يمكن تمريره click
مباشرة.
يأخذ query-tree
المختارين ويتصرف مثل الشجرة. كل عناصر الاستعلام المقبلة من العناصر السابقة. يعتمد محدد القبضة على عناصر الاكتشاف ، والباقي يستخدمون عناصر الاكتشاف من
( query-tree driver { :tag :div } { :tag :a })
وسائل
{:tag :div} -> [div1 div2 div3]
div1 -> [a1 a2 a3]
div2 -> [a4 a5 a6]
div3 -> [a7 a8 a9]
لذلك ستكون النتيجة [A1 ... A9]
للتفاعل مع العناصر الموجودة عبر استعلام ، يتعين عليك تمرير نتيجة الاستعلام إما إلى click-el
أو fill-el
:
( click-el driver ( first ( query-all driver { :tag :a })))
لذلك يمكنك جمع العناصر في ناقل وتتفاعل معها بشكل تعسفي في أي وقت:
( def elements ( query-all driver { :tag :input :type :text })
( fill-el driver ( first elements) " This is a test " )
( fill-el driver ( rand-nth elements) " I like tests! " )
لغرض محاكاة المدخلات البشرية ، يمكنك استخدام وظيفة fill-human
. يتم تمكين الخيارات التالية افتراضيًا:
{ :mistake-prob 0.1 ; ; a real number from 0.1 to 0.9, the higher the number, the more typos will be made
:pause-max 0.2 } ; ; max typing delay in seconds
ويمكنك إعادة تعريفها:
( fill-human driver q text { :mistake-prob 0.5
:pause-max 1 })
; ; or just use default opts by omitting them
( fill-human driver q text)
لمدخلات متعددة مع المحاكاة البشرية ، استخدم fill-human-multi
( fill-human-multi driver { :login " login "
:pass " password "
:textarea " some text " }
{ :mistake-prob 0.5
:pause-max 1 })
تؤدي وظيفة click
إلى تشغيل الماوس الأيسر على عنصر موجود بمصطلح الاستعلام:
( click driver { :tag :button })
تستخدم وظيفة click
فقط العنصر الأول الموجود في الاستعلام ، والذي يؤدي أحيانًا إلى النقر على العناصر الخاطئة. للتأكد من وجود عنصر واحد وعدد واحد فقط ، استخدم وظيفة click-single
. إنه يتصرف بنفس الشيء ولكنه يثير استثناء عند الاستعلام عن الصفحة بإرجاع عناصر متعددة:
( click-single driver { :tag :button :name " search " })
نادراً ما يتم استخدام نقرة مزدوجة على شبكة الإنترنت ، ولكنها ممكنة مع وظيفة double-click
(Chrome ، Phantom.js):
( double-click driver { :tag :dbl-click-btn })
هناك أيضًا مجموعة من وظائف النقر "الأعمى". أنها تؤدي إلى نقرات الماوس على موضع الماوس الحالي:
( left-click driver)
( middle-click driver)
( right-click driver)
حفنة أخرى من الوظائف تفعل الشيء نفسه ولكن نقل مؤشر الماوس إلى عنصر محدد قبل النقر عليها:
( left-click-on driver { :tag :img })
( middle-click-on driver { :tag :img })
( right-click-on driver { :tag :img })
تكون نقرة الماوس الأوسط مفيدة عند فتح رابط في علامة تبويب خلفية جديدة. يتم استخدام النقر بزر الماوس الأيمن في بعض الأحيان لتقليد قائمة السياق في تطبيقات الويب.
تدعم المكتبة إجراءات WebDriver. بشكل عام ، الإجراءات هي أوامر تصف أجهزة الإدخال الظاهري.
{ :actions [{ :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]}
{ :type " pointer "
:id " UUID or some name "
:parameters { :pointerType " mouse " }
:actions [{ :type " pointerMove " :origin " pointer " :x 396 :y 323 }
; ; double click
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }
{ :type " pointerDown " :duration 0 :button 0 }
{ :type " pointerUp " :duration 0 :button 0 }]}]}
يمكنك إنشاء خريطة يدويًا وإرسالها إلى طريقة perform-actions
:
( def keyboard-input { :type " key "
:id " some name "
:actions [{ :type " keyDown " :value cmd}
{ :type " keyDown " :value " a " }
{ :type " keyUp " :value " a " }
{ :type " keyUp " :value cmd}
{ :type " pause " :duration 100 }]})
( perform-actions driver keyboard-input)
أو استخدام الأغلفة. تحتاج أولاً إلى إنشاء أجهزة إدخال افتراضية ، على سبيل المثال:
( def keyboard ( make-key-input ))
ثم املأه بالإجراءات اللازمة:
( -> keyboard
( add-key-down keys/shift-left)
( add-key-down " a " )
( add-key-up " a " )
( add-key-up keys/shift-left))
مثال موسع:
( let [driver ( chrome )
_ ( go driver " https://google.com " )
search-box ( query driver { :name :q })
mouse ( -> ( make-mouse-input )
( add-pointer-click-el search-box))
keyboard ( -> ( make-key-input )
add-pause
( with-key-down keys/shift-left
( add-key-press " e " ))
( add-key-press " t " )
( add-key-press " a " )
( add-key-press " o " )
( add-key-press " i " )
( add-key-press " n " )
( add-key-press keys/enter))]
( perform-actions driver keyboard mouse)
( quit driver))
لمسح حالة أجهزة الإدخال الافتراضية ، حرر جميع المفاتيح المضغوطة وما إلى ذلك ، استخدم طريقة release-actions
:
( release-actions driver)
يؤدي النقر فوق زر إدخال الملف إلى فتح مربع حوار خاص بنظام التشغيل الذي لا يُسمح لك بالتفاعل معه باستخدام بروتوكول WebDriver. استخدم وظيفة upload-file
لإرفاق ملف محلي بعامل إدخال الملف. تأخذ الوظيفة محددًا يشير إلى إدخال الملف وإما مسار كامل كسلسلة أو مثيل java.io.File
الأصلي. يجب أن يكون الملف موجودًا أو ستحصل على استثناء خلاف ذلك. مثال الاستخدام:
( def driver ( chrome ))
; ; open a web page that serves uploaded files
( go driver " http://nervgh.github.io/pages/angular-file-upload/examples/simple/ " )
; ; bound selector to variable; you may also specify an id, class, etc
( def input { :tag :input :type :file })
; ; upload an image with the first one file input
( def my-file " /Users/ivan/Downloads/sample.png " )
( upload-file driver input my-file)
; ; or pass a native Java object:
( require '[clojure.java.io :as io])
( def my-file ( io/file " /Users/ivan/Downloads/sample.png " ))
( upload-file driver input my-file)
استدعاء دالة screenshot
يتفصل الصفحة الحالية إلى صورة PNG على القرص الخاص بك:
( screenshot driver " page.png " ) ; ; relative path
( screenshot driver " /Users/ivan/page.png " ) ; ; absolute path
يتم دعم كائن ملف Java الأصلي أيضًا:
; ; when imported as `[clojure.java.io :as io]`
( screenshot driver ( io/file " test.png " ))
; ; native object
( screenshot driver ( java.io.File. " test-native.png " ))
مع Firefox و Chrome ، لا يمكنك التقاط الصفحة بأكملها بل عنصر واحد ، على سبيل المثال Div ، عنصر واجهة مستخدم أو أي شيء آخر. لا يعمل مع متصفحات أخرى في الوقت الحالي. مثال:
( screenshot-element driver { :tag :div :class :smart-widget } " smart_widget.png " )
مع الماكرو with-screenshots
، يمكنك عمل لقطة شاشة بعد كل نموذج
( with-screenshots driver " ../screenshots "
( fill driver :simple-input " 1 " )
( fill driver :simple-input " 2 " )
( fill driver :simple-input " 3 " ))
ما يعادل السجل:
( fill driver :simple-input " 1 " )
( screenshot driver " ../screenshots/chrome-...123.png " )
( fill driver :simple-input " 2 " )
( screenshot driver " ../screenshots/chrome-...124.png " )
( fill driver :simple-input " 3 " )
( screenshot driver " ../screenshots/chrome-...125.png " )
في الآونة الأخيرة ، بدأت Google Chrome و Firefox لاحقًا في دعم ميزة تسمى وضع Headless. عندما تكون مقطوعة الرأس ، لا يحدث أي من نوافذ واجهة المستخدم على الشاشة ، فقط ينتقل إخراج stdout إلى وحدة التحكم. تتيح لك هذه الميزة تشغيل اختبارات التكامل على الخوادم التي لا تحتوي على جهاز إخراج رسومي.
تأكد من دعم المستعرض الخاص بك عن وضع مقطوعة الرأس من خلال التحقق مما إذا كان يقبل -وسيطة سطر الأوامر --headles
عند تشغيله من المحطة. Phantom.js Driver لا يتجاوز طبيعته (لم يتم تطويره مطلقًا لتقديم واجهة المستخدم).
عند بدء تشغيل برنامج تشغيل ، مرر :headless
للتبديل إلى وضع مقطوع الرأس. ملاحظة ، يتم دعم أحدث إصدار من Chrome و Firefox. بالنسبة للسائقين الآخرين ، سيتم تجاهل العلم.
( def driver ( chrome { :headless true })) ; ; runs headless Chrome
أو
( def driver ( firefox { :headless true })) ; ; runs headless Firefox
للتحقق من أي برنامج تشغيل تم تشغيله في وضع مقطوع الرأس ، استخدم headless?
المسند:
( headless? driver) ; ; true
ملاحظة ، ستعود دائمًا إلى حالات phantom.js.
هناك العديد من الاختصارات لتشغيل Chrome أو Firefox في وضع مقطوع الرأس افتراضيًا:
( def driver ( chrome-headless ))
; ; or
( def driver ( firefox-headless {...})) ; ; with extra settings
; ; or
( with-chrome-headless nil driver
( go driver " http://example.com " ))
( with-firefox-headless {...} driver ; ; extra settings
( go driver " http://example.com " ))
هناك أيضًا when-headless
when-not-headless
لا تسمح بأداء مجموعة من الأوامر فقط إذا كان المتصفح في وضع مقطوع الرأس أو ليس على التوالي:
( with-chrome nil driver
...
( when-not-headless driver
... some actions that might be not available in headless mode)
... common actions for both versions)
للاتصال بسائق يعمل بالفعل على مضيف محلي أو عن بُعد ، يجب عليك تحديد المعلمة :host
قد يكون إما اسم مضيف (LocalHost ، some.remote.host.net) أو عنوان IP (127.0.0.1 ، 183.102.156.31 ) و :port
. إذا لم يتم تحديد المنفذ ، يتم تعيين المنفذ الافتراضي.
مثال:
; ; Chrome
( def driver ( chrome { :host " 127.0.0.1 " :port 9515 })) ; ; for connection to driver on localhost on port 9515
; ; Firefox
( def driver ( firefox { :host " 192.168.1.11 " })) ; ; the default port for firefox is 4444
للعمل مع السائق في Docker ، يمكنك التقاط صور جاهزة:
مثال على الكروم:
docker run --name chromedriver -p 9515:4444 -d -e CHROMEDRIVER_WHITELISTED_IPS='' robcherry/docker-chromedriver:latest
ل Firefox:
docker run --name geckodriver -p 4444:4444 -d instrumentisto/geckodriver
للاتصال بالسائق ، تحتاج فقط إلى تحديد المعلمة :host
كضوء localhost
أو 127.0.0.1
و :port
الذي يعمل عليه. إذا لم يتم تحديد المنفذ ، يتم تعيين المنفذ الافتراضي.
( def driver ( chrome-headless { :host " localhost " :port 9515 :args [ " --no-sandbox " ]}))
( def driver ( firefox-headless { :host " localhost " })) ; ; will try to connect to port 4444
لتعيين إعدادات الوكيل ، استخدم متغيرات البيئة HTTP_PROXY
/ HTTPS_PROXY
أو تمرير خريطة من النوع التالي:
{ :proxy { :http " some.proxy.com:8080 "
:ftp " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 "
:socks { :host " myproxy:1080 " :version 5 }
:bypass [ " http://this.url " " http://that.url " ]
:pac-url " localhost:8888 " }}
; ; example
( chrome { :proxy { :http " some.proxy.com:8080 "
:ssl " some.proxy.com:8080 " }})
ملاحظة: A: PAC-url لملف التكوين التلقائي الوكيل. تستخدم مع Safari لأن خيارات الوكيل الأخرى لا تعمل في هذا المتصفح.
لضبط الوكيل ، يمكنك استخدام الكائن الأصلي وتمريره إلى القدرات:
{ :capabilities { :proxy { :proxyType " manual "
:proxyAutoconfigUrl " some.proxy.com:8080 "
:ftpProxy " some.proxy.com:8080 "
:httpProxy " some.proxy.com:8080 "
:noProxy [ " http://this.url " " http://that.url " ]
:sslProxy " some.proxy.com:8080 "
:socksProxy " some.proxy.com:1080 "
:socksVersion 5 }}}
( chrome { :capabilities { :proxy {...}}})
مع التحديثات الأخيرة ، تجلب المكتبة ميزة رائعة. الآن يمكنك تتبع الأحداث التي تأتي من لوحة DevTools. هذا يعني أن كل ما تراه في وحدة التحكم المطور متاح الآن من خلال API. هذا يعمل فقط مع Google Chrome الآن.
لبدء تشغيل برنامج تشغيل مع إعدادات تطوير خاصة محددة ، ما عليك سوى تمرير خريطة فارغة إلى حقل :dev
عند تشغيل برنامج تشغيل:
( def c ( chrome { :dev {}}))
يجب ألا تكون القيمة nil
. عندما تكون خريطة فارغة ، فإن وظيفة خاصة تأخذ الإعدادات الافتراضية. فيما يلي نسخة كاملة من إعدادات DEV مع جميع القيم المحتملة المحددة.
( def c ( chrome { :dev
{ :perf
{ :level :all
:network? true
:page? true
:interval 1000
:categories [ :devtools
:devtools.network
:devtools.timeline ]}}}))
تحت غطاء محرك السيارة ، يملأ قاموسًا خاصًا perfLoggingPrefs
داخل كائن chromeOptions
.
الآن بعد أن يتراكم متصفحك هذه الأحداث ، يمكنك قراءتها باستخدام مساحة اسم dev
خاصة.
( go c " http://google.com " )
; ; wait until the page gets loaded
; ; load the namespace
( require '[etaoin.dev :as dev])
دعنا نحصل على قائمة بجميع طلبات HTTP التي حدثت أثناء الصفحة التي تم تحميلها.
( def reqs ( dev/get-requests c))
; ; reqs is a vector of maps
( count reqs)
; ; 19
; ; what were their types?
( set ( map :type reqs))
; ; #{:script :other :document :image :xhr}
; ; we've got Js requests, images, AJAX and other stuff
; ; check the last one request, it's an image named tia.png
( -> reqs last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000052292.8 " ,
:type :image ,
:xhr? false ,
:url " https://www.gstatic.com/inputtools/images/tia.png " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " image/png " ,
:remote-ip " 173.194.73.94 " },
:done? true }
نظرًا لأننا مهتمون في الغالب بطلبات Ajax ، فهناك وظيفة get-ajax
تقوم بنفس الشيء ولكن طلبات XHR تصفية:
( -> c dev/get-ajax last clojure.pprint/pprint)
{ :state 4 ,
:id " 1000051989.41 " ,
:type :xhr ,
:xhr? true ,
:url
" https://www.google.com/complete/search?q=clojure%20spec&cp=12&client=psy-ab&xssi=t&gs_ri=gws-wiz&hl=ru&authuser=0&psi=4iUbXdapJsbmrgTVt7H4BA.1562060259137&ei=4iUbXdapJsbmrgTVt7H4BA " ,
:with-data? nil,
:request
{ :method :get ,
:headers
{ :Referer " https://www.google.com/ " ,
:User-Agent
" Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36 " }},
:response
{ :status 200 ,
:headers {}, ; ; truncated
:mime " application/json " ,
:remote-ip " 74.125.131.99 " },
:done? true }
نمط نموذجي لاستخدام get-ajax
هو ما يلي. ترغب في التحقق مما إذا كان قد تم إطلاق طلب معين على الخادم. لذلك تضغط على زر ، وانتظر لفترة من الوقت ثم اقرأ الطلبات المقدمة من متصفحك.
وجود قائمة بالطلبات ، يمكنك البحث عن تلك التي تحتاجها (مثل عنوان URL) ثم تحقق من حالتها. :state
حصلت على نفس الدلالات مثل XMLHttpRequest.readyState
. إنه عدد صحيح من 1 إلى 4 مع نفس السلوك.
للتحقق مما إذا كان قد تم الانتهاء من طلب أو فشله أو فشله ، استخدم هذه المتنبئين:
( def req ( last reqs))
( dev/request-done? req)
; ; true
( dev/request-failed? req)
; ; false
( dev/request-success? req)
; ; true
لاحظ هذا request-done?
لا يعني أن الطلب قد نجح. وهذا يعني فقط أن خط الأنابيب قد وصل إلى خطوة أخيرة.
تحذير: عندما تقرأ سجلات DEV ، فإنك تستهلكها من مخزن مؤقت داخلي يتم مسحه. ستعيد المكالمة الثانية get-requests
أو get-ajax
قائمة فارغة.
ربما تريد جمع هذه السجلات بمفردك. تُرجع دالة dev/get-performance-logs
قائمة من السجلات بحيث تتراكم عليها في ذرة أو أي شيء:
( def logs ( atom []))
; ; repeat that form from time to time
( do ( swap! logs concat ( dev/get-performance-logs c))
true )
( count @logs)
; ; 76
هناك logs->requests
logs->ajax
التي تقوم بتحويل السجلات إلى طلبات. على عكس get-requests
get-ajax
، فهي وظائف نقية ولن تغسل أي شيء.
( dev/logs->requests @logs)
عند العمل مع السجلات والطلبات ، انتبه إلى عددهم وحجمهم. تحتوي الخرائط على الكثير من المفاتيح وقد تكون كمية العناصر في المجموعات ضخمة. طباعة مجموعة كاملة من الأحداث قد تجمد محررك. فكر في استخدام وظيفة clojure.pprint/pprint
لأنها تعتمد على حدود الحد الأقصى والطول.
في بعض الأحيان ، قد يكون من الصعب اكتشاف الخطأ الذي حدث خلال جلسة اختبارات واجهة المستخدم الأخيرة. ماكرو خاص with-postmortem
يحفظ بعض البيانات المفيدة على القرص قبل تشغيل الاستثناء. هذه البيانات عبارة عن لقطة شاشة ورمز HTML وسجلات وحدة التحكم JS. ملاحظة: لا تدعم جميع المتصفحات الحصول على سجلات JS.
مثال:
( def driver ( chrome ))
( with-postmortem driver { :dir " /Users/ivan/artifacts " }
( click driver :non-existing-element ))
سيرتفع استثناء ، ولكن في /Users/ivan/artifacts
سيكون هناك ثلاثة ملفات سميتها قالب <browser>-<host>-<port>-<datetime>.<ext>
:
firefox-127.0.0.1-4444-2017-03-26-02-45-07.png
: لقطة شاشة فعلية لصفحة المتصفح ؛firefox-127.0.0.1-4444-2017-03-26-02-45-07.html
: محتوى HTML للمتصفح الحالي ؛firefox-127.0.0.1-4444-2017-03-26-02-45-07.json
: ملف JSON مع سجلات وحدة التحكم ؛ تلك هي ناقل الكائنات.يأخذ المعالج خريطة للخيارات مع المفاتيح التالية. كل منهم قد يكون غائبا.
{ ; ; default directory where to store artifacts
; ; might not exist, will be created otherwise. pwd is used when not passed
:dir " /home/ivan/UI-tests "
; ; a directory where to store screenshots; :dir is used when not passed
:dir-img " /home/ivan/UI-tests/screenshots "
; ; the same but for HTML sources
:dir-src " /home/ivan/UI-tests/HTML "
; ; the same but for console logs
:dir-log " /home/ivan/UI-tests/console "
; ; a string template to format a date; See SimpleDateFormat Java class
:date-format " yyyy-MM-dd-HH-mm-ss " }
تُرجع الوظيفة (get-logs driver)
سجلات المتصفح كمتجه للخرائط. كل خريطة لها الهيكل التالي:
{ :level :warning ,
:message " 1,2,3,4 anonymous (:1) " ,
:timestamp 1511449388366 ,
:source nil,
:datetime #inst " 2017-11-23T15:03:08.366-00:00 " }
حاليًا ، تتوفر سجلات في Chrome و Phantom.js فقط. يرجى ملاحظة أن نص الرسالة ونوع المصدر يعتمدان بشدة على المتصفح. يقوم Chrome بمسح السجلات بمجرد قراءتها. يحتفظهم Phantom.js ولكن فقط حتى تقوم بتغيير الصفحة.
عند تشغيل مثيل برنامج التشغيل ، قد يتم تمرير خريطة للمعلمات الإضافية لتعديل سلوك المتصفح:
( def driver ( chrome { :path " /path/to/driver/binary " }))
أدناه ، إليك خريطة للمعلمات دعم المكتبة. قد يتم تخطي كل منهم أو لديهم قيم لا شيء. بعضها ، إن لم يتم تمريره ، مأخوذ من خريطة defaults
.
{ ; ; Host and port for webdriver's process. Both are taken from defaults
; ; when are not passed. If you pass a port that has been already taken,
; ; the library will try to take a random one instead.
:host " 127.0.0.1 "
:port 9999
; ; Path to webdriver's binary file. Taken from defaults when not passed.
:path-driver " /Users/ivan/Downloads/geckodriver "
; ; Path to the driver's binary file. When not passed, the driver discovers it
; ; by its own.
:path-browser " /Users/ivan/Downloads/firefox/firefox "
; ; Extra command line arguments sent to the browser's process. See your browser's
; ; supported flags.
:args [ " --incognito " " --app " " http://example.com " ]
; ; Extra command line arguments sent to the webdriver's process.
:args-driver [ " -b " " /path/to/firefox/binary " ]
; ; Sets browser's minimal logging level. Only messages with level above
; ; that one will be collected. Useful for fetching Javascript logs. Possible
; ; values are: nil (aliases :off, :none), :debug, :info, :warn (alias :warning),
; ; :err (aliases :error, :severe, :crit, :critical), :all. When not passed,
; ; :all is set.
:log-level :err ; ; to show only errors but not debug
; ; Sets driver's log level.
; ; The value is a string. Possible values are:
; ; chrome: [ALL, DEBUG, INFO, WARNING, SEVERE, OFF]
; ; phantomjs: [ERROR, WARN, INFO, DEBUG] (default INFO)
; ; firefox [fatal, error, warn, info, config, debug, trace]
:driver-log-level
; ; Paths to the driver's log files as strings.
; ; When not set, the output goes to /dev/null (or NUL on Windows)
:log-stdout
:log-stderr
; ; Path to a custorm browser profile. See the section below.
:profile " /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test "
; ; Env variables sent to the driver's process.
:env { :MOZ_CRASHREPORTER_URL " http://test.com " }
; ; Initial window size.
:size [ 1024 680 ]
; ; Default URL to open. Works only in FF for now.
:url " http://example.com "
; ; Override the default User-Agent. Useful for headless mode.
:user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) "
; ; Where to download files.
:download-dir " /Users/ivan/Desktop "
; ; Driver-specific options. Make sure you have read the docs before setting them.
:capabilities { :chromeOptions { :args [ " --headless " ]}}}
عند الانتقال إلى صفحة معينة ، ينتظر برنامج التشغيل حتى يتم تحميل الصفحة بأكملها بالكامل. ما هو جيد في معظم الحالات ، لكن لا يعكس الطريقة التي يتفاعل بها البشر مع الإنترنت.
قم بتغيير هذا السلوك الافتراضي مع خيار :load-strategy
. هناك ثلاث قيم محتملة لذلك:: :none
، :eager
و :normal
وهو الافتراضي عند عدم تمريره.
عندما تمر :none
، يستجيب السائق على الفور حتى نرحب بك لتنفيذ التعليمات التالية. على سبيل المثال:
( def c ( chrome ))
( go c " http://some.slow.site.com " )
; ; you'll hang on this line until the page loads
( do-something )
الآن عند تمرير خيار استراتيجية التحميل:
( def c ( chrome { :load-strategy :none }))
( go c " http://some.slow.site.com " )
; ; no pause, acts immediately
( do-something )
لخيار :eager
، فهو يعمل فقط مع Firefox في لحظة إضافة الميزة إلى المكتبة.
هناك خيار لإدخال سلسلة من المفاتيح في وقت واحد. هذا مفيد لتقليد عقد مفتاح النظام مثل التحكم أو التحول أو أي شيء عند الكتابة.
يحمل مساحة الاسم etaoin.keys
مجموعة من الثوابت الرئيسية بالإضافة إلى مجموعة من الوظائف المتعلقة بالإدخال.
( require '[etaoin.keys :as keys])
مثال سريع على إدخال الأحرف العادية التي تحمل التحول:
( def c ( chrome ))
( go c " http://google.com " )
( fill-active c ( keys/with-shift " caps is great " ))
يتم ملء المدخلات الرئيسية مع "Caps رائع". الآن ترغب في حذف الكلمة الأخيرة. في Chrome ، يتم ذلك عن طريق الضغط على Space Holding ALT. دعونا نفعل ذلك:
( fill-active c ( keys/with-alt keys/backspace))
الآن لديك فقط "Caps IS" في المدخلات.
فكر في مثال أكثر تعقيدًا يكرر سلوك المستخدمين الحقيقيين. كنت ترغب في حذف كل شيء من المدخلات. أولاً ، تقوم بنقل الكاريت في البداية. ثم انقله إلى النهاية التي تحملها حتى يتم اختيار كل شيء. أخيرًا ، تضغط على DELETE لمسح النص المحدد.
التحرير والسرد هو:
( fill-active c keys/home ( keys/with-shift keys/end) keys/delete)
هناك أيضًا وظائف with-ctrl
و with-command
التي تتصرف كما هي.
انتبه ، هذه الوظائف لا تنطبق على اختصارات المتصفح العالمي. على سبيل المثال ، لا "أمر + r" ولا "command + t" إعادة تحميل الصفحة أو افتح علامة تبويب جديدة.
جميع وظائف keys/with-*
هي مجرد مغلفات على وظيفة keys/chord
التي يمكن استخدامها للحالات المعقدة.
لتحديد الدليل الخاص بك حيث يجب تنزيل الملفات ، تمرير :download-dir
في خريطة خيار عند تشغيل برنامج تشغيل:
( def driver ( chrome { :download-dir " /Users/ivan/Desktop " }))
الآن ، بمجرد النقر على رابط ، يجب وضع ملف في هذا المجلد. حاليًا ، يتم دعم Chrome و Firefox فقط.
يتطلب Firefox تحديد أنواع MIME لتلك الملفات التي يجب تنزيلها دون عرض مربع حوار النظام. بشكل افتراضي ، عند تمرير المعلمة :download-dir
، تضيف المكتبة أكثر أنواع MIME شيوعًا: المحفوظات ، ملفات الوسائط ، مستندات المكتب ، إلخ. إذا كنت بحاجة إلى إضافة واحدة خاصة بك ، فتجاوز هذا التفضيل يدويًا:
( def driver ( firefox { :download-dir " /Users/ivan/Desktop "
:prefs { :browser.helperApps.neverAsk.saveToDisk
" some-mime/type-1;other-mime/type-2 " }}))
للتحقق مما إذا كان تم تنزيل ملف أثناء اختبارات واجهة المستخدم ، راجع قسم الاختبار أدناه.
قم بتعيين رأس مستخدم مخصص مع خيار :user-agent
عند إنشاء برنامج تشغيل ، على سبيل المثال:
( def f ( firefox { :user-agent " Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) " }))
للحصول على القيمة الحالية للرأس في وقت التشغيل ، استخدم الوظيفة:
( get-user-agent f)
; ; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
يعد إعداد هذا الرأس أمرًا مهمًا للغاية بالنسبة للمتصفحات بدون مقطوعة الرأس حيث تحقق معظم المواقع ما إذا كان وكيل المستخدم يتضمن السلسلة "مقطوعة الرأس". هذا يمكن أن يؤدي إلى 403 استجابة أو بعض السلوك الغريب للموقع.
عند تشغيل Chrome أو Firefox ، يمكنك تحديد ملف تعريف خاص لأغراض الاختبار. الملف الشخصي هو مجلد يحتفظ بإعدادات المتصفح والتاريخ والإشارات المرجعية والبيانات الأخرى الخاصة بالمستخدم.
تخيل أنك ترغب في إجراء اختبارات التكامل الخاصة بك ضد المستخدم الذي أوقف تنفيذ JavaScript أو عرض الصور. لإعداد ملف تعريف خاص لهذه المهمة سيكون اختيارًا جيدًا.
chrome://version/
الصفحة. انسخ مسار الملف الموجود أسفل التسمية التوضيحية Profile Path
.-P
أو -p
أو -ProfileManager
كما تصف الصفحة الرسمية.about:support
. بالقرب من التسمية التوضيحية Profile Folder
، اضغط على زر Show in Finder
. يجب أن تظهر نافذة مجلد جديدة. انسخ مسارها من هناك. بمجرد أن تحصل على مسار ملف تعريف ، قم بتشغيل برنامج تشغيل مع مفتاح :profile
خاص على النحو التالي:
; ; Chrome
( def chrome-profile
" /Users/ivan/Library/Application Support/Google/Chrome/Profile 2/Default " )
( def chrm ( chrome { :profile chrome-profile}))
; ; Firefox
( def ff-profile
" /Users/ivan/Library/Application Support/Firefox/Profiles/iy4iitbg.Test " )
( def ff ( firefox { :profile ff-profile}))
تشحن المكتبة مجموعة من الوظائف لتمرير الصفحة.
أهم scroll-query
، يقفز العنصر الأول الموجود مع مصطلح الاستعلام:
( def driver ( chrome ))
; ; the form button placed somewhere below
( scroll-query driver :button-submit )
; ; the main article
( scroll-query driver { :tag :h1 })
للقفز إلى الموقف المطلق ، ما عليك سوى استخدام scroll
على النحو التالي:
( scroll driver 100 600 )
; ; or pass a map with x and y keys
( scroll driver { :x 100 :y 600 })
للتمرير نسبيًا ، استخدم scroll-by
مع قيم الإزاحة:
; ; keeps the same horizontal position, goes up for 100 pixels
( scroll-by driver 0 -100 ) ; ; map parameter is also supported
هناك اختصاران للقفز من أعلى أو أسفل الصفحة:
( scroll-bottom driver) ; ; you'll see the footer...
( scroll-top driver) ; ; ...and the header again
الوظائف التالية قم بتمرير الصفحة في جميع الاتجاهات:
( scroll-down driver 200 ) ; ; scrolls down by 200 pixels
( scroll-down driver) ; ; scrolls down by the default (100) number of pixels
( scroll-up driver 200 ) ; ; the same, but scrolls up...
( scroll-up driver)
( scroll-left driver 200 ) ; ; ...left
( scroll-left driver)
( scroll-right driver 200 ) ; ; ... and right
( scroll-right driver)
ملاحظة واحدة ، في جميع الحالات يتم تقديم إجراءات التمرير مع JavaScript. تأكد من تمكين المستعرض الخاص بك.
أثناء العمل مع الصفحة ، لا يمكنك التفاعل مع تلك العناصر التي يتم وضعها في إطار أو iframe. تعمل الوظائف أدناه على تبديل السياق الحالي على إطار محدد:
( switch-frame driver :frameId ) ; ; now you are inside an iframe with id="frameId"
( click driver :someButton ) ; ; click on a button inside that iframe
( switch-frame-top driver) ; ; switches on the top of the page again
يمكن أن تكون الإطارات متداخلة إلى أخرى. الوظائف تأخذ ذلك في الاعتبار. قل أن لديك تخطيط HTML مثل هذا:
< iframe src =" ... " >
< iframe src =" ... " >
< button id =" the-goal " >
</ frame >
</ frame >
حتى تتمكن من الوصول إلى الزر مع الكود التالي:
( switch-frame-first driver) ; ; switches to the first top-level iframe
( switch-frame-first driver) ; ; the same for an iframe inside the previous one
( click driver :the-goal )
( switch-frame-parent driver) ; ; you are in the first iframe now
( switch-frame-parent driver) ; ; you are at the top
لتقليل عدد خطوط التعليمات البرمجية ، هناك ماكرو خاص with-frame
. إنه يقوم بتبديل الإطارات المؤقتة أثناء تنفيذ الجسم لإعادة تعبيره الأخير والتحول إلى الإطار السابق بعد ذلك.
( with-frame driver { :id :first-frame }
( with-frame driver { :id :nested-frame }
( click driver { :id :nested-button })
42 ))
يعيد الرمز أعلاه 42
البقاء في نفس الإطار الذي تم قبل قبل تقييم وحدات الماكرو.
لتقييم رمز JavaScript في متصفح ، قم بتشغيل:
( js-execute driver " alert(1) " )
يمكنك تمرير أي معلمات إضافية في المكالمة وكاث داخل البرنامج النصي مع كائن يشبه مجموعة arguments
:
( js-execute driver " alert(arguments[2].foo) " 1 false { :foo " hello! " })
كنتيجة ، hello!
ستظهر السلسلة داخل الحوار.
لإرجاع أي بيانات إلى clojure ، فقط أضف return
إلى البرنامج النصي الخاص بك:
( js-execute driver " return {foo: arguments[2].foo, bar: [1, 2, 3]} "
1 false { :foo " hello! " })
; ; {:bar [1 2 3], :foo "hello!"}
إذا كان البرنامج النصي الخاص بك ينفذ طلبات AJAX أو يعمل على setTimeout
أو أي أشياء غير متزامنة أخرى ، فلا يمكنك return
النتيجة فقط. بدلاً من ذلك ، يجب استدعاء رد اتصال خاص ضد البيانات التي ترغب في تحقيقها. يمرر WebDriver هذا رد الاتصال هذا الوسيطة الأخيرة للنص الخاص بك وقد يتم الوصول إليه مع كائن يشبه مجموعة arguments
.
مثال:
( js-async
driver
" var args = arguments; // preserve the global args
var callback = args[args.length-1];
setTimeout(function() {
callback(args[0].foo.bar.baz);
},
1000); "
{ :foo { :bar { :baz 42 }}})
إرجاع 42
إلى رمز clojure.
لتقييم برنامج نصي غير متزامن ، تحتاج إما إلى إعداد مهلة خاصة لذلك:
( set-script-timeout driver 5 ) ; ; in seconds
أو لف الرمز في وحدات الماكرو التي تفعل ذلك مؤقتًا:
( with-script-timeout driver 30
( js-async driver " some long script " ))
الفرق الرئيسي بين البرنامج والإنسان هو أن الأول يعمل بسرعة كبيرة. هذا يعني بسرعة كبيرة ، بحيث لا يمكن للمتصفح أحيانًا تقديم HTML جديد في الوقت المناسب. لذلك بعد كل إجراء ، من الأفضل أن تضع وظيفة wait-<something>
التي تستطلع المتصفح فقط حتى يتم تقييم المسند إلى True. أو فقط (wait <seconds>)
إذا كنت لا تهتم بالتحسين.
قد يكون الماكرو with-wait
مفيدًا عندما تحتاج إلى إعداد كل إجراء مع (wait n)
. على سبيل المثال ، النموذج التالي
( with-chrome {} driver
( with-wait 3
( go driver " http://site.com " )
( click driver { :id " search_button " })))
يتحول إلى شيء مثل هذا:
( with-chrome {} driver
( wait 3 )
( go driver " http://site.com " )
( wait 3 )
( click driver { :id " search_button " }))
وبالتالي يعيد نتيجة الشكل الأخير من الجسم الأصلي.
هناك ماكرو آخر (doto-wait n driver & body)
يعمل مثل doto
القياسي ولكنه يعطي كل نموذج مع (wait n)
. على سبيل المثال:
( with-chrome {} driver
( doto-wait 1 driver
( go " http://site.com " )
( click :this-link )
( click :that-button )
...etc))
سيكون النموذج الأخير شيء من هذا القبيل:
( with-chrome {} driver
( doto driver
( wait 1 )
( go " http://site.com " )
( wait 1 )
( click :this-link )
( wait 1 )
( click :that-button )
...etc))
بالإضافة إلى with-wait
do-wait
هناك عدد من وظائف الانتظار: wait-visible
، wait-has-alert
، wait-predicate
، وما إلى ذلك (انظر القائمة الكاملة في قسم corresponsing). إنهم يقبلون قيم المهلة/الفاصل الزمني الافتراضي التي يمكن إعادة تعريفها باستخدام وحدات الماكرو with-wait-timeout
وذات with-wait-interval
، على التوالي.
مثال من اختبار etaoin:
( deftest test-wait-has-text
( testing " wait for text simple "
( with-wait-timeout 15 ; ; time in seconds
( doto *driver*
( refresh )
( wait-visible { :id :document-end })
( click { :id :wait-button })
( wait-has-text :wait-span " -secret- " ))
( is true " text found " ))))
انتظر النص:
ينتظر wait-has-text
حتى يحتوي عنصر على نص في أي مكان بداخله (بما في ذلك HTML الداخلي).
( wait-has-text driver :wait-span " -secret- " )
wait-has-text-everywhere
مثل wait-has-text
ولكن يبحث عن النص عبر الصفحة بأكملها
( wait-has-text-everywhere driver " -secret- " )
لجعل الاختبار الخاص بك لا يعتمد على بعضكما البعض ، تحتاج إلى لفها في تركيبات من شأنها أن تنشئ مثيلًا جديدًا لسائق وإغلاقه بشكل صحيح في النهاية إذا كان كل اختبار.
قد يكون الحل الجيد هو أن يكون هناك متغير عالمي (غير محدود بشكل افتراضي) سيشير إلى برنامج التشغيل المستهدف أثناء الاختبارات.
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-driver
" Executes a test running a driver. Bounds a driver
with the global *driver* variable. "
[f]
( with-chrome {} driver
( binding [*driver* driver]
( f ))))
( use-fixtures
:each ; ; start and stop driver for each test
fixture-driver)
; ; now declare your tests
( deftest ^:integration
test-some-case
( doto *driver*
( go url-project)
( click :some-button )
( refresh )
...
))
إذا كنت تريد استخدام مثيل واحد لسبب ما ، فيمكنك استخدام تركيبات مثل هذا:
( ns project.test.integration
" A module for integration tests "
( :require [clojure.test :refer :all ]
[etaoin.api :refer :all ]))
( def ^:dynamic *driver*)
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( disconnect-driver driver)
( binding [*driver* driver]
( f ))
( connect-driver driver)))
; ; creating a session every time that automatically erases resources
( defn fixture-clear-browser [f]
( connect-driver *driver*)
( go *driver* " http://google.com " )
( f )
( disconnect-driver *driver*))
; ; this is run `once` before running the tests
( use-fixtures
:once
fixture-browser)
; ; this is run `every` time before each test
( use-fixtures
:each
fixture-clear-browser)
...some tests
للاختبار بشكل أسرع ، يمكنك استخدام هذا المثال:
.....
( defn fixture-browser [f]
( with-chrome-headless { :args [ " --no-sandbox " ]} driver
( binding [*driver* driver]
( f ))))
; ; note that resources, such as cookies, are deleted manually,
; ; so this does not guarantee that the tests are clean
( defn fixture-clear-browser [f]
( delete-cookies *driver*)
( go *driver* " http://google.com " )
( f ))
......
في المثال أعلاه ، فحصنا حالة عند إجراء اختبارات مقابل نوع واحد من السائق. ومع ذلك ، قد ترغب في اختبار موقعك على برامج تشغيل متعددة ، على سبيل المثال ، Chrome و Firefox. في هذه الحالة ، قد تصبح المباراة أكثر تعقيدًا:
( def driver-type [ :firefox :chrome ])
( defn fixture-drivers [f]
( doseq [type driver-types]
( with-driver type {} driver
( binding [*driver* driver]
( testing ( format " Testing in %s browser " ( name type))
( f ))))))
الآن ، سيتم إجراء كل اختبار مرتين في كل من متصفحات Firefox و Chrome. يرجى ملاحظة أن استدعاء الاختبار مسبقًا مع testing
الماكرو الذي يضع اسم السائق في التقرير. بمجرد أن تحصل على خطأ ، ستجد سهلة برنامج التشغيل فشل الاختبارات بالضبط.
لحفظ بعض القطع الأثرية في حالة استثناء ، لف جسم الاختبار الخاص بك في معالج with-postmortem
على النحو التالي:
( deftest test-user-login
( with-postmortem *driver* { :dir " /path/to/folder " }
( doto *driver*
( go " http://127.0.0.1:8080 " )
( click-visible :login )
; ; any other actions...
)))
الآن ، في حالة حدوث أي استثناء في هذا الاختبار ، سيتم حفظ القطع الأثرية.
لعدم نسخ خريطة الخيارات ولصقها ، أعلن ذلك في الجزء العلوي من الوحدة النمطية. إذا كنت تستخدم Circle CI ، فسيكون من الرائع حفظ البيانات في دليل قطعة أثرية خاصة يمكن تنزيلها كملف مضغوط بمجرد الانتهاء من الإنشاء:
( def pm-dir
( or ( System/getenv " CIRCLE_ARTIFACTS " ) ; ; you are on CI
" /some/local/path " )) ; ; local machine
( def pm-opt
{ :dir pm-dir})
الآن مرر تلك الخريطة في كل مكان إلى معالج رئيس الوزراء:
; ; test declaration
( with-postmortem *driver* pm-opt
; ; test body goes here
)
بمجرد حدوث خطأ ، ستجد صورة PNG تمثل صفحة المتصفح الخاصة بك في لحظة الاستثناء وتفريغ HTML.
نظرًا لأن اختبارات واجهة المستخدم قد تستغرق الكثير من الوقت لتمريرها ، فمن المؤكد أنها ممارسة جيدة اجتياز اختبارات الخادم وواجهة المستخدم بشكل مستقل عن بعضها البعض.
أولاً ، أضف ^:integration
لجميع الاختبارات التي يتم تشغيلها في المتصفح مثل:
( deftest ^:integration
test-password-reset-pipeline
( doto *driver*
( go url-password-reset)
( click :reset-btn )
...
ثم ، افتح ملف project.clj
وأضف محددات الاختبار:
:test-selectors { :default ( complement :integration )
:integration :integration }
الآن ، بمجرد إطلاق lein test
، ستقوم بإجراء جميع الاختبارات باستثناء الاختبارات. لتشغيل اختبارات التكامل ، قم بإطلاق lein test :integration
.
الفرق الرئيسي بين البرنامج والإنسان هو أن الأول يعمل بسرعة كبيرة. هذا يعني بسرعة كبيرة ، بحيث لا يمكن للمتصفح أحيانًا تقديم HTML جديد في الوقت المناسب. لذلك ، بعد كل إجراء ، تحتاج إلى وضع وظيفة wait-<something>
التي تستطلع المتصفح فقط الذي يقوم بالتحقق من المستعار. o فقط (wait <seconds>)
إذا كنت لا تهتم بالتحسين.
في بعض الأحيان ، يبدأ الملف في التنزيل تلقائيًا بمجرد النقر على رابط أو زار بعض الصفحة. في الاختبارات ، تحتاج إلى التأكد من تنزيل ملف بالفعل. سيكون السيناريو الشائع:
مثال:
; ; Local helper that checks whether it is really an Excel file.
( defn xlsx? [file]
( -> file
.getAbsolutePath
( str/ends-with? " .xlsx " )))
; ; Top-level declarations
( def DL-DIR " /Users/ivan/Desktop " )
( def driver ( chrome { :download-dir DL-DIR}))
; ; Later, in tests...
( click-visible driver :download-that-application )
( wait driver 7 ) ; ; wait for a file has been downloaded
; ; Now, scan the directory and try to find a file:
( let [files ( file-seq ( io/file DL-DIR))
found ( some xlsx? files)]
( is found ( format " No *.xlsx file found in %s directory. " DL-DIR)))
يمكن لـ Etaoin تشغيل الملفات التي تنتجها Selenium IDE. إنها فائدة رسمية لإنشاء سيناريوهات بشكل تفاعلي. يأتي IDE امتدادًا لمتصفحك. بمجرد التثبيت ، يقوم بتسجيل الإجراءات في ملف JSON مع امتداد .side
. يمكنك حفظ هذا الملف وتشغيله باستخدام etaoin.
دعونا نتخيل أنك قمت بتثبيت IDE وسجلت بعض الإجراءات كما توصف الوثائق الرسمية. الآن بعد أن أصبح لديك ملف test.side
، افعل هذا:
( require '[etaoin.ide.flow :as flow])
( def driver ( chrome ))
( def ide-file ( io/resource " ide/test.side " ))
( def opt
{ ; ; The base URL redefines the one from the file.
; ; For example, the file was written on the local machine
; ; (http://localhost:8080), and we want to perform the scenario
; ; on staging (https://preprod-001.company.com)
:base-url " https://preprod-001.company.com "
; ; keywords :test-.. and :suite-.. (id, ids, name, names)
; ; are used to select specific tests. When not passed,
; ; all tests get run. For example:
:test-id " xxxx-xxxx... " ; ; a single test by its UUID
:test-name " some-test " ; ; a single test by its name
:test-ids [ " xxxx-xxxx... " , ...] ; ; multiple tests by their ids
:test-names [ " some-test1 " , ...] ; ; multiple tests by their names
; ; the same for suites:
:suite-id ...
:suite-name ...
:suite-ids [...]
:suite-names [...]})
( flow/run-ide-script driver ide-file opt)
يتم تخزين كل ما يتعلق بـ IDE تحت حزمة etaoin.ide
.
يمكنك أيضًا تشغيل برنامج نصي من سطر الأوامر. هنا مثال lein run
:
lein run -m etaoin.ide.main -d firefox -p ' {:port 8888 :args ["--no-sandbox"]} ' -r ide/test.side
وكذلك من أوبرجار. في هذه الحالة ، يجب أن يكون etaoin في التبعيات الأولية ، وليس على :dev
أو :test
المتعلق.
java -cp .../poject.jar -m etaoin.ide.main -d firefox -p ' {:port 8888} ' -f ide/test.side
نحن ندعم الوسائط التالية (تحقق منها باستخدام أمر lein run -m etaoin.ide.main -h
أمر):
-d, --driver-name name :chrome The name of driver. The default is `:chrome`
-p, --params params {} Parameters for the driver represented as an
EDN string, e.g '{:port 8080}'
-f, --file path Path to an IDE file on disk
-r, --resource path Path to an IDE resource
--test-ids ids Comma-separeted test ID(s)
--suite-ids ids Comma-separeted suite ID(s)
--test-names names Comma-separeted test name(s)
--suite-names names Comma-separeted suite name(s)
--base-url url Base URL for tests
-h, --help
انتبه إلى واحد --params
. يجب أن تكون هذه سلسلة EDN تمثل خريطة clojure. هذه هي نفس الخريطة التي تنتقلها إلى برنامج تشغيل عند بدء تشغيله.
يرجى ملاحظة أن دعم IDE لا يزال تجريبيًا. إذا واجهت سلوكًا غير متوقع ، فلا تتردد في فتح مشكلة. في الوقت الحالي ، ندعم فقط Chrome و Firefox لملفات IDE.
مثال:
etaoin.api> ( def driver ( chrome ))
#'etaoin.api/driver
etaoin.api> ( maximize driver)
ExceptionInfo throw+: { :response {
:sessionId " 2672b934de785aabb730fd19330cf40c " ,
:status 13 ,
:value { :message " unknown error: cannot get automation extension n from unknown error: page could not be found: chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/_generated_background_page.html n
(Session info: chrome=57.0.2987.133) n (Driver info: chromedriver=2.27.440174
(e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.11.6 x86_64) " }},
...
الحل: ما عليك سوى تحديث chromedriver
الخاص بك إلى الإصدار الأخير. تم اختباره مع 2.29 ، يعمل بشكل جيد. يقول الناس ذلك أيضًا منذ 2.28.
تذكر أن brew
Package Manager لديه الإصدار 2.27 عفا عليه الزمن. من المحتمل أن تضطر إلى تنزيل الثنائيات من الموقع الرسمي.
انظر القضية ذات الصلة في مشروع السيلينيوم.
عند اجتياز استعلام يشبه المتجه ، قل [{:tag :p} "//*[text()='foo')]]"}]
كن حذرا مع تعبيرات xpath المكتوبة باليد. في المتجه ، كل تعبير يبحث من التعبير السابق في حلقة. هناك خطأ خفي هنا: بدون نقطة رائدة ، يعني جملة "//..."
العثور على عنصر من جذر الصفحة بأكملها. مع نقطة ، يعني أن تجد من العقدة الحالية ، والتي هي واحدة من الاستعلام السابق ، وهكذا دواليك.
لهذا السبب ، من السهل اختيار شيء مختلف تمامًا عما تريده. سيكون التعبير المناسب: [{:tag :p} ".//*[text()='foo')]]"}]
.
مثال:
etaoin.api> ( click driver :some-id )
ExceptionInfo throw+: { :response {
:sessionId " d112ce8ddb49accdae78a769d5809eae " ,
:status 11 ,
:value { :message " element not visible n (Session info: chrome=57.0.2987.133) n
(Driver info: chromedriver=2.29.461585
(0be2cd95f834e9ee7c46bcc7cf405b483f5ae83b),platform=Mac OS X 10.11.6 x86_64) " }},
...
الحل: أنت تحاول النقر فوق عنصر غير مرئي أو أن عمليات التمييز الخاصة به أقل بقدر ما يستحيل على الإنسان النقر عليه. يجب أن تمرر محدد آخر.
المشكلة: عندما تركز على نافذة أخرى ، تفشل جلسة WebDriver التي يتم تشغيلها تحت Google Chrome.
الحل: قد تعلق Google Chrome علامة تبويب عندما تكون غير نشطة لبعض الوقت. عندما يتم تعليق الصفحة ، لا يمكن القيام بأي عملية. لا نقرات ، تنفيذ JS ، وما إلى ذلك ، لذا حاول الحفاظ على نافذة Chrome نشطة أثناء جلسة الاختبار.
المشكلة: عندما تحاول بدء تشغيل برنامج التشغيل ، تحصل على خطأ:
user=> ( use 'etaoin.api)
user=> ( def driver ( firefox { :headless true }))
خطأ في بناء الجملة (استثناء extinfo) تجميع في (REPL: 1: 13). رمي+: {: استجابة {: value {: خطأ "خطأ غير معروف" ،: رسالة "وسيطة غير صالحة: لا يمكن قتل عملية خارجة" ....
السبب المحتمل: "تشغيل Firefox كجذر في جلسة المستخدم العادية غير مدعوم"
الحل: للتحقق ، قم بتشغيل برنامج التشغيل مع المسار إلى ملفات السجل ومستوى السجل "تتبع" واستكشاف إخراجهم.
( def driver ( firefox { :log-stdout " ffout.log " :log-stderr " fferr.log " :driver-log-level " trace " }))
مشكلة مماثلة: Mozilla/Geckodriver#1655
المشكلة: عندما تحاول بدء تشغيل chromedriver ستحصل على خطأ:
clojure.lang.exceptionInfo: رمي+: {: استجابة {: SessionId "..... ،: الحالة 13 ،: القيمة {: رسالة" خطأ غير معروف: فشل Chrom الملف غير موجود) ...
السبب المحتمل:
يتم تشغيل سبب شائع لصفوف Chrome أثناء بدء التشغيل Chrome كمستخدم جذر (المسؤول) على Linux. على الرغم من أنه من الممكن أن تدور حول هذه المشكلة عن طريق المرور-لا يوجد علامة في Sandbox عند إنشاء جلسة WebDriver الخاصة بك ، إلا أن هذا التكوين غير مدعوم ومثبط للغاية. You need to configure your environment to run Chrome as a regular user instead.
Solution: Run driver with an argument --no-sandbox
. حذر! This is a bypass OS security model.
( def driver ( chrome { :args [ " --no-sandbox " ]}))
A similar problem is described here
The etaoin.api2
namespace brings some bits of alternative macros and functions. They provide better syntax and live in a separate namespace to prevent the old API from breaking.
At the moment, the api2
module provides a set of with-...
macros with a let
-like binding form:
( ns ...
( :require
[etaoin.api :as api]
[etaoin.api2 :as api2]))
( api2/with-chrome [driver {}]
( api/go driver " http://ya.ru " ))
The options map can be skipped so you have only a binding symbol:
( api2/with-firefox [ff]
( api/go ff " http://ya.ru " ))
The project is open for your improvements and ideas. If any of unit tests fall on your machine please submit an issue giving your OS version, browser and console output.
Copyright © 2017—2020 Ivan Grishaev.
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.