如何在Python中使用Selenium读取测试中的配置文件

哈ha Python质量检查工程师课程开始前夕,我们为您准备了另一篇有趣的翻译。










本文中的教程将帮助您测试Web界面。我们将使用PythonpytestSelenium WebDriver创建一个简单而强大的Web界面测试解决方案我们将研究构建好的测试的策略和编写好的自动化测试的模式。当然,开发的测试项目可以作为创建自己的测试用例的良好基础。



哪个浏览器?



上一章中的DuckDuckGo搜索测试工作正常,但仅适用于Chrome。让我们再看一下灯具browser



@pytest.fixture
def browser():
  driver = Chrome()
  driver.implicitly_wait(10)
  yield driver
  driver.quit()


驱动程序类型和超时是硬编码的。对于概念验证,这可能不错,但是生产测试需要能够在运行时进行配置。 Web界面测试应可在任何浏览器中使用。如果某些环境的运行速度比其他环境慢,则应调整默认超时值。用户名和密码等敏感数据也不应出现在源代码中。您如何处理此类测试数据



所有这些值都是自动化测试系统的配置数据。它们是离散的价值,系统地影响自动化的工作方式。每次测试运行时,配置数据都必须输入。任何与测试和环境配置有关的内容都应视为配置数据,以便可以重复使用自动化代码。



输入来源



在自动化测试系统中,有几种读取输入数据的方法:



  • 命令行参数;
  • 环境变量;
  • 系统属性;
  • 配置文件;
  • API请求。


不幸的是,大多数测试框架不支持从命令行参数读取数据。环境变量和系统属性难以管理,并且可能存在潜在的危险。服务API是一种使用输入的好方法,尤其是从密钥管理服务(例如AWS KMS或Azure Key Vault)获取机密(例如密码)时。但是,为此类功能付费可能是不可接受的,并且编写自己的文字是不明智的。在这种情况下,配置文件是最佳选择。



配置文件是包含配置数据的常规文件。自动化测试可以在运行测试时读取它并使用输入值来驱动测试。例如,配置文件可能会指定示例项目中用作浏览器固定装置的浏览器类型。通常,配置文件采用JSON,YAML或INI等标准格式。它们还应该是扁平的,以便可以与其他文件轻松区分开。



我们的配置文件



让我们为测试项目编写一个配置文件。我们将使用JSON格式,因为它易于使用,流行并且具有清晰的层次结构。另外,json模块是一个Python标准库,可轻松将JSON文件转换为字典。创建一个名为的新文件,tests/config.json并添加以下代码:



{
  "browser": "chrome",
  "wait_time": 10
}


JSON使用键值对。正如我们所说的,我们的项目中有两个配置值:浏览器选择和超时。这里的“浏览器”是一个字符串,“ wait_time”是一个整数。



用pytest读取配置文件



夹具是使用pytest读取配置文件的最佳方法。它们可用于在开始测试之前读取配置文件,然后将值插入测试甚至其他夹具中。将以下灯具添加到tests/test_web.py



import json

@pytest.fixture(scope='session')
def config():
  with open('tests/config.json') as config_file:
    data = json.load(config_file)
  return data


夹具使用json模块config读取文件tests/config.json并将其解析为字典。硬编码的文件路径是相当普遍的做法。实际上,许多自动化工具和系统都将检查多个目录中的文件或是否使用命名模式。固定装置的范围设置为“会话”,因此固定装置将在每个测试会话中运行一次。在新的测试中,不必每次都读取相同的配置文件-这样效率低下!



初始化WebDriver时需要配置输入。browser如下更新夹具



@pytest.fixture
def browser(config):
  if config['browser'] == 'chrome':
    driver = Chrome()
  else:
    raise Exception(f'"{config["browser"]}" is not a supported browser')

  driver.implicitly_wait(config['wait_time'])
  yield driver
  driver.quit()


browser现在 ,灯具将具有灯具依赖关系config即使config每次测试会话启动一次,浏览器仍会在每次测试之前被调用。现在,我browser有了一个链if-else来确定要使用哪种类型的WebDriver。目前,仅支持Chrome,但我们很快将添加更多类型。如果未检测到浏览器,将引发异常。隐式超时也将从配置文件中获取其值。



由于browser它仍然返回WebDriver实例,因此不需要重构使用它的测试!让我们运行测试以确保配置文件有效:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 5.00 seconds ===========================


添加新的浏览器



现在我们的项目有了一个配置文件,我们可以使用它来更改浏览器。让我们在Mozilla Firefox而不是Google Chrome上运行测试。为此,请下载并安装最新版本的Firefox,然后下载最新的geckodriver(Firefox驱动程序)。确保它geckodriver也在系统路径中。



更新灯具代码browser以与Firefox一起使用:



from selenium.webdriver import Chrome, Firefox

@pytest.fixture
def browser(config):
  if config['browser'] == 'chrome':
    driver = Chrome()
  elif config['browser'] == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config["browser"]}" is not a supported browser')

  driver.implicitly_wait(config['wait_time'])
  yield driver
  driver.quit()


然后在配置文件中添加一个选项«firefox»



{
  "browser": "firefox",
  "wait_time": 10
}


现在重新开始测试,您将看到一个Firefox窗口而不是Chrome!







验证方式



尽管配置文件可以工作,但是在处理逻辑上仍然存在很大的缺陷:在运行测试之前不检查数据。browser如果未正确选择浏览器,则固定装置将引发异常,但每次测试都会发生。如果每个测试会话抛出一次此类异常,则效率会更高。此外,如果配置文件中缺少“浏览器”“ wait_time”键,则测试将失败。让我们解决这个问题。



添加新的夹具以验证浏览器选择:



