Selenium3元素定位詳解與封裝(一)


一、UI自動化測試概述

1.1、為什么需要自動化測試

       在學習自動化測試之前,首先需要思考清楚的是為什么需要學習自動化測試,以及今天業界談的研發效能對測試而言意味着什么?其實這就需要在測試以及

整體研發的角度來思考問題,在今天這樣的市場環境中,打造高質量的持續交付產品質量,基本是所有互聯網研發團隊的測試團隊都追求的一個方向。在敏捷流行

的今天,以及新的技術在企業全面的落地,在測試而言,就需要通過測試技術的手段以及質量管理的思維能力,來提升測試效率,和交付滿足市場期待的產品質量。

自動化測試是所有測試形式里面在目前而言,是最基礎的也是最核心的,因為自動化測試連接了功能測試以及高階的測試開發的測試技術棧的知識體系。即使初級

的測試同學,也得具備自動化測試的思維能力和技術能力。

1.2、UI自動化測試的必要性

      與API自動化測試相比較,UI自動化測試不論是從執行效率還是編程難易度上,都比API自動化測試的成本是比較高的,如果單純的從技術復雜度上來說,與API

的測試技術棧的體系是一樣的,不同的是測試的思維以及背后的思想。但是還是需要比較清楚的是,UI自動化測試在DevOps的體系以及測試流水線上它是非常必要

的,只不過我們需要使用更加正確的姿勢來利用好這個技術,比如使用它來驗證核心的流程,而拋開更多的非主線的業務。不能單純的說它的測試執行效率低,就完

全的否定UI自動化測試的價值,這樣是很不理性的。在主流的UI自動化測試框架中,Selenium3經過多年的發展,它的技術體系以及生態體系都是非常完善的,能夠得

到各大主流瀏覽器廠商的支持,和完善的document文檔,以及與各個編程語言之間的兼容。本文章系列主要是以Python語言為主要的編程語言。當然,課程的整體設

計思考是借鑒我的書籍《Python自動化測試》來思考的,本課程《Selenium3自動化測試實戰》主要在進行直播分享。

二、元素屬性操作

      在UI自動化測試中,最核心最基礎的就是首先需要定位到元素的屬性,然后就可以針對這個屬性進行具體的相關的頁面交互操作,比如進行進行關鍵字的輸入,以及

點擊的操作等。下面針對這部分進行詳細的開展說明。

3.1 解讀源碼

      我們先來看Selenium3的源碼體系,當然我們知道元素的方法都是來自by模塊中的By類,下面具體顯示的是By類的源碼,具體如下:

class By(object):
    """
    Set of supported locator strategies.
    """

    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

從By類里面可以看到,元素屬性定位的方式有8種,具體的8種在上面顯示的非常詳細,但是針對元素具體的方法都是在webdriver的模塊里面,這些方法都在該模塊

里面。從元素分類的角度而言,元素定位可以分為單個元素定位和多個元素定位,那么也就是從單個元素的方法有8種,多個元素的定位方法也是有8種,總共就16種

方法。這是分類方法的總結思維,在文章的最后我會怎么說利用這兩個方法,讓我們的元素定位更加簡單和優雅,下面具體展示單個元素的方法和單個元素的方法,具

體如下:

   def find_element(self, by=By.ID, value=None) -> WebElement:
        """
        Find an element given a By strategy and locator.

        :Usage:
            ::

                element = driver.find_element(By.ID, 'foo')

        :rtype: WebElement
        """
        if by == By.ID:
            by = By.CSS_SELECTOR
            value = '[id="%s"]' % value
        elif by == By.TAG_NAME:
            by = By.CSS_SELECTOR
        elif by == By.CLASS_NAME:
            by = By.CSS_SELECTOR
            value = ".%s" % value
        elif by == By.NAME:
            by = By.CSS_SELECTOR
            value = '[name="%s"]' % value

        return self.execute(Command.FIND_ELEMENT, {
            'using': by,
            'value': value})['value']

    def find_elements(self, by=By.ID, value=None) -> List[WebElement]:
        """
        Find elements given a By strategy and locator.

        :Usage:
            ::

                elements = driver.find_elements(By.CLASS_NAME, 'foo')

        :rtype: list of WebElement
        """
        if isinstance(by, RelativeBy):
            _pkg = '.'.join(__name__.split('.')[:-1])
            raw_function = pkgutil.get_data(_pkg, 'findElements.js').decode('utf8')
            find_element_js = "return ({}).apply(null, arguments);".format(raw_function)
            return self.execute_script(find_element_js, by.to_dict())

        if by == By.ID:
            by = By.CSS_SELECTOR
            value = '[id="%s"]' % value
        elif by == By.TAG_NAME:
            by = By.CSS_SELECTOR
        elif by == By.CLASS_NAME:
            by = By.CSS_SELECTOR
            value = ".%s" % value
        elif by == By.NAME:
            by = By.CSS_SELECTOR
            value = '[name="%s"]' % value

        # Return empty list if driver returns null
        # See https://github.com/SeleniumHQ/selenium/issues/4555
        return self.execute(Command.FIND_ELEMENTS, {
            'using': by,
            'value': value})['value'] or []

3.2 常用元素定位實戰

3.2.1 單個元素

        本實戰的都是圍繞百度搜索輸入框來進行開展,主要是百度大家都是比較熟悉的產品。

3.2.1.1、find_element_by_id

          find_element_by_id()的方法主要指的是我們定位元素屬性主要是以ID的方式來進行定位,ID一般都是唯一的,當兒開發同學某些時候為了保持這種唯一性,使用了

動態的ID方式,其實解決的思路是非常簡單的,那就是xpath的解決思路了。這地方我們還是聚焦於ID的屬性定位方式,百度搜索輸入框的ID源碼具體為:

<input id="kw" name="wd" class="s_ipt" value="" maxlength="255" autocomplete="off">

 

這是百度搜索輸入框的input輸入框的源代碼部分,從源代碼我們就可以得到它的ID是kw,下面我們結合具體的代碼來進行操作下,案例代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_id('kw').send_keys('無涯 接口測試')
driver.quit()

3.2.1.2、find_element_by_name

             下來是以name的屬性來進行定位和具體的操作,還是從上面的源碼得到它的name為wd,調用的方法當然都是find_element_by_name()的方法,按照name的屬性

在搜索輸入框輸入搜索關鍵字的測試案例實戰代碼:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_name('wd').send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

3.2.1.3、find_element_by_class_name

            下面以class的屬性,它使用到的方法為find_element_by_class_name的方式來進行,在屬性里面也就是class,還是從上面的HTML的源碼里面可以知道,它的class

為s_ipt,下面實現以class的方式來進行,測試實戰代碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_class_name('s_ipt').send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

3.2.1.4、find_element_by_class_xpath

          當一個元素實在在定位不到的時候,也就是id,name,class都不可以的時候,可以使用xpath或者是css的模式,我個人一般推薦可以使用xpath的方式,那么獲取

元素屬性的xpth怎么獲取了,下面為具體說下操作步驟:

1、鼠標到需要操作的元素屬性

2、右鍵,點擊Copy,如下圖所示:

 

3、點擊Copy后,選擇Copy Xpath,如下圖所示:

當然如果是動態的ID,獲取到的xpath也是錯誤的,那么這個時候怎么解決問題了?解決的思路就是點擊Copy full Xpath,這樣獲取到的xpath是完整的,就不會

因為動態的ID而導致錯誤。下面還是以百度搜索輸入框,獲取到它的xpath為://*[@id="kw"],下面具體顯示實戰的測試案例代碼:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_xpath('//*[@id="kw"]').send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

 3.2.1.5、find_element_by_css_selector

            使用css的定位操作方式與xpath的操作步驟都是一樣的,只不過有點區別的是在點擊Copy后,需要點擊的是Copy selector,具體如下圖所示:

調用的方法為find_element_by_css_selector, 百度搜索輸入框獲取到它的css為#kw,那么就以css的方式來演示這部分的測試實戰代碼,代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_css_selector('#kw').send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

3.2.1.6、find_element_by_link_text

            在頁面的交互種如果存在超鏈接,可以使用的方法為find_element_by_link_text,比如在百度首先我們需要點擊新聞,那么就可以使用方法來進行定位了

一般而言在a標簽里面的,我們都可以理解為超鏈接,就可以使用該方法來進行具體的操作了,針對點擊新聞的超鏈接測試代碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_link_text('新聞').click()
t.sleep(3)
driver.quit()

