first commit

This commit is contained in:
casual 2023-08-17 14:55:39 +08:00
commit ab94f0ca7a
20 changed files with 1236 additions and 0 deletions

129
.gitignore vendored Normal file
View File

@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

View File

@ -0,0 +1,45 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="ignoredPackages">
<value>
<list size="16">
<item index="0" class="java.lang.String" itemvalue="yagmail" />
<item index="1" class="java.lang.String" itemvalue="pytest-rerunfailures" />
<item index="2" class="java.lang.String" itemvalue="paramiko" />
<item index="3" class="java.lang.String" itemvalue="PyYAML" />
<item index="4" class="java.lang.String" itemvalue="mitmproxy" />
<item index="5" class="java.lang.String" itemvalue="requests" />
<item index="6" class="java.lang.String" itemvalue="PyMySQL" />
<item index="7" class="java.lang.String" itemvalue="xlrd" />
<item index="8" class="java.lang.String" itemvalue="markupsafe" />
<item index="9" class="java.lang.String" itemvalue="mysqlclient" />
<item index="10" class="java.lang.String" itemvalue="beautifulsoup4" />
<item index="11" class="java.lang.String" itemvalue="pywin32" />
<item index="12" class="java.lang.String" itemvalue="keyboard" />
<item index="13" class="java.lang.String" itemvalue="pytest" />
<item index="14" class="java.lang.String" itemvalue="allure-python-commons" />
<item index="15" class="java.lang.String" itemvalue="allure-pytest" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="page.page_login.test_login" />
<option value="raw_input" />
<option value="libs.utils" />
<option value="globalvar" />
<option value="pom.login_page.alert" />
<option value="pom.login_page.text" />
<option value="pom.login_page.alert_text" />
<option value="utils.config.root_path" />
<option value="utils.config.case_path" />
<option value="common.read_yaml.result" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/playwright-master.iml" filepath="$PROJECT_DIR$/.idea/playwright-master.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# playwright-master
> 基于 playwright 和 pytest 单元测试框架的自动化项目
### 实现功能
* 使用python3 -m playwright codegen进行录制自动生成代码脚本直接复制到test_cases中使用
* 将登录前置到conftest.py中避免执行每个用例都登录一次
* 日志记录运行情况
* pytest-html查看测试结果
### 安装依赖库
```shell
$ pip install -r requirements.txt
```
依赖库
```
playwright==1.36.0
pytest-playwright==0.4.2
pytest==7.4.0
pytest-html==3.2.0
pytest-rerunfailures==10.2
seldom==3.2.3
```
注:安装```requirements.txt```指定依赖库的版本,这是经过测试的,有时候新的版本可会有错。
### 配置
`config.py` 文件配置
```python
class RunConfig:
"""
运行测试配置
"""
# 运行测试用例的目录或文件
cases_path = "test_cases/test_001.py"
# 配置浏览器驱动类型(chromium, firefox, webkit)。
browser = "chromium"
# 运行模式headless, headful
mode = "headful"
# 配置运行的 URL
url = "https://www.baidu.com"
# 失败重跑次数
rerun = "0"
# 当达到最大失败数,停止执行
max_fail = "5"
```
### 运行
运行测试
```shell
$ python run.py
```

150
conftest.py Normal file
View File

@ -0,0 +1,150 @@
import os
import pytest
from py.xml import html
from tools.config import RunConfig
from playwright.async_api import Playwright
from tools.get_log import GetLog
logger = GetLog.get_log()
# 项目目录配置
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
REPORT_DIR = BASE_DIR + "/test_report/"
# 定义基本测试环境
@pytest.fixture(scope='session')
def base_url():
return RunConfig.url
# 定义全局登录
@pytest.fixture(scope="session", autouse=True)
def page(base_url, playwright: Playwright):
browser = playwright.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto(base_url)
logger.info("打开浏览器进入首页:{}".format(base_url))
page.get_by_role("link", name="登录").click()
page.get_by_placeholder("手机号码").click()
page.get_by_placeholder("手机号码").fill("18211111111")
page.get_by_placeholder("密码").click()
page.get_by_placeholder("密码").fill("111111")
page.get_by_role("button", name="登录").click()
logger.info("完成前置登录操作:{}".format(18211111111))
yield page
page.close()
logger.info("测试完成,关闭页面!!!")
# ---------------------
context.close()
browser.close()
logger.info("关闭浏览器!!!")
# 视频录制
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
"""
pytest-playwrigt 内置钩子
:param browser_context_args:
:param tmpdir_factory:
:return:
"""
return {
**browser_context_args,
"record_video_dir": os.path.join(REPORT_DIR, "videos") # 开始录制时并不知道测试结果正确与否,所以成功失败都会录制
}
# 设置用例描述表头
def pytest_html_results_table_header(cells):
cells.insert(2, html.th('Description'))
cells.pop()
# 设置用例描述表格
def pytest_html_results_table_row(report, cells):
cells.insert(2, html.td(report.description))
cells.pop()
@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
"""
用于向测试用例中添加用例的开始时间内部注释和失败截图等.
:param item:
"""
pytest_html = item.config.pluginmanager.getplugin('html')
outcome = yield
report = outcome.get_result()
report.description = description_html(item.function.__doc__)
extra = getattr(report, 'extra', [])
if "page" not in item.funcargs:
return "page not in item.funcargs"
page = item.funcargs["page"]
if report.when == 'call':
xfail = hasattr(report, 'wasxfail')
if (report.skipped and xfail) or (report.failed and not xfail):
case_path = report.nodeid.replace("::", "_") + ".png"
if "[" in case_path:
case_name = case_path.split("-")[0] + "].png"
else:
case_name = case_path
capture_screenshots(case_name, page)
img_path = "image/" + case_name.split("/")[-1]
if img_path:
html = '<div><img src="%s" alt="screenshot" style="width:304px;height:228px;" ' \
'onclick="window.open(this.src)" align="right"/></div>' % img_path
extra.append(pytest_html.extras.html(html))
report.extra = extra
def description_html(desc):
"""
将用例中的描述转成HTML对象
:param desc: 描述
:return:
"""
if desc is None:
return "No case description"
desc_ = ""
for i in range(len(desc)):
if i == 0:
pass
elif desc[i] == '\n':
desc_ = desc_ + ";"
else:
desc_ = desc_ + desc[i]
desc_lines = desc_.split(";")
desc_html = html.html(
html.head(
html.meta(name="Content-Type", value="text/html; charset=latin1")),
html.body(
[html.p(line) for line in desc_lines]))
return desc_html
def capture_screenshots(case_name, page):
"""
配置用例失败截图路径
:param case_name: 用例名
:return:
"""
global driver
file_name = case_name.split("/")[-1]
print("qhh", RunConfig.NEW_REPORT)
if RunConfig.NEW_REPORT is None:
raise NameError('没有初始化测试报告目录')
else:
image_dir = os.path.join(RunConfig.NEW_REPORT, "image", file_name)
page.screenshot(path=image_dir)
if __name__ == "__main__":
capture_screenshots("test_cases/test_baidu_search.test_search_python.png")

0
element/__init__.py Normal file
View File

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
playwright==1.36.0
pytest-playwright==0.4.2
pytest==7.4.0
pytest-html==3.2.0
pytest-rerunfailures==10.2
seldom==3.2.3

62
run.py Normal file
View File

@ -0,0 +1,62 @@
# coding=utf-8
import os
import time
import logging
import pytest
from conftest import REPORT_DIR
from tools.config import RunConfig
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
'''
说明
1用例创建原则测试文件名必须以test开头测试函数必须以test开头
2运行方式
> python run.py
'''
def init_env(new_report):
"""
初始化测试报告目录
"""
os.mkdir(new_report)
os.mkdir(new_report + "/image")
def run_tests():
logger.info("开始测试✨✨✨!")
now_time = time.strftime("%Y%m%d-%H%M%S")
RunConfig.NEW_REPORT = os.path.join(REPORT_DIR, now_time)
init_env(RunConfig.NEW_REPORT)
html_report = os.path.join(RunConfig.NEW_REPORT, "report.html")
xml_report = os.path.join(RunConfig.NEW_REPORT, "junit-xml.xml")
if RunConfig.mode == "headless":
pytest.main(["-s", "-v", RunConfig.cases_path,
"--browser=" + RunConfig.browser,
"--html=" + html_report,
"--junit-xml=" + xml_report,
"--self-contained-html",
"--maxfail", RunConfig.max_fail,
"--reruns", RunConfig.rerun])
if RunConfig.mode == "headful":
pytest.main(["-s", "-v", RunConfig.cases_path,
"--browser=" + RunConfig.browser,
"--html=" + html_report,
"--junit-xml=" + xml_report,
"--self-contained-html",
"--maxfail", RunConfig.max_fail,
"--reruns", RunConfig.rerun])
# pytest.main(["-s", "-v", "--headful", RunConfig.cases_path,
# "--browser=" + RunConfig.browser,
# "--html=" + html_report,
# "--junit-xml=" + xml_report,
# "--self-contained-html",
# "--maxfail", RunConfig.max_fail,
# "--reruns", RunConfig.rerun])
logger.info("测试结束,生成测试报告💕 💕 💕 ")
if __name__ == '__main__':
run_tests()

0
test_cases/__init__.py Normal file
View File

47
test_cases/test_001.py Normal file
View File

@ -0,0 +1,47 @@
import os
from playwright.sync_api import Playwright, sync_playwright
from playwright.sync_api import Page, expect
from tools.get_log import GetLog
logger = GetLog.get_log()
def test_run(page: Page) -> None:
"""
名称阅读书籍
步骤
1点击搜索框
2输入金牌卧底
3点击搜索按钮
4搜索结果页点击书籍名称进入书籍详情开始阅读
检查点
* 书籍章节标题第一章 让我泡她
"""
casename = os.path.splitext(os.path.basename(__file__))[0]
logger.info("正在执行用例:{}".format(casename))
page.get_by_placeholder("书名、作者、关键字").click()
page.get_by_placeholder("书名、作者、关键字").fill("金牌卧底")
page.locator("#btnSearch i").click()
page.get_by_role("link", name="金牌卧底").click()
page.get_by_role("link", name="点击阅读").click()
# Click text=2月上架的月卡
#page.click("text=2月上架的月卡")
article_title = page.get_by_role("heading", name="第一章 让我泡她")
print(article_title)
try:
assert article_title is not None
logger.info("用例{}的断言结果为{}".format(casename,article_title is not None))
except Exception as e:
logger.error("用例{}的断言结果为{},错误信息是:{}".format(casename, article_title is not None, e))
raise
# Close page
# page.close()
#
# # ---------------------
# context.close()
# browser.close()
#
#
# with sync_playwright() as playwright:
# run(playwright)

47
test_cases/test_002.py Normal file
View File

@ -0,0 +1,47 @@
import os
from playwright.sync_api import Playwright, sync_playwright
from playwright.sync_api import Page, expect
from tools.get_log import GetLog
logger = GetLog.get_log()
def test_run(page: Page) -> None:
"""
名称查看个人中心
步骤
1点击登录的账号进入个人中心
2点击全部收藏
3点击我的书评
4点击我的反馈
5点击账号设置
6
检查点
* 我的昵称是18211111111
"""
casename = os.path.splitext(os.path.basename(__file__))[0]
logger.info("正在执行用例:{}".format(casename))
page.get_by_role("link", name="18211111111").click()
page.get_by_role("link", name="全部收藏 >").click()
page.get_by_role("link", name="我的书评").click()
page.get_by_role("link", name="我的反馈").click()
page.get_by_role("link", name="账号设置").click()
my_name = page.get_by_role("link", name="18211111111[修改]")
print(my_name)
try:
assert my_name is not None
logger.info("用例{}的断言结果为{}".format(casename,my_name is not None))
except Exception as e:
logger.error("用例{}的断言结果为{},错误信息是:{}".format(casename, my_name is not None, e))
raise
# Close page
# page.close()
#
# # ---------------------
# context.close()
# browser.close()
#
#
# with sync_playwright() as playwright:
# run(playwright)

44
test_cases/test_003.py Normal file
View File

@ -0,0 +1,44 @@
import os
from playwright.sync_api import Playwright, sync_playwright
from playwright.sync_api import Page, expect
from tools.get_log import GetLog
logger = GetLog.get_log()
def test_run(page: Page) -> None:
"""
名称查看各个排行榜
步骤
1点击排行榜
2点击新书榜
3点击更新榜
4点击评论榜
检查点
* 存在评论榜元素
"""
casename = os.path.splitext(os.path.basename(__file__))[0]
logger.info("正在执行用例:{}".format(casename))
page.get_by_role("link", name="排行榜").click()
page.get_by_role("link", name="新书榜").click()
page.get_by_role("link", name="更新榜").click()
page.get_by_role("link", name="评论榜").click()
pinglun_bang = page.get_by_role("heading", name="评论榜")
print(pinglun_bang)
try:
assert pinglun_bang is not None
logger.info("用例{}的断言结果为{}".format(casename,pinglun_bang is not None))
except Exception as e:
logger.error("用例{}的断言结果为{},错误信息是:{}".format(casename, pinglun_bang is not None, e))
raise
# Close page
#page.close()
# ---------------------
#context.close()
#browser.close()
# with sync_playwright() as playwright:
# run(playwright)

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="3" time="5.317" timestamp="2023-08-16T17:11:52.526774" hostname="DESKTOP-SQBJDBE"><testcase classname="test_cases.test_001" name="test_run" time="3.898" /><testcase classname="test_cases.test_002" name="test_run" time="0.730" /><testcase classname="test_cases.test_003" name="test_run" time="0.650" /></testsuite></testsuites>

View File

@ -0,0 +1,543 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="GB2312"/>
<title>report.html</title>
<style>body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
/* do not increase min-width as some may use split screens */
min-width: 800px;
color: #999;
}
h1 {
font-size: 24px;
color: black;
}
h2 {
font-size: 16px;
color: black;
}
p {
color: black;
}
a {
color: #999;
}
table {
border-collapse: collapse;
}
/******************************
* SUMMARY INFORMATION
******************************/
#environment td {
padding: 5px;
border: 1px solid #E6E6E6;
}
#environment tr:nth-child(odd) {
background-color: #f6f6f6;
}
/******************************
* TEST RESULT COLORS
******************************/
span.passed,
.passed .col-result {
color: green;
}
span.skipped,
span.xfailed,
span.rerun,
.skipped .col-result,
.xfailed .col-result,
.rerun .col-result {
color: orange;
}
span.error,
span.failed,
span.xpassed,
.error .col-result,
.failed .col-result,
.xpassed .col-result {
color: red;
}
/******************************
* RESULTS TABLE
*
* 1. Table Layout
* 2. Extra
* 3. Sorting items
*
******************************/
/*------------------
* 1. Table Layout
*------------------*/
#results-table {
border: 1px solid #e6e6e6;
color: #999;
font-size: 12px;
width: 100%;
}
#results-table th,
#results-table td {
padding: 5px;
border: 1px solid #E6E6E6;
text-align: left;
}
#results-table th {
font-weight: bold;
}
/*------------------
* 2. Extra
*------------------*/
.log {
background-color: #e6e6e6;
border: 1px solid #e6e6e6;
color: black;
display: block;
font-family: "Courier New", Courier, monospace;
height: 230px;
overflow-y: scroll;
padding: 5px;
white-space: pre-wrap;
}
.log:only-child {
height: inherit;
}
div.image {
border: 1px solid #e6e6e6;
float: right;
height: 240px;
margin-left: 5px;
overflow: hidden;
width: 320px;
}
div.image img {
width: 320px;
}
div.video {
border: 1px solid #e6e6e6;
float: right;
height: 240px;
margin-left: 5px;
overflow: hidden;
width: 320px;
}
div.video video {
overflow: hidden;
width: 320px;
height: 240px;
}
.collapsed {
display: none;
}
.expander::after {
content: " (show details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
.collapser::after {
content: " (hide details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
/*------------------
* 3. Sorting items
*------------------*/
.sortable {
cursor: pointer;
}
.sort-icon {
font-size: 0px;
float: left;
margin-right: 5px;
margin-top: 5px;
/*triangle*/
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
}
.inactive .sort-icon {
/*finish triangle*/
border-top: 8px solid #E6E6E6;
}
.asc.active .sort-icon {
/*finish triangle*/
border-bottom: 8px solid #999;
}
.desc.active .sort-icon {
/*finish triangle*/
border-top: 8px solid #999;
}
</style></head>
<body onLoad="init()">
<script>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function toArray(iter) {
if (iter === null) {
return null;
}
return Array.prototype.slice.call(iter);
}
function find(selector, elem) { // eslint-disable-line no-redeclare
if (!elem) {
elem = document;
}
return elem.querySelector(selector);
}
function findAll(selector, elem) {
if (!elem) {
elem = document;
}
return toArray(elem.querySelectorAll(selector));
}
function sortColumn(elem) {
toggleSortStates(elem);
const colIndex = toArray(elem.parentNode.childNodes).indexOf(elem);
let key;
if (elem.classList.contains('result')) {
key = keyResult;
} else if (elem.classList.contains('links')) {
key = keyLink;
} else {
key = keyAlpha;
}
sortTable(elem, key(colIndex));
}
function showAllExtras() { // eslint-disable-line no-unused-vars
findAll('.col-result').forEach(showExtras);
}
function hideAllExtras() { // eslint-disable-line no-unused-vars
findAll('.col-result').forEach(hideExtras);
}
function showExtras(colresultElem) {
const extras = colresultElem.parentNode.nextElementSibling;
const expandcollapse = colresultElem.firstElementChild;
extras.classList.remove('collapsed');
expandcollapse.classList.remove('expander');
expandcollapse.classList.add('collapser');
}
function hideExtras(colresultElem) {
const extras = colresultElem.parentNode.nextElementSibling;
const expandcollapse = colresultElem.firstElementChild;
extras.classList.add('collapsed');
expandcollapse.classList.remove('collapser');
expandcollapse.classList.add('expander');
}
function showFilters() {
let visibleString = getQueryParameter('visible') || 'all';
visibleString = visibleString.toLowerCase();
const checkedItems = visibleString.split(',');
const filterItems = document.getElementsByClassName('filter');
for (let i = 0; i < filterItems.length; i++) {
filterItems[i].hidden = false;
if (visibleString != 'all') {
filterItems[i].checked = checkedItems.includes(filterItems[i].getAttribute('data-test-result'));
filterTable(filterItems[i]);
}
}
}
function addCollapse() {
// Add links for show/hide all
const resulttable = find('table#results-table');
const showhideall = document.createElement('p');
showhideall.innerHTML = '<a href="javascript:showAllExtras()">Show all details</a> / ' +
'<a href="javascript:hideAllExtras()">Hide all details</a>';
resulttable.parentElement.insertBefore(showhideall, resulttable);
// Add show/hide link to each result
findAll('.col-result').forEach(function(elem) {
const collapsed = getQueryParameter('collapsed') || 'Passed';
const extras = elem.parentNode.nextElementSibling;
const expandcollapse = document.createElement('span');
if (extras.classList.contains('collapsed')) {
expandcollapse.classList.add('expander');
} else if (collapsed.includes(elem.innerHTML)) {
extras.classList.add('collapsed');
expandcollapse.classList.add('expander');
} else {
expandcollapse.classList.add('collapser');
}
elem.appendChild(expandcollapse);
elem.addEventListener('click', function(event) {
if (event.currentTarget.parentNode.nextElementSibling.classList.contains('collapsed')) {
showExtras(event.currentTarget);
} else {
hideExtras(event.currentTarget);
}
});
});
}
function getQueryParameter(name) {
const match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function init () { // eslint-disable-line no-unused-vars
resetSortHeaders();
addCollapse();
showFilters();
sortColumn(find('.initial-sort'));
findAll('.sortable').forEach(function(elem) {
elem.addEventListener('click',
function() {
sortColumn(elem);
}, false);
});
}
function sortTable(clicked, keyFunc) {
const rows = findAll('.results-table-row');
const reversed = !clicked.classList.contains('asc');
const sortedRows = sort(rows, keyFunc, reversed);
/* Whole table is removed here because browsers acts much slower
* when appending existing elements.
*/
const thead = document.getElementById('results-table-head');
document.getElementById('results-table').remove();
const parent = document.createElement('table');
parent.id = 'results-table';
parent.appendChild(thead);
sortedRows.forEach(function(elem) {
parent.appendChild(elem);
});
document.getElementsByTagName('BODY')[0].appendChild(parent);
}
function sort(items, keyFunc, reversed) {
const sortArray = items.map(function(item, i) {
return [keyFunc(item), i];
});
sortArray.sort(function(a, b) {
const keyA = a[0];
const keyB = b[0];
if (keyA == keyB) return 0;
if (reversed) {
return keyA < keyB ? 1 : -1;
} else {
return keyA > keyB ? 1 : -1;
}
});
return sortArray.map(function(item) {
const index = item[1];
return items[index];
});
}
function keyAlpha(colIndex) {
return function(elem) {
return elem.childNodes[1].childNodes[colIndex].firstChild.data.toLowerCase();
};
}
function keyLink(colIndex) {
return function(elem) {
const dataCell = elem.childNodes[1].childNodes[colIndex].firstChild;
return dataCell == null ? '' : dataCell.innerText.toLowerCase();
};
}
function keyResult(colIndex) {
return function(elem) {
const strings = ['Error', 'Failed', 'Rerun', 'XFailed', 'XPassed',
'Skipped', 'Passed'];
return strings.indexOf(elem.childNodes[1].childNodes[colIndex].firstChild.data);
};
}
function resetSortHeaders() {
findAll('.sort-icon').forEach(function(elem) {
elem.parentNode.removeChild(elem);
});
findAll('.sortable').forEach(function(elem) {
const icon = document.createElement('div');
icon.className = 'sort-icon';
icon.textContent = 'vvv';
elem.insertBefore(icon, elem.firstChild);
elem.classList.remove('desc', 'active');
elem.classList.add('asc', 'inactive');
});
}
function toggleSortStates(elem) {
//if active, toggle between asc and desc
if (elem.classList.contains('active')) {
elem.classList.toggle('asc');
elem.classList.toggle('desc');
}
//if inactive, reset all other functions and add ascending active
if (elem.classList.contains('inactive')) {
resetSortHeaders();
elem.classList.remove('inactive');
elem.classList.add('active');
}
}
function isAllRowsHidden(value) {
return value.hidden == false;
}
function filterTable(elem) { // eslint-disable-line no-unused-vars
const outcomeAtt = 'data-test-result';
const outcome = elem.getAttribute(outcomeAtt);
const classOutcome = outcome + ' results-table-row';
const outcomeRows = document.getElementsByClassName(classOutcome);
for(let i = 0; i < outcomeRows.length; i++){
outcomeRows[i].hidden = !elem.checked;
}
const rows = findAll('.results-table-row').filter(isAllRowsHidden);
const allRowsHidden = rows.length == 0 ? true : false;
const notFoundMessage = document.getElementById('not-found-message');
notFoundMessage.hidden = !allRowsHidden;
}
</script>
<h1>report.html</h1>
<p>Report generated on 16-Aug-2023 at 17:11:57 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> v3.2.0</p>
<h2>Environment</h2>
<table id="environment">
<tr>
<td>Base URL</td>
<td></td></tr>
<tr>
<td>JAVA_HOME</td>
<td>C:\Program Files\Java\jdk1.8.0_281</td></tr>
<tr>
<td>Packages</td>
<td>{"pluggy": "0.13.1", "py": "1.10.0", "pytest": "7.4.0"}</td></tr>
<tr>
<td>Platform</td>
<td>Windows-10-10.0.19044-SP0</td></tr>
<tr>
<td>Plugins</td>
<td>{"Faker": "13.3.4", "allure-pytest": "2.8.17", "assume": "2.4.3", "base-url": "2.0.0", "cov": "2.8.1", "forked": "1.3.0", "html": "3.2.0", "json": "0.4.0", "metadata": "1.8.0", "ordering": "0.6", "playwright": "0.4.2", "repeat": "0.9.1", "rerunfailures": "10.2", "xdist": "2.3.0"}</td></tr>
<tr>
<td>Python</td>
<td>3.9.7</td></tr></table>
<h2>Summary</h2>
<p>3 tests ran in 5.32 seconds. </p>
<p class="filter" hidden="true">(Un)check the boxes to filter the results.</p><input checked="true" class="filter" data-test-result="passed" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="passed">3 passed</span>, <input checked="true" class="filter" data-test-result="skipped" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="skipped">0 skipped</span>, <input checked="true" class="filter" data-test-result="failed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="failed">0 failed</span>, <input checked="true" class="filter" data-test-result="error" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="error">0 errors</span>, <input checked="true" class="filter" data-test-result="xfailed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="xfailed">0 expected failures</span>, <input checked="true" class="filter" data-test-result="xpassed" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="xpassed">0 unexpected passes</span>, <input checked="true" class="filter" data-test-result="rerun" disabled="true" hidden="true" name="filter_checkbox" onChange="filterTable(this)" type="checkbox"/><span class="rerun">0 rerun</span>
<h2>Results</h2>
<table id="results-table">
<thead id="results-table-head">
<tr>
<th class="sortable result initial-sort" col="result">Result</th>
<th class="sortable" col="name">Test</th>
<th>Description</th>
<th class="sortable" col="duration">Duration</th></tr>
<tr hidden="true" id="not-found-message">
<th colspan="4">No results found. Try to check the filters</th></tr></thead>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_cases/test_001.py::test_run</td>
<td>
<html>
<head>
<meta name="Content-Type" value="text/html; charset=latin1"/></head>
<body>
<p> 名称:阅读书籍</p>
<p> 步骤:</p>
<p> 1、点击搜索框</p>
<p> 2、输入金牌卧底</p>
<p> 3、点击搜索按钮</p>
<p> 4、搜索结果页点击书籍名称进入书籍详情开始阅读</p>
<p> 检查点:</p>
<p> * 书籍章节标题:第一章 让我泡她</p>
<p> </p></body></html></td>
<td class="col-duration">3.89</td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_cases/test_002.py::test_run</td>
<td>
<html>
<head>
<meta name="Content-Type" value="text/html; charset=latin1"/></head>
<body>
<p> 名称:查看个人中心</p>
<p> 步骤:</p>
<p> 1、点击登录的账号进入个人中心</p>
<p> 2、点击全部收藏</p>
<p> 3、点击我的书评</p>
<p> 4、点击我的反馈</p>
<p> 5、点击账号设置</p>
<p> 6、</p>
<p> 检查点:</p>
<p> * 我的昵称是18211111111</p>
<p> </p></body></html></td>
<td class="col-duration">0.72</td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_cases/test_003.py::test_run</td>
<td>
<html>
<head>
<meta name="Content-Type" value="text/html; charset=latin1"/></head>
<body>
<p> 名称:查看各个排行榜</p>
<p> 步骤:</p>
<p> 1、点击排行榜</p>
<p> 2、点击新书榜</p>
<p> 3、点击更新榜</p>
<p> 4、点击评论榜</p>
<p> 检查点:</p>
<p> * 存在评论榜元素</p>
<p> </p></body></html></td>
<td class="col-duration">0.64</td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody></table></body></html>

29
tools/config.py Normal file
View File

@ -0,0 +1,29 @@
import os
PRO_PATH = os.path.dirname(os.path.abspath(__file__))
class RunConfig:
"""
rms运行测试配置
"""
# 运行测试用例的目录或文件
# cases_path = os.path.join(PRO_PATH, "test_cases", "test_001.py")
cases_path = os.path.join(PRO_PATH, "../test_cases")
# 配置浏览器驱动类型(chromium, firefox, webkit)。
browser = "chromium"
# 运行模式headless, headful
mode = "headful"
# 配置运行的 URL
url = "http://novel.hctestedu.com/?to=pc"
# 失败重跑次数
rerun = "0"
# 当达到最大失败数,停止执行
max_fail = "5"
# 报告路径(不需要修改)
NEW_REPORT = None

33
tools/get_log.py Normal file
View File

@ -0,0 +1,33 @@
import logging.handlers
import os
import time
from tools.config import PRO_PATH
item_path = os.path.dirname(PRO_PATH)
log_path = item_path+"\logs"
print(log_path)
class GetLog:
__logger = None
# 获取日志器
@classmethod
def get_log(cls):
if cls.__logger is None:
cls.__logger = logging.getLogger()
cls.__logger.setLevel(logging.INFO)
filename = os.path.join(log_path, f'{time.strftime("%Y%m%d-%H%M%S", time.localtime())}.log')
print(filename)
fh = logging.handlers.TimedRotatingFileHandler(filename,
when='midnight', interval=1, backupCount=3, encoding="utf-8")
"""
when='midnight':一天一夜
interval=1每隔一个when生成一个日志文件
backupCount=7保存7天的日志备份数量
"""
fmt = logging.Formatter(
"%(asctime)s --%(name)s-- %(filename)s.%(funcName)s():line %(lineno)d [%(levelname)s]:%(message)s")
fh.setFormatter(fmt)
cls.__logger.addHandler(fh)
return cls.__logger