first commit
This commit is contained in:
commit
ab94f0ca7a
|
@ -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/
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
```
|
|
@ -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,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
|
|
@ -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,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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue