随心玩玩(十一)Selenium从入门到入土

2023-12-20 18:10:01

写在前面:脚本是最简单的开发


参考资料:
https://www.geeksforgeeks.org/selenium-python-tutorial/
https://selenium-python.readthedocs.io/

介绍

Selenium是一个用于自动化浏览器操作的开源框架,而Selenium WebDriver则是Selenium的一个组件,它提供了一种编程接口,用于控制浏览器的行为。Selenium WebDriver支持多种浏览器,包括但不限于Chrome、Firefox、Safari和Edge等。

Selenium Python绑定提供了一个方便的API来访问Selenium WebDrivers,如Firefox、Ie、Chrome、Remote等。

安装

pip install selenium
pip install webdriver_manager

Selenium需要一个驱动程序来驱动所选浏览器。例如,Firefox需要geckodriver,谷歌需要chrome driver。

如果自己已经装了chrome的话,查看自己的chrome版本,在https://chromedriver.storage.googleapis.com/index.html安装相应的chrome driver下载后解压随便放一个目录里即可。

或者也可以在https://chromedriver.chromium.org/downloads查看各chrome driver版本的下载

浏览器方面,我就直接网盘提供绿色安装的压缩包吧,不忘初心方便大家,版本是108.0.5359.125:
链接:https://pan.baidu.com/s/1DN0JhGyCIudTLDBXUl0c4w?pwd=fjfc
提取码:fjfc (永久有效,网盘挂了记得踹我一脚)

108版本chrome driver 下载地址:
https://chromedriver.storage.googleapis.com/index.html?path=108.0.5359.71/

下载好后,随便解压到一个目录即可。

让我们开始吧

简单例子

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

# 启动driver
options = Options()
# 替换为你本地的Chromed路径
options.binary_location = r"D:\software\Chrome-bin\chrome.exe"
# 替换为你本地的Chromedriver路径
chromedriver_path = r'D:\software\Chrome-bin\chromedriver.exe'
# 使用Service对象指定Chromedriver路径
service = Service(chromedriver_path)
driver = webdriver.Chrome(service=service, options=options)
# ==============
driver.get("https://www.python.org")
assert "Python" in driver.title
elem = driver.find_element(By.NAME, "q")
elem.clear()
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
driver.close()

这是一个使用Selenium库进行自动化测试的get start脚本,下面是对代码的解释:

  1. 导入必要的模块和类:

    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    

    这里导入了Selenium的webdriver模块,以及一些其他的类和模块,包括ServiceOptionsByKeys

  2. 配置Chrome选项和路径:

    options = Options()
    options.binary_location = r"D:\software\Chrome-bin\chrome.exe"
    chromedriver_path = r'D:\software\Chrome-bin\chromedriver.exe'
    

    在这里,创建了一个Options对象来配置Chrome浏览器的选项,包括指定Chrome二进制文件的路径。chromedriver_path是Chromedriver的可执行文件路径。

  3. 启动Chromedriver服务和浏览器:

    service = Service(chromedriver_path)
    driver = webdriver.Chrome(service=service, options=options)
    

    创建了一个Service对象,用于指定Chromedriver的路径,然后通过webdriver.Chrome启动了Chrome浏览器,并传递了Chromedriver服务和选项。

  4. 访问网页:

    driver.get("https://www.python.org")
    

    打开Chrome浏览器后,通过get方法访问了Python官方网站。

  5. 验证页面标题:

    assert "Python" in driver.title
    

    使用断言来确认打开的页面标题中包含"Python"。

  6. 查找页面元素并输入搜索内容:

    elem = driver.find_element(By.NAME, "q")
    elem.clear()
    elem.send_keys("pycon")
    

    使用find_element方法通过By.NAME查找页面上的元素,然后清空该元素的内容并输入"pycon"。

  7. 模拟键盘按键并提交表单:

    elem.send_keys(Keys.RETURN)
    

    使用send_keys方法模拟按下回车键,以提交搜索表单。

  8. 验证页面中是否包含指定文本:

    assert "No results found." not in driver.page_source
    

    使用断言确认在页面源代码中不存在"No results found."这个文本,以确保搜索结果不为空。

  9. 关闭浏览器:

    driver.close()
    

    使用close方法关闭浏览器窗口。

这个脚本的主要目的是通过自动化测试的方式打开Python官方网站,执行搜索操作,并验证搜索结果。

编写测试

selenium包本身不提供测试框架。但可以使用Python的unittest模块编写测试用例。工具/框架的其他选项是pytest和nose。
在例子中,我们使用单元测试作为选择的框架。以下是使用单元测试模块的修改示例。这是对python.org搜索功能的测试:

import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys


class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
        # 启动driver
        options = Options()
        # 替换为你本地的Chromed路径
        options.binary_location = r"D:\software\Chrome-bin\chrome.exe"
        # 替换为你本地的Chromedriver路径
        chromedriver_path = r'D:\software\Chrome-bin\chromedriver.exe'
        # 使用Service对象指定Chromedriver路径
        service = Service(chromedriver_path)
        driver = webdriver.Chrome(service=service, options=options)
        # ==============

        self.driver = driver

    def test_search_in_python_org(self):
        driver = self.driver
        driver.get("http://www.python.org")
        self.assertIn("Python", driver.title)
        elem = driver.find_element(By.NAME, "q")
        elem.send_keys("pycon")
        elem.send_keys(Keys.RETURN)
        self.assertNotIn("No results found.", driver.page_source)

    def tearDown(self):
        self.driver.quit()


if __name__ == "__main__":
    unittest.main()

这段代码是一个使用Selenium和unittest模块的测试脚本,它执行以下操作:

  1. 导入必要的模块和类:

    import unittest
    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    

    这里导入了unittest模块和Selenium的webdriver模块以及其他相关类。

  2. 定义测试类:

    class PythonOrgSearch(unittest.TestCase):
    

    定义了一个继承自unittest.TestCase的测试类。

  3. 设置测试环境(setUp方法):

    def setUp(self):
        # 启动driver
        options = Options()
        options.binary_location = r"D:\software\Chrome-bin\chrome.exe"
        chromedriver_path = r'D:\software\Chrome-bin\chromedriver.exe'
        service = Service(chromedriver_path)
        driver = webdriver.Chrome(service=service, options=options)
        self.driver = driver
    

    在每个测试方法执行之前,setUp方法被调用,其中初始化了WebDriver,即启动了Chrome浏览器,并将WebDriver对象存储在self.driver中供其他测试方法使用。

  4. 定义测试方法:

    def test_search_in_python_org(self):
        driver = self.driver
        driver.get("http://www.python.org")
        self.assertIn("Python", driver.title)
        elem = driver.find_element(By.NAME, "q")
        elem.send_keys("pycon")
        elem.send_keys(Keys.RETURN)
        self.assertNotIn("No results found.", driver.page_source)
    

    这是一个测试方法,以test_开头的方法会被unittest识别为测试用例。该测试方法访问Python官方网站,搜索关键字"pycon",然后断言页面标题中包含"Python",并且页面源代码中不包含"No results found."文本。

  5. 清理测试环境(tearDown方法):

    def tearDown(self):
        self.driver.quit()
    

    在每个测试方法执行之后,tearDown方法被调用,用于清理测试环境。在这里,它关闭了WebDriver,即关闭了Chrome浏览器。

  6. 运行测试用例:

    if __name__ == "__main__":
        unittest.main()
    

    这段代码确保当脚本被直接运行时才执行测试,而不是在其他模块中被导入时执行测试。执行unittest.main()会运行所有以test_开头的测试方法。

您可以从pycharm上运行上面的测试用例,将会显示类似结果表明测试成功:

Ran 1 test in 8.607s

OK

Process finished with exit code 0

跳转链接

使用WebDriver要做的第一件事就是导航到一个链接。执行此操作的正常方法是调用get方法:
driver.get("http://www.google.com")

WebDriver将等待页面完全加载(即启动onload事件),然后将控制权返回到测试或脚本。请注意,如果您的页面在加载时使用了大量AJAX,那么WebDriver可能不知道它何时已经完全加载。如果需要确保这些页面已完全加载,则可以使用等待。

与页面交互

仅仅能够去一些地方并不是很有用。我们真正想做的是与页面交互,或者更具体地说,是与页面中的HTML元素交互。首先,我们需要找到一个想要交互的HTML元素。WebDriver提供了多种查找元素的方法。例如,给定一个定义为的元素:
<input type="text" name="passwd" id="passwd-id" />

您可以使用以下任意一种方法找到它:

element = driver.find_element(By.ID, "passwd-id")
element = driver.find_element(By.NAME, "passwd")
element = driver.find_element(By.XPATH, "//input[@id='passwd-id']")
element = driver.find_element(By.CSS_SELECTOR, "input#passwd-id")

你也可以通过文本来寻找链接,但要小心文本必须完全匹配。在WebDriver中使用XPATH时也应该小心。如果有多个元素与查询匹配,则只返回第一个元素。如果找不到任何内容,将引发NoSuchElementException。

WebDriver有一个“基于对象的”API;我们使用相同的接口来表示所有类型的元素。这意味着,您可能会看到许多可能调用的方法来完成组合操作,但并非所有方法都有意义或有效,但别担心WebDriver将尝试做正确的事情。

但如果您调用一个没有意义的方法,例如,“meta”上调用setSelected(),则会引发异常。

所以,你有一个元素。你能用它做什么?首先,您可能需要在文本字段中输入一些文本:
element.send_keys("some text")

您可以使用“keys”类模拟按下箭头键:
element.send_keys(" and some", Keys.ARROW_DOWN)

你可以在任何元素上调用send_keys,这使得测试键盘快捷键(如GMail上使用的快捷键)成为可能。这样做的一个副作用是,您键入的内容会附加到已经存在的内容上。您可以使用clear方法轻松清除文本字段或文本区域的内容:
element.clear()

填充表格

我们已经了解了如何在文本区域或文本字段中输入文本,但其他元素呢?您可以“切换”下拉菜单的状态,也可以使用“setSelected”设置类似于OPTION标记的选定内容。处理SELECT标记:

element = driver.find_element(By.XPATH, "//select[@name='name']")
all_options = element.find_elements(By.TAG_NAME, "option")
for option in all_options:
    print("Value is: %s" % option.get_attribute("value"))
    option.click()

这将找到页面上的第一个“SELECT”元素,并依次循环浏览其每个选项,打印出它们的值,然后依次选择每个选项。
正如您所看到的,这不是处理SELECT元素的最有效方法。WebDriver的支持类包括一个名为“Select”的类,它提供了与这些类交互的有用方法:

from selenium.webdriver.support.ui import Select
select = Select(driver.find_element(By.NAME, 'name'))
select.select_by_index(index)
select.select_by_visible_text("text")
select.select_by_value(value)

WebDriver还提供了取消选择所有选定选项的功能:

select = Select(driver.find_element(By.ID, 'id'))
select.deselect_all()

这将取消选择页面上特定SELECT中的所有选项。
假设在一个测试中,我们需要所有默认选择选项的列表,Select类提供了一个返回列表的属性方法:

select = Select(driver.find_element(By.XPATH, "//select[@name='name']"))
all_selected_options = select.all_selected_options

要获取所有可用选项:options = select.options

填写完表格后,您可能需要提交。一种方法是找到“提交”按钮并单击它:

# Assume the button has the ID "submit" :)
driver.find_element(By.ID, "submit").click()

或者,WebDriver在每个元素上都有方便的方法“提交”。如果在表单中的元素上调用this,WebDriver将遍历DOM,直到找到封闭的表单,然后对其调用submit。如果元素不在表单中,则将引发NoSuchElementException:

element.submit()

拖放

可以使用拖放,将一个图元移动一定量,也可以移动到另一个图元:

element = driver.find_element(By.NAME, "source")
target = driver.find_element(By.NAME, "target")

from selenium.webdriver import ActionChains
action_chains = ActionChains(driver)
action_chains.drag_and_drop(element, target).perform()

窗口和框架之间移动

现代web应用程序很少不具有任何框架或仅限于一个窗口。WebDriver支持使用“switch_to.window”方法在命名窗口之间移动:

driver.switch_to.window("windowName")

所有对驱动程序的调用现在都将被解释为指向特定窗口。但是你怎么知道窗户的名字?看看打开它的javascript或链接:

<a href="somewhere.html" target="windowName">Click here to open a new window</a>

或者,您可以将“窗口句柄”传递给“switch_to.window()”方法。知道了这一点,就可以对每个打开的窗口进行迭代,如下所示:

for handle in driver.window_handles:
    driver.switch_to.window(handle)

在一个网页中,有时会有嵌套的框架,例如HTML页面内包含一个或多个<iframe>元素,每个iframe可以包含独立的HTML内容。当你想要操作iframe内的元素时,就需要先切换到相应的iframe,然后才能执行操作。

您还可以从一帧转到另一帧(或转到iframe):

driver.switch_to.frame("frameName")

可以通过用点分隔路径来访问子帧,也可以通过索引来指定帧。即:

driver.switch_to.frame("frameName.0.child")

将转到名为“frameName”的帧的第一个子帧的名为“child”的帧。所有帧的评估都像从top开始一样。一旦我们完成了对框架的处理,我们将不得不回到父框架,可以使用以下方法完成:

driver.switch_to.default_content()

弹出对话框

Selenium WebDriver内置支持处理弹出对话框。在您触发将打开弹出窗口的操作后,您可以使用以下内容访问警报:

alert = driver.switch_to.alert

这将返回当前打开的警报对象。有了这个对象,您现在可以接受、取消、读取其内容,甚至可以在提示中键入。此界面同样适用于警报、确认和提示。有关更多信息,请参阅API文档。

跳转:历史

早些时候,我们介绍了使用“get”命令(driver.get())

driver.get("http://www.example.com")

要在浏览器的历史记录中前后移动:

driver.forward()
driver.back()

请注意,此功能完全取决于底层驱动程序。如果你习惯了一个浏览器对另一个浏览器的行为,那么当你调用这些方法时,可能会发生一些意想不到的事情。

Cookies

在进入本教程的下一节之前,您可能有兴趣了解如何使用cookie。首先,您需要在cookie有效的域上:

# Go to the correct domain
driver.get("http://www.example.com")

# Now set the cookie. This one's valid for the entire domain
cookie = {'name' : 'foo', 'value' : 'bar'}
driver.add_cookie(cookie)

# And now output all the available cookies for the current URL
driver.get_cookies()

定位元素

有多种策略可以定位页面中的元素。您可以使用最适合您的案例的。Selenium提供了以下方法来定位页面中的元素:find_element

要查找多个元素(这些方法将返回一个列表):find_elements

from selenium.webdriver.common.by import By

driver.find_element(By.XPATH, '//button[text()="Some text"]')
driver.find_elements(By.XPATH, '//button')

By类可用的属性用于定位页面上的元素。以下是可用于By类的属性:

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

“By”类用于指定用于定位页面上元素的属性。以下是使用属性定位页面上元素的各种方式:

find_element(By.ID, "id")
find_element(By.NAME, "name")
find_element(By.XPATH, "xpath")
find_element(By.LINK_TEXT, "link text")
find_element(By.PARTIAL_LINK_TEXT, "partial link text")
find_element(By.TAG_NAME, "tag name")
find_element(By.CLASS_NAME, "class name")
find_element(By.CSS_SELECTOR, "css selector")

如果要查找具有相同属性的多个元素,请将find_element替换为find_elements。

by Id

当您知道元素的id属性时,请使用此选项。使用此策略,将返回具有匹配id属性的第一个元素。如果没有元素具有匹配的id属性,则将引发NoSuchElementException。例如,考虑以下页面来源:

<html>
 <body>
  <form id="loginForm">
   <input name="username" type="text" />
   <input name="password" type="password" />
   <input name="continue" type="submit" value="Login" />
  </form>
 </body>
</html>

表单元素的位置如下所示:

login_form = driver.find_element(By.ID, 'loginForm')

by name

username = driver.find_element(By.NAME, 'username')
password = driver.find_element(By.NAME, 'password')
continue = driver.find_element(By.NAME, 'continue')

by XPath

XPath是用于在XML文档中定位节点的语言。由于HTML可以是XML(XHTML)的一种实现,Selenium用户可以利用这种强大的语言来定位其web应用程序中的元素。XPath支持按id或name属性定位的简单方法,并通过打开各种新的可能性(如定位页面上的第三个复选框)来扩展这些方法。

使用XPath的主要原因之一是当您没有合适的id或name属性来定位元素时。您可以使用XPath以绝对值(不建议)或相对于具有id或name属性的元素来定位元素。XPath定位器还可以用于通过id和name以外的属性指定元素。

Absolute XPaths包含根(html)中所有元素的位置,因此可能会失败,只需对应用程序进行最轻微的调整。通过查找附近具有id或name属性的元素(最好是父元素),您可以根据关系定位目标元素。这种情况不太可能改变,并且可以使您的测试更加健壮。
例如,考虑以下页面来源:

<html>
 <body>
  <form id="loginForm">
   <input name="username" type="text" />
   <input name="password" type="password" />
   <input name="continue" type="submit" value="Login" />
   <input name="continue" type="button" value="Clear" />
  </form>
</body>
</html>
login_form = driver.find_element(By.XPATH, "/html/body/form[1]")
login_form = driver.find_element(By.XPATH, "//form[1]")
login_form = driver.find_element(By.XPATH, "//form[@id='loginForm']")
  1. 绝对路径(如果只稍微更改HTML,则会中断)
  2. HTML中的第一个表单元素
  3. 属性id设置为loginForm的form元素

username元素的位置如下所示:

username = driver.find_element(By.XPATH, "//form[input/@name='username']")
username = driver.find_element(By.XPATH, "//form[@id='loginForm']/input[1]")
username = driver.find_element(By.XPATH, "//input[@name='username']")
  1. 第一个表单元素,其输入子元素的名称设置为username
  2. 属性id设置为loginForm的form元素的第一个输入子元素
  3. 属性名称设置为username的第一个输入元素

“清除”按钮元素的位置如下所示:

clear_button = driver.find_element(By.XPATH, "//input[@name='continue'][@type='button']")
clear_button = driver.find_element(By.XPATH, "//form[@id='loginForm']/input[4]")
  1. 属性名称设置为continue,属性类型设置为按钮的输入
  2. 属性id设置为loginForm的form元素的第四个输入子元素

这些示例涵盖了一些基础知识,但为了了解更多信息,建议使用以下参考资料:https://www.w3schools.com/xml/xpath_intro.asp

http://www.zvon.org/comp/r/tut-XPath_1.html

这里有几个非常有用的插件,可以帮助发现元素的XPath:
https://chrome.google.com/webstore/detail/xpath-helper/hgimnogjllphhhkhlmebbmlgjoejdpjl

by Link Text

当您知道定位标记中使用的链接文本时,请使用此选项。使用此策略,将返回链接文本与提供的值匹配的第一个元素。如果没有元素具有匹配的链接文本属性,则会引发NoSuchElementException。
例如,考虑以下页面来源:

<html>
 <body>
  <p>Are you sure you want to do this?</p>
  <a href="continue.html">Continue</a>
  <a href="cancel.html">Cancel</a>
</body>
</html>

continue.html链接的位置如下:

continue_link = driver.find_element(By.LINK_TEXT, 'Continue')
continue_link = driver.find_element(By.PARTIAL_LINK_TEXT, 'Conti')

by Tag Name

heading(h1)元素的位置如下所示:

heading1 = driver.find_element(By.TAG_NAME, 'h1')

by Class Name

content = driver.find_element(By.CLASS_NAME, 'content')

by CSS Selectors

content = driver.find_element(By.CSS_SELECTOR, 'p.content')

等待

如今,大多数网络应用程序都在使用AJAX技术。当浏览器加载页面时,该页面中的元素可能会以不同的时间间隔加载。这使得定位元素变得困难:如果DOM中还没有元素,则定位函数将引发ElementNotVisibleException异常。使用等待,我们可以解决这个问题。等待在执行的操作之间提供了一些间隙——主要是定位元素或对元素进行任何其他操作。
SeleniumWebdriver提供两种类型的等待——隐式和显式。显式等待使WebDriver在继续执行之前等待某个条件发生。在试图定位元素时,隐含的等待会使WebDriver轮询DOM一段时间。

显式等待

显式等待是您定义的代码,用于等待某个条件发生,然后再继续执行代码。这种情况的极端情况是time.sleep(),它将条件设置为等待的确切时间段。提供了一些方便的方法,可以帮助您编写只需等待所需时间的代码。WebDriverWait与ExpectedCondition相结合是实现这一目标的一种方法。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

在上面的代码中,Selenium将等待最多10秒,以便找到符合给定条件的元素。如果在该时间内未找到任何元素,则会引发TimeoutException。默认情况下,WebDriverWait每500毫秒调用一次ExpectedCondition,直到返回成功为止。ExpectedCondition在成功的情况下将返回true(布尔值),或者在找不到元素的情况下不返回null。

预期条件

在自动化web浏览器时,有一些常见的条件是经常使用的。下面列出了每个的名称。SeleniumPython绑定提供了一些方便的方法,因此您不必自己编写预期的condition类,也不必为它们创建自己的实用程序包。

  • title_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present

这是Selenium WebDriver 中用于等待特定条件发生的预定义条件(expected conditions)列表。这些条件主要用于等待页面加载、元素可见、元素可点击等操作。以下是对每个条件的简要介绍:

  1. title_is(title)

    • 等待页面的标题与给定的标题完全匹配。
  2. title_contains(title)

    • 等待页面的标题包含给定的标题。
  3. presence_of_element_located(locator)

    • 等待指定定位器(locator)的元素出现在DOM中。
  4. visibility_of_element_located(locator)

    • 等待指定定位器的元素在页面可见。
  5. visibility_of(element)

    • 等待给定元素在页面可见。
  6. presence_of_all_elements_located(locator)

    • 等待所有指定定位器的元素出现在DOM中。
  7. text_to_be_present_in_element(locator, text)

    • 等待指定定位器的元素文本包含给定文本。
  8. text_to_be_present_in_element_value(locator, text)

    • 等待指定定位器的元素值包含给定文本。
  9. frame_to_be_available_and_switch_to_it(frame_locator)

    • 等待指定定位器的iframe可用并切换到它。
  10. invisibility_of_element_located(locator)

    • 等待指定定位器的元素在页面不可见。
  11. element_to_be_clickable(locator)

    • 等待指定定位器的元素可被点击。
  12. staleness_of(element)

    • 等待给定元素不再附加到DOM中。
  13. element_to_be_selected(element)

    • 等待给定元素被选中。
  14. element_located_to_be_selected(locator)

    • 等待指定定位器的元素被选中。
  15. element_selection_state_to_be(element, is_selected)

    • 等待给定元素的选中状态为给定状态。
  16. element_located_selection_state_to_be(locator, is_selected)

    • 等待指定定位器的元素的选中状态为给定状态。
  17. alert_is_present()

    • 等待警告框(alert)出现。

这些条件可以与 WebDriverWait 结合使用,使得测试脚本能够更智能地等待特定的条件发生,而不是使用固定的等待时间。例子1:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

# 创建WebDriverWait对象
wait = WebDriverWait(driver, 10)  # 最多等待10秒

# 使用条件等待页面标题包含 "example"
wait.until(EC.title_contains("example"))

# 使用条件等待指定定位器的元素可见
element = wait.until(EC.visibility_of_element_located((By.ID, "myElement")))

例子2:

from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))

expected_conditions模块包含一组要与WebDriverWait一起使用的预定义条件。

自定义等待条件

当以前的方便方法都不符合您的要求时,您也可以创建自定义等待条件。可以使用带有__call__方法的类创建自定义等待条件,当条件不匹配时,该方法将返回False。

class element_has_css_class(object):
  """An expectation for checking that an element has a particular css class.

  locator - used to find the element
  returns the WebElement once it has the particular css class
  """
  def __init__(self, locator, css_class):
    self.locator = locator
    self.css_class = css_class

  def __call__(self, driver):
    element = driver.find_element(*self.locator)   # Finding the referenced element
    if self.css_class in element.get_attribute("class"):
        return element
    else:
        return False

# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

这段代码定义了一个名为 element_has_css_class 的类,它是一个自定义的等待条件,用于检查元素是否具有特定的 CSS 类。以下是代码的详细解释:

  1. element_has_css_class 类:

    • 这是一个自定义的等待条件类,它实现了一个期望条件的逻辑,该逻辑检查一个元素是否具有特定的 CSS 类。
    • 构造函数接受两个参数:locator 用于定位元素,css_class 是要检查的 CSS 类。
  2. __call__ 方法:

    • 这是一个特殊方法,用于在类的实例被调用时执行特定的操作。在这里,__call__ 方法被用来实现等待条件的逻辑。
    • __call__ 方法中,首先通过 driver.find_element(*self.locator) 定位到指定的元素。
    • 然后,通过 element.get_attribute("class") 获取该元素的所有 CSS 类。
    • 最后,检查 self.css_class 是否在元素的 CSS 类中,如果是,则返回该元素,表示条件满足;否则,返回 False
  3. 等待条件的使用:

    • 在测试中,通过创建 WebDriverWait 对象并使用 element_has_css_class 条件,可以等待直到元素具有特定的 CSS 类。
    • 在这个例子中,等待直到 id 为 ‘myNewInput’ 的元素具有名为 ‘myCSSClass’ 的 CSS 类。
# 创建WebDriverWait对象
wait = WebDriverWait(driver, 10)

# 使用条件等待元素具有特定的CSS类
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

这样的等待条件对于确保页面上的元素在特定的状态(例如,具有特定的样式)下再继续执行测试非常有用。

笔记:
polling2库
您也可以考虑使用需要单独安装的polling2库。
Polling2是一个强大的python实用程序,用于在满足指定条件时等待函数返回。

参考资料:https://polling2.readthedocs.io/en/latest/

隐式等待

当试图查找任何不立即可用的元素(或多个元素)时,隐式等待“告诉”WebDriver轮询DOM一段时间。默认设置为0(零)。一旦设置好,就会为WebDriver对象的生命周期设置隐式等待。

from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

页面对象-设计模式

本章是页面对象设计模式的教程介绍。页面对象是测试在web应用程序用户界面内进行交互的区域。
使用页面对象模式的好处:

  • 易于阅读的测试用例
  • 创建可在多个测试用例中共享的可重用代码
  • 减少重复代码的数量
  • 如果用户界面发生更改,则修复程序只需要在一个位置进行更改

测试样例

下面是一个测试案例,它在python.org网站上搜索一个单词并确保得到一些结果。以下部分将介绍定义页面对象的页面模块。

import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options


import page


class PythonOrgSearch(unittest.TestCase):
    """A sample test class to show how page object works"""

    def setUp(self):
        # 启动driver
        options = Options()
        # 替换为你本地的Chromed路径
        options.binary_location = r"D:\software\Chrome-bin\chrome.exe"
        # 替换为你本地的Chromedriver路径
        chromedriver_path = r'D:\software\Chrome-bin\chromedriver.exe'
        # 使用Service对象指定Chromedriver路径
        service = Service(chromedriver_path)
        driver = webdriver.Chrome(service=service, options=options)
        self.driver = driver
        self.driver.get("http://www.python.org")

    def test_search_in_python_org(self):
        """Tests python.org search feature. Searches for the word "pycon" then
        verified that some results show up.  Note that it does not look for
        any particular text in search results page. This test verifies that
        the results were not empty."""

        # Load the main page. In this case the home page of Python.org.
        main_page = page.MainPage(self.driver)
        # Checks if the word "Python" is in title
        self.assertTrue(main_page.is_title_matches(), "python.org title doesn't match.")
        # Sets the text of search textbox to "pycon"
        main_page.search_text_element = "pycon"
        main_page.click_go_button()
        search_results_page = page.SearchResultsPage(self.driver)
        # Verifies that the results page is not empty
        self.assertTrue(search_results_page.is_results_found(), "No results found.")

    def tearDown(self):
        self.driver.quit()


if __name__ == "__main__":
    unittest.main()

页面对象类

页面对象模式旨在为网页的每个部分创建一个对象。这种技术有助于在测试代码和与网页交互的实际代码之间建立分离。

page.py将如下所示:

from element import BasePageElement
from locators import MainPageLocators

class SearchTextElement(BasePageElement):
    """This class gets the search text from the specified locator"""

    #The locator for search box where search string is entered
    locator = 'q'


class BasePage(object):
    """Base class to initialize the base page that will be called from all
    pages"""

    def __init__(self, driver):
        self.driver = driver


class MainPage(BasePage):
    """Home page action methods come here. I.e. Python.org"""

    #Declares a variable that will contain the retrieved text
    search_text_element = SearchTextElement()

    def is_title_matches(self):
        """Verifies that the hardcoded text "Python" appears in page title"""

        return "Python" in self.driver.title

    def click_go_button(self):
        """Triggers the search"""

        element = self.driver.find_element(*MainPageLocators.GO_BUTTON)
        element.click()


class SearchResultsPage(BasePage):
    """Search results page action methods come here"""

    def is_results_found(self):
        # Probably should search for this text in the specific page
        # element, but as for now it works fine
        return "No results found." not in self.driver.page_source

页面元素

element.py将如下所示:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait


class BasePageElement(object):
    """Base page class that is initialized on every page object class."""

    def __set__(self, obj, value):
        """Sets the text to the value supplied"""

        driver = obj.driver
        WebDriverWait(driver, 100).until(
            lambda driver: driver.find_element(By.NAME, self.locator))
        driver.find_element(By.NAME, self.locator).clear()
        driver.find_element(By.NAME, self.locator).send_keys(value)

    def __get__(self, obj, owner):
        """Gets the text of the specified object"""

        driver = obj.driver
        WebDriverWait(driver, 100).until(
            lambda driver: driver.find_element(By.NAME, self.locator))
        element = driver.find_element(By.NAME, self.locator)
        return element.get_attribute("value")

定位器

其中一种做法是将定位器字符串与使用它们的位置分开。在本例中,同一页面的定位器属于同一类。
locators.py将如下所示:

from selenium.webdriver.common.by import By

class MainPageLocators(object):
    """A class for main page locators. All main page locators should come here"""

    GO_BUTTON = (By.ID, 'submit')

class SearchResultsPageLocators(object):
    """A class for search results locators. All search results locators should
    come here"""

    pass

这段代码是一个示例,演示了页面对象设计模式在使用 Selenium 进行 Web 应用程序测试时的应用。下面我将逐步解释代码的各个部分:

  1. 测试用例 (PythonOrgSearch 类)
    这是一个简单的测试用例,用于在 Python 官方网站上执行搜索操作并验证搜索结果是否存在。
    setUp 方法:在每个测试用例运行之前启动 WebDriver,并打开 Python 官方网站。
    test_search_in_python_org 方法:执行搜索操作,搜索关键词为 “pycon”,然后验证是否有搜索结果显示。

  2. 页面对象类 (MainPageSearchResultsPage 类)
    这些类是页面对象模式的体现,用于将页面的不同部分抽象成对象,使测试代码更易读、可维护。
    BasePage 类:所有页面对象类的基类,初始化时传入 WebDriver 对象。
    MainPage 类:表示 Python 官方网站的主页,包含了主页的元素和相应的操作方法。
    is_title_matches 方法:验证页面标题是否包含 “Python”。
    click_go_button 方法:模拟点击搜索按钮的操作。
    SearchResultsPage 类:表示搜索结果页面,包含了相应的验证方法。
    is_results_found 方法:验证是否找到了搜索结果。

  3. 页面元素类 (SearchTextElement 类)
    这是一个页面元素类,用于获取搜索文本框中的文本。它继承自 BasePageElement 类。
    BasePageElement 类:用于处理页面元素的基类,包含了设置和获取元素文本的方法。
    BasePageElement这个类还可以优化一下和locators组合,请各位自行尝试吧。

  4. 定位器类 (MainPageLocators 类)
    这是一个简单的定位器类,包含了在主页中使用的元素的定位器。

  5. 元素处理基类 (element.py)
    这是一个包含 BasePageElement 类的辅助模块,用于处理页面元素。

  6. 定位器模块 (locators.py)
    这个模块包含了页面元素的定位器类,用于将定位器字符串与页面对象分开。

  7. 测试脚本 (__main__ 部分)
    这部分是测试脚本的入口,它实例化了测试用例类并运行测试。

通过页面对象设计模式,代码更易读、易维护,测试用例的逻辑与页面结构分离,提高了代码的可重用性。这对于在页面结构变化时维护测试用例非常有帮助,只需要在页面对象类中进行修改。

总之,核心思想就是:拆分页面,元素,定位之间的耦合

API

参考资料:https://selenium-python.readthedocs.io/api.html

文章来源:https://blog.csdn.net/qq_19841133/article/details/135095411
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。