3.2.1.7、find_element_by_partial_link_text

           針對超鏈接定位還有另外一方法它就是find_element_by_partial_link_text,那么把它可以理解為也是針對超鏈接的定位方式,不過它可以使用模糊匹配

的原則,這個怎么理解了,比如點擊新聞,我們只可以使用一個關鍵字“聞”,下面還是點擊新聞的超鏈接,但是使用模糊的方式,測試代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_partial_link_text('聞').click()
t.sleep(3)
driver.quit()

3.2.1.8、find_element_by_tag_name

            tag_name可以理解為是標簽,怎么理解了,就是就是百度搜索輸入框,它的標簽是input,那么針對這種我們可以使用標簽的方式來進行,使用到的方法

是find_element_by_tag_name,測試實戰代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
driver.find_element_by_tag_name('input').send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

執行如上的代碼,很遺憾出現錯誤了,具體錯誤信息為:

Traceback (most recent call last):
  File "/Applications/code/Yun/零基礎測試/login.py", line 10, in <module>
    driver.find_element_by_tag_name('input').send_keys('無涯 接口測試')
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/selenium/webdriver/remote/webelement.py", line 522, in send_keys
    'value': keys_to_typing(value)})
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/selenium/webdriver/remote/webelement.py", line 664, in _execute
    return self._parent.execute(command, params)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 344, in execute
    self.error_handler.check_response(response)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 236, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.ElementNotInteractableException: Message: element not interactable

出現問題不要急着問別人,其實仔細看看錯誤信息,我們是能夠獨立的解決問題的,出現這個問題說明元素定位找不到,導致錯誤,那么有可能是定位到的元

素屬性是錯誤,還有一種是我們需要索引的方式來解決。如果是后者,是單個元素定位的方式無法解決的,就是多個元素可以解決的了。 

3.2.2 多個元素

          針對單個元素定位無法解決的問題,主要核心點獲取到的元素屬性都一樣,比如就以百度搜索輸入框為案例,我們使用的是input標簽的方式進行,但是

input標簽有8個,那么就不是唯一的了,具體如下所示:

 

針對這種不是唯一的,我們可以使用多個元素定位的方式來解決,其實多個元素定位的核心思想是獲取到的元素屬性是一個列表,我們可以使用列表的索引來進

行定位,比如針對標簽的方法就是find_elements_by_tag_name(),當然其他的方法其實都是一樣的,這里我們先獲取到它的屬性,然后輸出,就可以看到它的數

據是列表,具體案例代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
so=driver.find_elements_by_tag_name('input')
print(type(so))
t.sleep(3)
driver.quit()

我們定位的百度搜索輸入框的input是在第八位,那么它的索引就是7,那么針對這部分的操作可以調整下代碼,修改后的代碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t

driver=webdriver.Chrome()
driver.get('http://www.baidu.com')
so=driver.find_elements_by_tag_name('input')
so[7].send_keys('無涯 接口測試')
t.sleep(3)
driver.quit()

三、函數式思維

3.1、方法封裝

           Python是函數式的編程語言也是面向對象的編程,那么什么是函數,其實函數來自數學的思想,模塊化的組織思維和把復雜問題簡單化的結構化

的思維方式,通俗的理解就是把一組語句的集合通過一個函數名封裝起來,要想執行這個函數,只需要調用這個函數名就可以了。函數的優勢可以總結為

  • 減少重復代碼

  • 程序變得可擴展

  • 程序變得可以容易維護

根據函數式的思想,我們可以針對元素定位的方法進行封裝,這樣調用起來會更加簡單,其實只所以要封裝的思考點是特別的簡單,一是利用函數的結構化

的思想,而是讓調用的方法更加簡潔,如下是常有方法的封裝,具體如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t



def findID(driver,ID):
    return driver.find_element_by_id(ID)

def findName(driver,name):
    return driver.find_element_by_name(name)


def findCalssName(driver,className):
    return driver.find_element_by_class_name(className)

def findXpath(driver,xpath):
    return driver.find_element_by_xpath(xpath)

def findCssSelector(driver,cssSelector):
    return  driver.find_element_by_css_selector(cssSelector)

def findTagsName(driver,tagName,index):
    return  driver.find_elements_by_tag_name(tagName)[index]

上面我只是封裝了部分的方法,其他的方法您可以根據自己的需求進行繼續封裝,下面我們還是依據之前的代碼來調用封裝后的代碼是否正確,就以

多個元素定位的方式來進行,調整后的代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
import  time as t



def findID(driver,ID):
    return driver.find_element_by_id(ID)

def findName(driver,name):
    return driver.find_element_by_name(name)


def findCalssName(driver,className):
    return driver.find_element_by_class_name(className)

def findXpath(driver,xpath):
    return driver.find_element_by_xpath(xpath)

def findCssSelector(driver,cssSelector):
    return  driver.find_element_by_css_selector(cssSelector)

def findTagsName(driver,tagName,index):
    return  driver.find_elements_by_tag_name(tagName)[index]

if __name__ == '__main__':
    driver=webdriver.Chrome()
    driver.get('http://www.baidu.com')
    findTagsName(driver=driver,tagName='input',index=7).send_keys('無涯 接口測試')
    t.sleep(3)
    driver.quit()

執行無任何的問題,但是有警告,具體警告信息為:

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py:606: UserWarning: find_elements_by_* commands are deprecated. Please use find_elements() instead
  warnings.warn("find_elements_by_* commands are deprecated. Please use find_elements() instead")

3.2、頂層思維

           在3.1的收尾中,有警告的信息,我們可以具體到webdriver.py模塊的606行看看警告信息,這部分的源碼為:

    def find_elements_by_tag_name(self, name):
        """
        Finds elements by tag name.

        :Args:
         - name - name of html tag (eg: h1, a, span)

        :Returns:
         - list of WebElement - a list with elements if any was found.  An
           empty list if not

        :Usage:
            ::

                elements = driver.find_elements_by_tag_name('h1')
        """
        warnings.warn("find_elements_by_* commands are deprecated. Please use find_elements() instead")
        return self.find_elements(by=By.TAG_NAME, value=name)

很明顯,按照官方的意思,后面這種方式逐步的會被替代以及放棄,警告不是錯誤,但是讓人不舒服,那么解決的思路是什么了?還是看官方的警告

代碼來分析,根據警告官方更加推薦我們使用(by=By.TAG_NAME, value=name)這種方式來解決,這也是在我開頭部分說的,不管元素有多少個方

法,我們只可以分為兩個,主要就是單個元素定位和多個元素定位的方法,那么針對上面的封裝進行改造,改造成(by=By.TAG_NAME, value=name)

的模式,調整后的代碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
from selenium.webdriver.common.by import By
import  time as t



def findID(driver,ID):
    return driver.find_element(By.ID,ID)

def findName(driver,name):
    return driver.find_element(By.NAME,name)


def findCalssName(driver,className):
    return driver.find_element(By.CLASS_NAME,className)

def findXpath(driver,xpath):
    return driver.find_element(By.XPATH,xpath)

def findCssSelector(driver,cssSelector):
    return  driver.find_element(By.CSS_SELECTOR,cssSelector)

def findTagsName(driver,tagName,index):
    return  driver.find_elements(By.TAG_NAME,tagName)[index]

if __name__ == '__main__':
    driver=webdriver.Chrome()
    driver.get('http://www.baidu.com')
    findTagsName(driver=driver,tagName='input',index=7).send_keys('無涯 接口測試')
    t.sleep(3)
    driver.quit()

再次執行沒有任何的錯誤信息,但是還是感覺封裝的方法有點多,沒有達到封裝的最原始的訴求的,最原始的訴求我更想的是按照分類的思考

點來進行,也就是元素定位只有兩個方法,那么如下是封裝后的代碼,具體如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
from selenium.webdriver.common.by import By
import  time as t

def findElement(driver,*args):
    return  driver.find_element(*args)

def findElements(driver,*args):
    return driver.find_elements(*args)

if __name__ == '__main__':
    driver=webdriver.Chrome()
    driver.get('http://www.baidu.com')
    findElements(
        driver,
        *(By.TAG_NAME,'input'))[7].send_keys('無涯 接口測試')

    # findElement(driver,*(By.ID,'kw')).send_keys('無涯 接口測試')
    t.sleep(3)
    driver.quit()

通過如上的代碼,可以看到是非常簡單,達到了訴求。感謝您的閱讀,第十一期《服務端測試開發訓練營》開始招生啦,歡迎

掃描二維碼咨詢。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM