現在の仕事には WebUI の自動テストは含まれていませんが、技術を向上させるために、もっと学んでおいて損はないので、ナンセンスな話はしません。現在の主流の WebUI テスト フレームワークは、保守性と拡張性を考慮すると、依然として Selenium であるべきです。再利用性などを考慮して、PO モードを使用してスクリプトを記述します。このドキュメントでも主に Selenium+PO モード+Pytest+Allure を統合しています。本題に入りましょう。注: Github アドレスは記事の最後に添付されています
技術的な前提: Python、Selenium、pytest の基本的な知識
1. プロジェクト構造ディレクトリ:
2. PO モードの概要
PO モードの機能:
- メンテナンスが簡単
- 高い再利用性
- スクリプトは読みやすく、理解しやすい
PO モードの要素:
1. PO モードでは、Webdriver インスタンスのみを実装するプロパティを持つ BasePage クラスに抽象的にカプセル化されます。
2. 各ページは BasePage を継承し、ドライバーを通じてこのページ内の要素を管理し、ページ内の操作を個別のメソッドにカプセル化します。
3. TestCase はページ クラスに依存して、対応するテスト ステップを実装します。
3. BasePage ページのカプセル化
import logging
import os
import time
from datetime import datetime
from time import sleep
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from Utils.myLog import MyLog
"""
此类封装所有操作,所有页面继承该类
"""
class BasePage(object):
def __init__(self, driver):
self.logger = MyLog().getLog()
self.driver = driver
# 等待元素可见
def wait_eleVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
"""
:param loc:元素定位表达;元组类型,表达方式(元素定位类型,元素定位方法)
:param timeout:等待的上限
:param poll_frequency:轮询频率
:param model:等待失败时,截图操作,图片文件中需要表达的功能标注
:return:None
"""
self.logger.info(f'等待"{model}"元素,定位方式:{loc}')
try:
start = datetime.now()
WebDriverWait(self.driver, timeout, poll_frequency).until(EC.visibility_of_element_located(loc))
end = datetime.now()
self.logger.info(f'等待"{model}"时长:{end - start}')
except TimeoutException:
self.logger.exception(f'等待"{model}"元素失败,定位方式:{loc}')
# 截图
self.save_webImgs(f"等待元素[{model}]出现异常")
raise
# 等待元素不可见
def wait_eleNoVisible(self, loc, timeout=30, poll_frequency=0.5, model=None):
"""
:param loc:元素定位表达;元组类型,表达方式(元素定位类型,元素定位方法)
:param timeout:等待的上限
:param poll_frequency:轮询频率
:param model:等待失败时,截图操作,图片文件中需要表达的功能标注
:return:None
"""
logging.info(f'等待"{model}"消失,元素定位:{loc}')
try:
start = datetime.now()
WebDriverWait(self.driver, timeout, poll_frequency).until_not(EC.visibility_of_element_located(loc))
end = datetime.now()
self.logger.info(f'等待"{model}"时长:{end - start}')
except TimeoutException:
self.logger.exception(f'等待"{model}"元素失败,定位方式:{loc}')
# 截图
self.save_webImgs(f"等待元素[{model}]消失异常")
raise
# 查找一个元素element
def find_element(self, loc, model=None):
self.logger.info(f'查找"{model}"元素,元素定位:{loc}')
try:
return self.driver.find_element(*loc)
except NoSuchElementException:
self.logger.exception(f'查找"{model}"元素失败,定位方式:{loc}')
# 截图
self.save_webImgs(f"查找元素[{model}]异常")
raise
# 查找元素elements
def find_elements(self, loc, model=None):
self.logger.info(f'查找"{model}"元素集,元素定位:{loc}')
try:
return self.driver.find_elements(*loc)
except NoSuchElementException:
self.logger.exception(f'查找"{model}"元素集失败,定位方式:{loc}')
# 截图
self.save_webImgs(f"查找元素集[{model}]异常")
raise
# 输入操作
def input_text(self, loc, text, model=None):
# 查找元素
ele = self.find_element(loc, model)
# 输入操作
self.logger.info(f'在"{model}"输入"{text}",元素定位:{loc}')
try:
ele.send_keys(text)
except:
self.logger.exception(f'"{model}"输入操作失败!')
# 截图
self.save_webImgs(f"[{model}]输入异常")
raise
# 清除操作
def clean_inputText(self, loc, model=None):
ele = self.find_element(loc, model)
# 清除操作
self.logger.info(f'清除"{model}",元素定位:{loc}')
try:
ele.clear()
except:
self.logger.exception(f'"{model}"清除操作失败')
# 截图
self.save_webImgs(f"[{model}]清除异常")
raise
# 点击操作
def click_element(self, loc, model=None):
# 先查找元素在点击
ele = self.find_element(loc, model)
# 点击操作
self.logger.info(f'点击"{model}",元素定位:{loc}')
try:
ele.click()
except:
self.logger.exception(f'"{model}"点击失败')
# 截图
self.save_webImgs(f"[{model}]点击异常")
raise
# 获取文本内容
def get_text(self, loc, model=None):
# 先查找元素在获取文本内容
ele = self.find_element(loc, model)
# 获取文本
self.logger.info(f'获取"{model}"元素文本内容,元素定位:{loc}')
try:
text = ele.text
self.logger.info(f'获取"{model}"元素文本内容为"{text}",元素定位:{loc}')
return text
except:
self.logger.exception(f'获取"{model}"元素文本内容失败,元素定位:{loc}')
# 截图
self.save_webImgs(f"获取[{model}]文本内容异常")
raise
# 获取属性值
def get_element_attribute(self, loc, name, model=None):
# 先查找元素在去获取属性值
ele = self.find_element(loc, model)
# 获取元素属性值
self.logger.info(f'获取"{model}"元素属性,元素定位:{loc}')
try:
ele_attribute = ele.get_attribute(name)
self.logger.info(f'获取"{model}"元素"{name}"属性集为"{ele_attribute}",元素定位:{loc}')
return ele_attribute
except:
self.logger.exception(f'获取"{model}"元素"{name}"属性失败,元素定位:{loc}')
# 截图
self.save_webImgs(f"获取[{model}]属性异常")
raise
# iframe 切换
def switch_iframe(self, frame_refer, timeout=30, poll_frequency=0.5, model=None):
# 等待 iframe 存在
self.logger.info('iframe 切换操作:')
try:
# 切换 == index\name\id\WebElement
WebDriverWait(self.driver, timeout, poll_frequency).until(
EC.frame_to_be_available_and_switch_to_it(frame_refer))
sleep(0.5)
self.logger.info('切换成功')
except:
self.logger.exception('iframe 切换失败!!!')
# 截图
self.save_webImgs(f"iframe切换异常")
raise
# 窗口切换 = 如果是切换到新窗口,new. 如果是回到默认的窗口,default
def switch_window(self, name, cur_handles=None, timeout=20, poll_frequency=0.5, model=None):
"""
调用之前要获取window_handles
:param name: new 代表最新打开的一个窗口. default 代表第一个窗口. 其他的值表示为窗口的 handles
:param cur_handles:
:param timeout:等待的上限
:param poll_frequency:轮询频率
:param model:等待失败时,截图操作,图片文件中需要表达的功能标注
:return:
"""
try:
if name == 'new':
if cur_handles is not None:
self.logger.info('切换到最新打开的窗口')
WebDriverWait(self.driver, timeout, poll_frequency).until(EC.new_window_is_opened(cur_handles))
window_handles = self.driver.window_handles
self.driver.swich_to.window(window_handles[-1])
else:
self.logger.exception('切换失败,没有要切换窗口的信息!!!')
self.save_webImgs("切换失败_没有要切换窗口的信息")
raise
elif name == 'default':
self.logger.info('切换到默认页面')
self.driver.switch_to.default()
else:
self.logger.info('切换到为 handles 的窗口')
self.driver.swich_to.window(name)
except:
self.logger.exception('切换窗口失败!!!')
# 截图
self.save_webImgs("切换失败_没有要切换窗口的信息")
raise
# 截图
def save_webImgs(self, model=None):
# filepath = 指图片保存目录/model(页面功能名称)_当前时间到秒.png
# 截图保存目录
# 拼接日志文件夹,如果不存在则自动创建
cur_path = os.path.dirname(os.path.realpath(__file__))
now_date = time.strftime('%Y-%m-%d', time.localtime(time.time()))
screenshot_path = os.path.join(os.path.dirname(cur_path), f'Screenshots\\{now_date}')
if not os.path.exists(screenshot_path):
os.mkdir(screenshot_path)
# 当前时间
dateNow = time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
# 路径
filePath = '{}\\{}_{}.png'.format(screenshot_path, model, dateNow)
try:
self.driver.save_screenshot(filePath)
self.logger.info(f"截屏成功,图片路径为{filePath}")
except:
self.logger.exception('截屏失败!')
# 退出
def get_driver(self):
return self.driver
4. ページは BasPage から継承します
from Common.basePage import BasePage
from selenium.webdriver.common.by import By
from time import sleep
class BaiduIndex(BasePage):
"""
页面元素
"""
# 百度首页链接
baidu_index_url = "https://www.baidu.com"
# 搜索框
search_input = (By.ID, "kw")
# "百度一下"按钮框
search_button = (By.ID, "su")
# 查询操作
def search_key(self, search_key):
self.logger.info("【===搜索操作===】")
# 等待用户名文本框元素出现
self.wait_eleVisible(self.search_input, model='搜索框')
# 输入内容
self.input_text(self.search_input, "阿崔", model="搜索框")
# 清除文本框内容
self.clean_inputText(self.search_input, model='搜索框')
# 输入用户名
self.input_text(self.search_input, text=search_key, model='搜索框')
# 等待搜索按钮出现
self.wait_eleVisible(self.search_button, model='"百度一下"搜索按钮')
# 点击搜索按钮
self.click_element(self.search_button, model='"百度一下"搜索按钮')
# 搜索后等待界面加载完成
self.driver.implicitly_wait(10)
sleep(3)
5. pytest+allure でテスト ケースを作成する
注: Pytest 統合 Allure チュートリアルを参照してください: https://www.cnblogs.com/huny/p/13752406.html
import os
import time
import pytest
import allure
from time import sleep
from selenium import webdriver
from PageObject.baiduIndex import BaiduIndex
driver = webdriver.Chrome()
baidu_index = BaiduIndex(driver)
@pytest.fixture(scope="class")
def init():
# 打开浏览器,访问登录页面
baidu_index.logger.info("\nWebDriver 正在初始化...")
driver.get(baidu_index.baidu_index_url)
baidu_index.logger.info(f"打开链接: {baidu_index.baidu_index_url}...")
# 窗口最大化
driver.maximize_window()
# 隐式等待
driver.implicitly_wait(10)
baidu_index.logger.info("WebDriver 初始化完成!")
yield
driver.quit()
baidu_index.logger.info("WebDriver 成功退出...")
@allure.feature("百度搜索")
class TestBaiduSearch:
@allure.story("搜索指定关键字")
@pytest.mark.baidu_search
@pytest.mark.parametrize("key_word", [
"哈哈",
"呵呵",
], )
def test_search(self, init, key_word):
# @pytest.mark.parametrize 参数化
baidu_index.search_key(key_word)
web_title = driver.title
assert "哈哈_百度搜索" == web_title
6. Allure テストレポートの生成
Github アドレス: https://github.com/Zimo6/Selenium_Demo
以下はサポート学習教材です。[ソフトウェア テスト] を行う友人にとって、これは最も包括的で完全な準備倉庫となるはずです。この倉庫は、最も困難な旅を私に同行させてくれました。あなたにも役立つことを願っています。
ソフトウェアテストインタビューアプレット
ソフトウェア テストの質問バンクには、何百万人もの人が参加しました。!!誰が知っているのか!!!ネットワーク全体で最も包括的なクイズ ミニ プログラムです。携帯電話、地下鉄、バスの中でクイズを行うことができます。
次の面接の質問セクションが取り上げられます。
1. ソフトウェアテストの基礎理論、2. Web、アプリ、インターフェース機能テスト、3. ネットワーク、4. データベース、5. Linux
6. Web、アプリ、インターフェイスの自動化、7. パフォーマンス テスト、8. プログラミングの基本、9. 時間面接の質問、10. 公開テストの質問、11. セキュリティ テスト、12. コンピューターの基本
情報取得方法: