ZetCode

Python Selenium

最后修改于 2024 年 1 月 29 日

Python Selenium 教程演示了如何使用 Selenium 框架在 Python 中自动化 Web 应用程序测试。

Selenium

Selenium 是一个用于测试 Web 应用程序的可移植框架。Selenium 在 Windows、Linux 和 macOS 上运行。

Selenium WebDriver 是一组开源 API,用于自动化 Web 应用程序的测试。浏览器有专门的驱动程序,包括 Chrome、Firefox、Opera、Microsoft Edge。这些驱动程序需要下载并放置在 PATH 中。Selenium WebDriver 支持不同的编程语言,包括 Python、C# 和 Java。

Selenium 可以以全模式或无头模式运行。在无头模式下,浏览器不会启动。

Selenium 驱动程序

我们需要从 https://selenium-python.readthedocs.io/installation.html#drivers 下载我们使用的浏览器的驱动程序。驱动程序必须放置在 PATH 中,例如 /usr/bin//usr/local/bin/ 或当前工作目录。

Python Selenium 安装

使用以下命令安装 selenium 模块

$ pip install selenium

这将安装 selenium 模块。

Python Selenium Firefox 示例

对于 Firefox 浏览器,我们从 https://github.com/mozilla/geckodriver/releases 下载驱动程序(Windows 版为 geckodriver.exe)。

firefox_get_title_.py
#!/usr/bin/python

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options

opts = Options()
opts.headless = True

driver = Firefox(options=opts)

try:

    driver.get('http://webcode.me')
    print(browser.title)
    assert 'My html page' == driver.title

finally:

    driver.quit()

在示例中,我们使用 Firefox 的驱动程序测试网页标题。

opts = Options()
opts.headless = True

driver = Firefox(options=opts)

我们在无头模式下创建了一个驱动程序。浏览器不会启动。

try:

    driver.get('http://webcode.me')
    print(driver.title)
    assert 'My html page' == driver.title

我们向 webcode.me 页面发出 get 请求并获取其标题。我们对标题的内容进行断言。

finally:

    driver.quit()

最后,我们关闭驱动程序。

Python Selenium Chrome 示例

对于 Chrome 浏览器,我们从 sites.google.com/chromium.org/driver/ 下载驱动程序(Windows 版为 chromedriver.exe)。

chrome_get_title.py
#!/usr/bin/python

from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options

opts = Options()
opts.headless = True

driver = Chrome(options=opts, executable_path='chromedriver.exe')

try:
    driver.get('http://webcode.me')

    assert 'My html page!' == driver.title

finally:

    driver.quit()

在此示例中,我们使用 Chrome 浏览器。

driver = Chrome(options=opts, executable_path='chromedriver.exe')

我们创建了 Chrome 驱动程序的实例。executable_path 指向可执行文件;如果未指定,则假定可执行文件在 PATH 中。

assert 'My html page!' == driver.title

我们添加了一个额外的感叹号,以便测试失败。

> py chrome_get_title.py

DevTools listening on ws://127.0.0.1:61178/devtools/browser/14d2fd68-eb2a-415a-9bf0-53a0f7b388d6
My html page
Traceback (most recent call last):
  File "chrome_get_title.py", line 19, in <module>
    assert 'My html page!' == driver.title
AssertionError

Python Selenium 页面源代码

page_source 属性获取当前页面的源代码。

page_source.py
#!/usr/bin/python

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options

opts = Options()
opts.headless = True

driver = Firefox(options=opts)

try:

    driver.get('http://webcode.me')
    title = driver.title
    content = driver.page_source

    print(content)

    assert title == 'My html page'
    assert 'Today is a beautiful day' in content

finally:

    driver.quit()

在示例中,我们测试了页面源代码中的标题和特定文本。

Python Selenium 查找元素

我们可以使用 find_elements_by_tag_namefind_element_by_idfind_elements_by_class_name 等方法来定位 HTML 元素并获取其内容。

get_paragraphs.py
#!/usr/bin/python

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options

opts = Options()
opts.headless = True

driver = Firefox(options=opts)

try:

    driver.get('http://webcode.me')

    els = driver.find_elements_by_tag_name("p")

    for el in els:
        print(el.text)

finally:

    driver.close()

在示例中,我们获取并打印 webcode.me 主页上两个段落的文本。

els = driver.find_elements_by_tag_name("p")

我们使用 find_elements_by_tag_name 方法查找 p 标签。

for el in els:
    print(el.text)

我们遍历元素列表,并使用 text 属性打印其内容。

> py get_paragraphs.py
Today is a beautiful day. We go swimming and fishing.
Hello there. How are you?

Python Selenium 警告框

在接下来的示例中,我们展示了如何测试 JavaScript 警告框。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test page</title>
</head>
<body>

    <button id="mybtn" onclick="alert('Hello there!')">
        Click
    </button>

</body>
</html>

我们有一个带有按钮的 HTML 页面。当我们单击按钮时,会出现一个警告框。

alert_box.py
#!/usr/bin/python

import time
from pathlib import Path

from selenium.common.exceptions import TimeoutException
from selenium.webdriver import Firefox
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait

myfile = Path.cwd() / "index.html"
driver = Firefox()

try:
    driver.get(f'file://{myfile}')
    button = driver.find_element_by_id("mybtn")
    button.click()
    time.sleep(2)

    try:
        WebDriverWait(driver, 3).until(ec.alert_is_present(),
                                       'Timed out waiting for confirmation popup to appear')

        alert = driver.switch_to.alert
        assert alert.text == 'Hello there!'

        alert.accept()
        print("alert accepted")

    except TimeoutException:
        print("no alert")

    time.sleep(5)
finally:

    driver.quit()

在示例中,我们单击一个按钮元素并检查警告框的文本。

try:
    driver.get(f'file://{myfile}')
    button = driver.find_element_by_id("mybtn")
    button.click()
    time.sleep(2)

我们加载一个本地文件,找到按钮元素并单击它。在此示例中,浏览器会出现;因此,我们睡眠 2 秒,以便我们能看到发生了什么。

WebDriverWait(driver, 3).until(ec.alert_is_present(),
                               'Timed out waiting for confirmation popup to appear')

使用 WebDriverWait,我们等待 3 秒钟以让警告出现。如果警告框未出现,我们会提供一条错误消息。

alert = driver.switch_to.alert
assert alert.text == 'Hello there!'

我们检查警告框的文本。

Python Selenium unittest 示例

unittest 是一个 Python 单元测试框架。它是 JUnit 的 Python 版本,JUnit 是 Java 编程语言的原始单元测试框架。unittest 支持测试自动化、测试的设置和拆卸代码的共享、将测试聚合到集合中以及测试与报告框架的独立性。

unittest 提供了一个基类 TestCase,可用于创建新的测试用例。setUp 是一个钩子方法,用于在执行测试夹具之前进行设置,而 tearDown 是一个钩子方法,用于在测试后对测试夹具进行拆卸。

一个 测试夹具 表示执行一个或多个测试所需的准备工作以及任何相关的清理操作。这可能涉及例如创建临时或代理数据库、目录或启动服务器进程。一个 测试用例 是引用单个测试的测试函数。函数名称必须以 test 开头。检查是通过各种断言方法执行的,例如 assertInassertTrueassertEqual

一个 测试套件 是测试用例、测试套件或两者的集合。它用于聚合应一起执行的测试。一个 测试运行器 是一个协调测试执行并将结果提供给用户的组件。运行器可以使用图形界面、文本界面或返回特殊值来指示测试执行的结果。unittestpytestnose 是 Python 测试运行器的示例。

unittest_example.py
#!/usr/bin/python

import unittest

from selenium import webdriver
from selenium.webdriver.firefox.options import Options


class WebCode(unittest.TestCase):

    def setUp(self):
        opts = Options()
        opts.headless = True

        self.driver = webdriver.Firefox(options=opts)

    def test_title(self):

        self.driver.get("http://webcode.me")
        self.assertIn("My html page", self.driver.title)

    def test_paragraphs(self):

        self.driver.get("http://webcode.me")

        els = self.driver.find_elements_by_tag_name("p")

        self.assertIn('Today is a beautiful day', els[0].text)
        self.assertIn('Hello there', els[1].text)

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


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

我们有一个测试文件,其中我们检查 webcode.me 主页的标题和段落。

def setUp(self):
    opts = Options()
    opts.headless = True

    self.driver = webdriver.Firefox(options=opts)

setUp 方法中,我们设置了 Firefox 驱动程序。

def test_title(self):

    self.driver.get("http://webcode.me")
    self.assertIn("My html page", self.driver.title)

test_title 方法是一个单独的测试用例,它使用 assertIn 方法检查指定网页的标题。

def test_paragraphs(self):

    self.driver.get("http://webcode.me")

    els = self.driver.find_elements_by_tag_name("p")

    self.assertIn('Today is a beautiful day', els[0].text)
    self.assertIn('Hello there', els[1].text)

test_paragraphs 测试用例中,我们检查两个段落的内容。

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

tearDown 方法中,我们关闭驱动程序。

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

通过 main 方法,我们执行测试。

> py unittest_example.py
..
----------------------------------------------------------------------
Ran 2 tests in 13.273s

OK

我们运行测试。unittest 对成功执行的测试用例显示一个点,对失败的测试用例显示 F,对测试执行期间发生的错误显示 E。

Python Selenium 与 pytest

pytest 模块是一个用于测试 Python 应用程序的 Python 库。它是 nose 和 unittest 的替代品。

$ pip install pytest

我们安装 pytest 库。

pytest 在目录中查找 test_*.py*_test.py 文件。在选定的文件中,pytest 在类外部查找以 test 开头的测试函数,在以 Test 开头的测试类(不带 __init__ 方法)中查找以 test 开头的测试方法。

tests/test_web.py
#!/usr/bin/python

import pytest

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options


@pytest.fixture
def browser():

    opts = Options()
    opts.headless = True
    driver = Firefox(options=opts)

    driver.implicitly_wait(5)

    yield driver

    # For cleanup, quit the driver
    driver.quit()


def test_get_title(browser):
    browser.get("http://webcode.me")

    assert 'My html page' == browser.title

该示例展示了一个使用 pytest 模块的简单测试用例。

@pytest.fixture
def browser():

    opts = Options()
    opts.headless = True
    driver = Firefox(options=opts)
...

我们定义了一个 fixture。它设置了 Firefox 驱动程序。

driver.implicitly_wait(5)

我们在尝试交互之前隐式等待元素就绪。

yield driver

使用 yield 关键字,我们在 setup 结束时返回 driver 对象。

def test_get_title(browser):
    browser.get("http://webcode.me")

    assert 'My html page' == browser.title

我们有一个测试方法,用于检查网页的标题。它将浏览器 fixture 作为参数接收。

> pytest
============================== test session starts ==============================
platform win32 -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Jano\Documents\python\SeleniumPytest
collected 1 item

tests\test_web.py .                                                        [100%]

=============================== 1 passed in 6.31s ===============================

我们运行测试。

Python Selenium Flask 示例

在下一个示例中,我们将使用 pytest 和 Selenium 为 Flask Web 应用程序创建一个测试用例。我们测试 HTML 表单的响应。

app.py
├───static
│       greet.html
├───templates
│       index.html
└───tests
        web_test.py

这是项目结构。

static/greet.html
<!doctype html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Greet form</title>
</head>

<body>

    <p>Enter your name:</p>

    <form id="myform" action="greet">

        <input name="name" type="text">
        <button type="submit">Submit</button>

    </form>

</body>

</html>

我们在静态资源中有一个 greet 表单。该表单将文本值发送到 Flask 应用程序。

templates/index.html
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>Greeting</title>
</head>

<body>
    <p>
        Hello {{ name }}
    </p>
</body>

</html>

这是 Flask 模板文件,它将消息返回给客户端。

app.py
#!/usr/bin/python

from flask import Flask, render_template, request

app = Flask(__name__)


@app.route("/")
def home():
    return app.send_static_file('greet.html')


@app.route("/greet")
def greet():
    username = request.args.get('name')
    return render_template('index.html', name=username)


if __name__ == "__main__":
    app.run()

Flask 应用程序有两个路由:一个用于主页,一个用于问候。

tests/web_test.py
#!/usr/bin/python

import pytest

from selenium.webdriver import Firefox
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait


@pytest.fixture
def browser():
    opts = Options()
    opts.headless = True
    driver = Firefox(options=opts)

    driver.implicitly_wait(10)

    yield driver

    # For cleanup, quit the driver
    driver.quit()


def test_greet_form(browser):
    user_name = "John"

    browser.get('https://:5000/')

    form = browser.find_element_by_id("myform")
    name = browser.find_element_by_name("name")
    name.send_keys(user_name)

    form.submit()

    WebDriverWait(browser, 12).until(ec.url_matches('/greet'),
                                     'Timed out waiting for response')

    content = browser.page_source
    print(content)
    assert 'Hello John' in content

web_test.py 包含一个 greet 表单的测试用例。

def test_greet_form(browser):
    user_name = "John"

    browser.get('https://:5000/')
...

首先,我们获取 greet 表单。

form = browser.find_element_by_id("myform")
name = browser.find_element_by_name("name")
name.send_keys(user_name)

form.submit()

我们检索表单的元素。我们将测试用户名添加到 input 标签并提交表单。

WebDriverWait(browser, 12).until(ec.url_matches('/greet'),
                                 'Timed out waiting for response')

我们等待 Flask 重定向到 /greet 路由。

content = browser.page_source
print(content)
assert 'Hello John' in content

我们获取响应并检查响应中的消息。

> set FLASK_APP=app.py
> flask run

我们运行 Flask 应用程序。

> pytest
========================== test session starts ===========================
platform win32 -- Python 3.8.1, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: C:\Users\Jano\PycharmProjects\SeleniumPytestFlask
collected 1 item

tests\web_test.py .                                                 [100%]

=========================== 1 passed in 12.44s ===========================

从另一个 shell,我们执行 pytest

来源

Selenium 与 Python

在本文中,我们使用了 Python Selenium 框架。

作者

我叫 Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写超过 1400 篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Python 教程