@pytest.fixture(scope='session')
def config_browser(config):
  if 'browser' not in config:
    raise Exception('The config file does not contain "browser"')
  elif config['browser'] not in ['chrome', 'firefox']:
    raise Exception(f'"{config["browser"]}" is not a supported browser')
  return config['browser']


夹具config_browser取决于配置夹具另外,像config一样,它的作用域为“会话”。如果配置文件中没有“浏览器”键或不支持所选的浏览器,我们将得到一个例外。最后,它返回选定的浏览器,以便测试和其他固定装置可以安全地访问此值。



接下来是用于超时验证的以下夹具:



@pytest.fixture(scope='session')
def config_wait_time(config):
  return config['wait_time'] if 'wait_time' in config else 10


如果在配置文件中指定了超时,灯具config_wait_time将返回它。否则,它将默认返回10秒。再次



更新灯具browser以使用新的验证灯具:



@pytest.fixture
def browser(config_browser, config_wait_time):
  if config_browser == 'chrome':
    driver = Chrome()
  elif config_browser == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config_browser}" is not a supported browser')

  driver.implicitly_wait(config_wait_time)
  yield driver
  driver.quit()


为配置数据的每个值编写单独的夹具功能,可以使它们简单,清晰和具体。它们还允许您仅声明发送请求所需的那些值。



运行测试并确保一切正常:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 4.58 seconds ===========================


太酷了!但是,为了使验证更加现实,您必须要很棘手。让我们将“浏览器”的值更改“ Safari”(不受支持的浏览器)。



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py E                                                      [100%]

==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________

config = {'browser': 'safari', 'wait_time': 10}

    @pytest.fixture(scope='session')
    def config_browser(config):
      # Validate and return the browser choice from the config data
      if 'browser' not in config:
        raise Exception('The config file does not contain "browser"')
      elif config['browser'] not in SUPPORTED_BROWSERS:
>       raise Exception(f'"{config["browser"]}" is not a supported browser')
E       Exception: "safari" is not a supported browser

tests/conftest.py:30: Exception
=========================== 1 error in 0.09 seconds ============================


哇!该错误清楚地表明了它为什么出现。现在,如果我们从配置文件中删除浏览器选择,会发生什么?



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py E                                                      [100%]

==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________

config = {'wait_time': 10}

    @pytest.fixture(scope='session')
    def config_browser(config):
      # Validate and return the browser choice from the config data
      if 'browser' not in config:
>       raise Exception('The config file does not contain "browser"')
E       Exception: The config file does not contain "browser"

tests/conftest.py:28: Exception
=========================== 1 error in 0.10 seconds ============================


优秀的!另一个有用的错误消息。对于最后一个测试,让我们添加一个浏览器选择,但删除超时:



$ pipenv run python -m pytest tests/test_web.py 
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item                                                               

tests/test_web.py .                                                      [100%]

=========================== 1 passed in 4.64 seconds ===========================


该测试应该运行,因为超时是可选的。好吧,我们所做的更改是有益的!请记住,有时您还需要测试您的测试



最后一个考试



为了使测试代码更整洁,我们还可以做两件事。首先,让我们将网络设备移到一个文件中,conftest.py以便所有测试都可以使用它们,而不仅仅是test / test_web.py中的测试。其次,让我们将一些文字值放入模块变量中。



创建一个tests/conftest.py使用以下代码命名的新文件



import json
import pytest

from selenium.webdriver import Chrome, Firefox


CONFIG_PATH = 'tests/config.json'
DEFAULT_WAIT_TIME = 10
SUPPORTED_BROWSERS = ['chrome', 'firefox']


@pytest.fixture(scope='session')
def config():
  # Read the JSON config file and returns it as a parsed dict
  with open(CONFIG_PATH) as config_file:
    data = json.load(config_file)
  return data


@pytest.fixture(scope='session')
def config_browser(config):
  # Validate and return the browser choice from the config data
  if 'browser' not in config:
    raise Exception('The config file does not contain "browser"')
  elif config['browser'] not in SUPPORTED_BROWSERS:
    raise Exception(f'"{config["browser"]}" is not a supported browser')
  return config['browser']


@pytest.fixture(scope='session')
def config_wait_time(config):
  # Validate and return the wait time from the config data
  return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME


@pytest.fixture
def browser(config_browser, config_wait_time):
  # Initialize WebDriver
  if config_browser == 'chrome':
    driver = Chrome()
  elif config_browser == 'firefox':
    driver = Firefox()
  else:
    raise Exception(f'"{config_browser}" is not a supported browser')

  # Wait implicitly for elements to be ready before attempting interactions
  driver.implicitly_wait(config_wait_time)
  
  # Return the driver object at the end of setup
  yield driver
  
  # For cleanup, quit the driver
  driver.quit()


现在,完整的内容tests/test_web.py应该更简单,更简洁:



import pytest

from pages.result import DuckDuckGoResultPage
from pages.search import DuckDuckGoSearchPage


def test_basic_duckduckgo_search(browser):
  # Set up test case data
  PHRASE = 'panda'

  # Search for the phrase
  search_page = DuckDuckGoSearchPage(browser)
  search_page.load()
  search_page.search(PHRASE)

  # Verify that results appear
  result_page = DuckDuckGoResultPage(browser)
  assert result_page.link_div_count() > 0
  assert result_page.phrase_result_count(PHRASE) > 0
  assert result_page.search_input_value() == PHRASE


好了,这已经是Python风格了!



下一步是什么?



因此,我们测试项目的示例代码已完成。您可以将其用作创建新测试的基础。您还可以在GitHub找到该项目的最终示例但是,我们已完成编写代码的事实并不意味着我们已完成培训。在以后的文章中,我们将讨论如何将Python测试自动化提升到新的水平!






All Articles