Showing preview only (794K chars total). Download the full file or copy to clipboard to get everything.
Repository: defnngj/pyse
Branch: master
Commit: f94c55006802
Files: 204
Total size: 675.4 KB
Directory structure:
gitextract_p6uqsu94/
├── .gitignore
├── CHANGES.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── api_case/
│ ├── api_case.xlsx
│ └── confrun.py
├── demo/
│ ├── README.md
│ ├── __init__.py
│ ├── confrun.py
│ ├── reports/
│ │ └── .keep
│ ├── run.py
│ ├── test_data/
│ │ ├── csv_data.csv
│ │ ├── excel_data.xlsx
│ │ ├── json_data.json
│ │ └── yaml_data.yaml
│ └── test_dir/
│ ├── __init__.py
│ ├── api_case/
│ │ ├── __init__.py
│ │ └── test_http_demo.py
│ ├── app_case/
│ │ ├── __init__.py
│ │ ├── test_first_demo.py
│ │ ├── test_po_demo.py
│ │ └── test_u2_demo.py
│ └── web_case/
│ ├── __init__.py
│ ├── test_data_demo.py
│ ├── test_ddt_demo.py
│ ├── test_first_demo.py
│ ├── test_fixture_demo.py
│ ├── test_playwright_demo.py
│ └── test_po_demo.py
├── description.rst
├── docs/
│ ├── .gitignore
│ ├── README.md
│ ├── compare.md
│ ├── deploy.sh
│ ├── package.json
│ └── vpdocs/
│ ├── .vuepress/
│ │ └── config.js
│ ├── README.md
│ ├── api-testing/
│ │ ├── api_case.md
│ │ ├── api_object.md
│ │ ├── assert.md
│ │ ├── more.md
│ │ ├── start.md
│ │ └── webscocket.md
│ ├── app-testing/
│ │ ├── adb_lib.md
│ │ ├── appium_lab.md
│ │ ├── extensions.md
│ │ ├── page_object.md
│ │ └── start.md
│ ├── develop.md
│ ├── getting-started/
│ │ ├── advanced.md
│ │ ├── create_project.md
│ │ ├── data_driver.md
│ │ ├── dependent_func.md
│ │ ├── installation.md
│ │ ├── quick_start.md
│ │ └── seldom_cli.md
│ ├── introduce.md
│ ├── more-ability/
│ │ ├── benchmark.md
│ │ ├── db_operation.md
│ │ └── test_library.md
│ ├── platform/
│ │ └── platform.md
│ ├── version/
│ │ └── CHANGES.md
│ └── web-testing/
│ ├── browser_driver.md
│ ├── chaining.md
│ ├── other.md
│ ├── page_object.md
│ └── seldom_api.md
├── pyproject.toml
├── requirements.txt
├── seldom/
│ ├── __init__.py
│ ├── appdriver.py
│ ├── appium_lab/
│ │ ├── __init__.py
│ │ ├── action.py
│ │ ├── android.py
│ │ ├── appium_service.py
│ │ ├── find.py
│ │ ├── keyboard.py
│ │ ├── ocr_plugin.py
│ │ └── switch.py
│ ├── case.py
│ ├── cli.py
│ ├── db_operation/
│ │ ├── __init__.py
│ │ ├── base_db.py
│ │ ├── mongo_db.py
│ │ ├── mssql_db.py
│ │ ├── mysql_db.py
│ │ ├── postgres_db.py
│ │ └── sqlite_db.py
│ ├── driver.py
│ ├── extend_lib/
│ │ ├── __init__.py
│ │ ├── base_assert.py
│ │ ├── curlify.py
│ │ ├── jsonpath.py
│ │ ├── parameterized.py
│ │ └── tomorrow.py
│ ├── file_runner/
│ │ ├── __init__.py
│ │ └── api_excel.py
│ ├── har2case/
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── demo.har
│ │ └── utils.py
│ ├── logging/
│ │ ├── __init__.py
│ │ ├── exceptions.py
│ │ └── log.py
│ ├── project_temp/
│ │ ├── __init__.py
│ │ ├── api/
│ │ │ ├── confrun.py
│ │ │ ├── run.py
│ │ │ └── test_sample.py
│ │ ├── app/
│ │ │ ├── confrun.py
│ │ │ ├── run.py
│ │ │ └── test_sample.py
│ │ ├── data.json
│ │ └── web/
│ │ ├── confrun.py
│ │ ├── run.py
│ │ └── test_sample.py
│ ├── request.py
│ ├── running/
│ │ ├── DebugTestRunner.py
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── loader_extend.py
│ │ ├── loader_hook.py
│ │ └── runner.py
│ ├── skip.py
│ ├── swagger2case/
│ │ ├── __init__.py
│ │ ├── core.py
│ │ └── swagger.json
│ ├── testdata/
│ │ ├── __init__.py
│ │ ├── conversion.py
│ │ ├── parameterization.py
│ │ ├── random_data.py
│ │ └── random_func.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── adbutils.py
│ │ ├── benchmark.py
│ │ ├── cache.py
│ │ ├── cache_data.json
│ │ ├── dependence.py
│ │ ├── diff.py
│ │ ├── encrypt.py
│ │ ├── file_extend.py
│ │ ├── genson.py
│ │ ├── jmespath.py
│ │ ├── match_image.py
│ │ ├── resource_loader.py
│ │ ├── send_extend.py
│ │ ├── thread_lab.py
│ │ └── timer.py
│ ├── webcommon/
│ │ ├── __init__.py
│ │ ├── find_elems.py
│ │ ├── keyboard.py
│ │ ├── locators.py
│ │ └── selector.py
│ ├── webdriver.py
│ ├── webdriver_chaining.py
│ └── websocket_client.py
└── tests/
├── data/
│ ├── country.graphql
│ ├── db.sqlite3
│ └── hello.txt
├── test_adb.py
├── test_api_object.py
├── test_autowing.py
├── test_base_assert.py
├── test_benchmark.py
├── test_browser.py
├── test_browser_new.py
├── test_cache/
│ ├── __init__.py
│ ├── test_cache.py
│ ├── test_cache_thread.py
│ └── test_memory_cache.py
├── test_db/
│ ├── __init__.py
│ ├── test_db_mssql.py
│ ├── test_db_mysql.py
│ ├── test_db_postgresdb.py
│ └── test_db_sqlite3.py
├── test_dependent_func.py
├── test_encrypt.py
├── test_fixture.py
├── test_graphql.py
├── test_http_assert.py
├── test_jsonpath.py
├── test_locators.py
├── test_log.py
├── test_other_lib/
│ ├── __init__.py
│ ├── test_playwright.py
│ ├── test_pyautogui.py
│ └── test_uiautomator.py
├── test_playwright_sample.py
├── test_random/
│ ├── __init__.py
│ └── test_testdata.py
├── test_request_extend.py
├── test_skip.py
├── test_steps_chaining.py
├── test_steps_chaining_browser.py
├── test_thread/
│ ├── __init__.py
│ ├── test_thread.py
│ ├── test_thread_browser.py
│ ├── test_thread_case.py
│ └── test_thread_path.py
├── test_utils/
│ ├── __init__.py
│ └── test_file.py
└── test_websocket/
├── __init__.py
├── test_websocket.py
└── webscoket_server.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.idea
*.pyc
*.log
report
build
dist
seldom.egg-info
log/
.history
================================================
FILE: CHANGES.md
================================================
### 3.14.2
* 合并提交:
* `send_extend.py`: 规范参数多类型,避免警告提示。
* 优化`loadert('start run')`和`loader("end run")` 的调用。
* 升级依赖库的版本。
### 3.14.1
* 修复:脚手架:web示例错误。
* 修复:App API `drag_from_to()` 参数顺序错误。#262
* 修复:Web API `is_visible()` 执行报错。 #264
* 优化:`seldom.main()` 参数类型警告。
* 优化:`seldom` 命令,提升性能。
* 支持:python 3.14 测试通过。
* 新增:`HttpRequest` 增加`__init__()` 支持初始化`base_url`参数。
* 新增:提供`resource_file()` 加载 GraphQL 和 JSON 文件。
### 3.14.0
* App测试
* 功能:增加`ADBUtils`类,支持更多`adb`操作。
* Web测试:
* 增加`self.assert_screenshot()`断言图片。
* API测试:
* 增加`slef.save_response()`保存响应结果到文件中。
* 增加`self.ip_address()`获取请求的IP地址。
* 扩展模块重新实现`File`类。
* 修复`seldom.main()`方法`browser`参数类型警告。
* `typer`重写`seldom`命令行工具。
* 增加`PostgresDB`使用文档。
* 升级`Appium-Python-Client==5.1.0`。
### 3.13.0
* 功能:unittest所有基础断言增加日志, 例如:`assertEqual()`、`assertIn()`...。
* 功能:增加`adb`操作:`get_devices`、`launch_app`、`close_app`。
* 功能:`seldom.main()`增加`device`参数,用于存储设备ID。感谢@ThickBull
* 修复bug: appium提供的`query_app_state()`执行报错。
* 文档:增加`auto-wing` AI库使用示例。
* 文档:修复官方文档左侧菜单无法显示的问题。
* 升级`Appium-Python-Client==4.5.0`。
* 支持 `python 3.9 ~ 3.13` 版本。
### 3.12.0
* 功能:支持基准测试功能。
* 功能:提供加密模块,支持 `MD5`,`SHA1`,`AES`,`Base64`等各种`编码&解码`,`加密&解密`等算法。
* 功能:`seldom.main()`增加`env`参数,用于设置静态变量。
* 功能:HTML测试报告根据网络判断是否生成本地本地静态文件,无网络情况也可正常显示报告。
* 修复:`dependent_func()`装饰器调用静态方法报错。感谢@Catking233
### 3.11.0
* 功能:平台化用例执行,`seldom.main()`支持加载`confrun.py`中的 `start_run()/end_run()`。
* 功能:平台化用例解析,识别用例标签`label`。
* HTTP测试:通过`confrun.py`支持`proxies()`配置全局的请求代理。
* App测试:
* appium_lab 增加 `drag_from_to()`方法,支持坐标位滑动。感谢@guweifan
* appium_lab 增加`AppiumService`类,支持启动appium server。感谢@guweifan
* 优化:`jsonpath.py`的代码。
### 3.10.0
* 重要:所有app/web元素定位支持`selector`模式,详细查看文档。
* 更新:`sleep()`增加默认值1s,也支持随机休眠范围:`self.seep((1, 3))`。
* 更新: `appium_lab`模块的 `Action()` 类下面的方法支持自定义休眠时间、间隔时间等。
* 修复:`Steps()`类的 `open()` 方法默认传url报错 [#241](https://github.com/SeldomQA/seldom/issues/241)。
* 告警:`type_enter()`添加移除警告,推荐使用`type()`。
* 文档:
* 修改playwright使用示例。
* 增加pyAutoGUI使用示例。
### 3.9.1
* 更新:脚手架项目模板,增加`run.py`文件。
* 修复:生成随机数,获取在线时间接口错误。
* 修复:`datetime.utcnow()`在Python 3.12 告警。
* App测试:
* 修复`back()`、`home()`方法报错。
* 增加`long_press_key()`方法。
* API测试:
* 增加`assertStatusOk()`断言方法,断言接口返回状态码`200`。
* `@check_response()`装饰器重命名`@api()`,更简洁。
* Web测试:
* 增加`prompt_value()`方法,支持弹窗输入 [#166](https://github.com/SeldomQA/seldom/issues/166)。
* 增加`action_chains()`方法,返回Selenium的`ActionChains()`
类对象 [#119](https://github.com/SeldomQA/seldom/issues/119)。
* 增加`is_visible()`方法,检查页面元素是否可见 [#62](https://github.com/SeldomQA/seldom/issues/62)。
* `Pycharm`右键运行Web UI用例,抛异常提示。
* 文档更新:
* 增加浏览器代理设置示例 [#31](https://github.com/SeldomQA/seldom/issues/31)。
* 操作已打开浏览器示例 [#174](https://github.com/SeldomQA/seldom/issues/174)。
* 升级:`XTestRunner==1.8.0`。
### 3.9.0
* App测试。
* 升级`Appium-Python-Client==4.1.0`
* 提供`UiAutomator2Options`和`EspressoOptions`类,替换appium提供的这个两个类。
* 移除不再支持的API: `launch_app()`、`close_app()`、`reset()`。
* 增加App相关操作时的日志。
* Web测试浏览启动重构。
* 支持`start/end`启动和关闭浏览器。
* 支持`start_class/end_class`启动和关闭浏览器。
* 支持`new_browser()`重新打开一个浏览器。
* `self.open()` 检测到没有指定浏览器,不再默认启动一个`Chrome()`浏览器。
* 链式API `Steps()`类添加`browser`参数。
* `Seldom.driver`对象支持多线程。
* `log`日志显示当前运行的线程。
* `Cache`缓存类支持多线程。
* 其他:移除直接依赖库:`requests`和`websocket-client`, 使用间接依赖。
* `XTestRunner` -> `requests`
* `Appium-Python-Client` -> `selenium` -> `websocket-client`
### 3.8.1
* App测试。
* 支持`Appium-Python-Client==4.0.1`,修复`4.0.0` 引起的问题。
* `seldom` 命令,创建项目命令区分`web/app/api`项目。
* 修复`seldom-platform`平台运行错误。
### 3.8.0
* API测试:支持执行Excel测试用例, `seldom --api-excel api_case.xlsx` 具体用法查看文档。
* App:增加 `self.keyboard_search()`模拟键盘上的搜索按键。
* 优化: `@file_data()`参数化装饰器代码。
### 3.7.1
* 优化:`main()` 中的`path`参数支持列表,可以指定多个目录或文件。
* 新增:提供`from seldom.utils.send_extend import RunResult` 获取用例的执行数据。
* App测试。
* 增加`swipe_right()`左滑 和 `swipe_left()`右滑支持。
* `AppiumLab()` 默认允许不传`driver`参数。
* 其他:
* `Python 3.12` 测试通过。
### 3.7.0
* `@data()`数据驱动装饰器增加`cartesian=True`参数,支持笛卡尔积。
* 新增`WebSocket`接口测试支持。
* App测试。
* 支持`Appium-Python-Client==4.0.0`,修复`4.0.0` 引起的问题。
* Web测试
* 重新支持指定浏览器驱动,使用`executable_path`参数。
* 其他:
* 基于`selenium`依赖库,移除 `Python 3.7` 支持。
### 3.6.0
* `seldom.main()`方法增加`failfast`参数,debug模式,允许第一条用例失败,停止执行。
* 增加`@retry()`装饰器,用于函数&方法错误重试。
* HTTP测试
* 支持`swagger`文档转seldom用例,使用命令 `seldom -s2c swagger.json` 。
* 文档:增加 API Object model 概念的介绍,以及seldom中的应用。
### 3.5.0
* 新增:支持 Postgre SQL 数据库操作。
* web测试
* `pause()` 用于暂停操作。
* 移除`webdriver_manager_extend.py`文件(之前漏移除文件)。
* App测试
* 支持`appium 2.0` 正式版。
* 支持appium-OCR-plugin插件。
* 增加`click_image()`方法,支持图片点击定位。
* `press_key()` 支持`ENTER`参数,模拟键盘回车。
### 3.4.1
* 修复:`diff_json()` 对比特殊数据的异常没有捕捉到。
* web测试
* `screenshots()` 增加`images`参数,支持传入截图对象 [#202](https://github.com/SeldomQA/seldom/issues/202)。
* `open_electron()` 增加`chromedriver_path`参数,支持手动指定驱动地址。
* `setUpClass()`/`tearDownClass()` 增加异常捕捉,避免报错之后,用例无法统计的问题。
### 3.4.0
* 新增:`dependent_func()`装饰器,支持用例方法依赖调用,具体使用参考文档。
* api测试
* 修复:har2case 请求头参数类型判断不准的问题。
* web测试
* 增加`open_electron()` 方法,支持启动桌面electron应用。
* 键盘操作`Key()`支持链式调用,例如: `self.Keys(id_="kw").select_all().cut()` 全选并删除。
* cache操作日志增加 emoji。
* 修复:`diff_json()` 优化,支持dict深度排序。 [#197](https://github.com/SeldomQA/seldom/issues/197)
### 3.3.0
* web测试
* 浏览器驱动`webdriver-manager` 替换为`selenium-manager`。
* 增加`execute_cdp_cmd()` 方法。
* 随机数据
* `online_timestamp()` 在线获取时间戳。
* `online_now_datetime()` 在线获取当前时间,格式为:`%Y-%m-%d %H:%M:%S`。
* 增加运行时内嵌(built-in)方法:`base_url()`、`driver()` - 无需导入,可以在自动化程序任意位置使用这两个方法。
* 移除`parameterized` 库的依赖,改为内置。
* 修复:`diff_json()` 对比 `[{}]` 数据时报错。 [#197](https://github.com/SeldomQA/seldom/issues/197)
### 3.2.3
* HTTP自动化
* `confrun.py` 支持 `mock_url` hook 钩子函数。
* 增加 `self.base_url` 获取 `base_url`。
* Web自动化
* 更新:`get_elements()` 增加`empty`参数,设置为`True`, 允许返回空列表 `[]`
* 更新: `debug=True` 模式,移除操作元素边框高亮,提高用例执行速度。
* App测试
* 修复:`key_text()` 无法输入点号`.`的问题。
* 优化:`seldom_log.log` 文件只记录一次运行结果,减少文件大小。
* 升级:`webdriver_manager==4.0.0` [#189](https://github.com/SeldomQA/seldom/issues/189)
* 其他: 添加 `pyproject.toml` 支持。
* 文档:增加其他库的使用例子。
### 3.2.2
* 功能:增加`@threads()`支持多线程运行用例。
* 功能:增加`@rerun()` 重复执行某个测试方法。
* 功能:数据库操作
* `MySQLDB()`、`MSSQLDB()` 支持`charset` 参数设置字符集。
* `init_table()` 批量插入数据库增加`clear` 参数,可以选择是否删除表再插入。
* 功能:Web自动化
* 新增`save_screenshot()` 截图保存本地。
* 修改`screenshots()` 自动截图保存到HTML报告,移除`file_path` 参数。
* 修改`element_screenshot()` 元素截图保存到HTML报告,移除`file_path` 参数。
* `type()` 方法增加 `click` 参数,针对app元素优化,app的输入框往往需要点击以下锁定光标再输入。
* 修复:浏览器配置参数 `option` 更名为 `options`。
* 其他:增加 python3.11 支持。
### 3.2.1
* 功能:增加`@disk_cache()`、`@memory_cache()` 缓存装饰器。
* 功能:app测试,seldom支持本身API支持appium定位。
* 功能:db操作,增加`insert_get_last_id()` 方法,插入数据并返回id。
* 修复:`@data_class()` 必传`input_values` 参数问题。
* 修复:设置log等级,HTML报告无法根据等级打印日志问题。
### 3.2.0
* Web UI测试,增加一组新的警告框 alert 操作。
* `self.alert.text`
* `self.alert.accept()`
* `self.alert.dismiss()`
* `self.alert.send_keys("text")`
* App UI测试。
* `AppiumLab()` 类增加 `context()` 方法获取当前上下文。
* `AppiumLab()` 类增加 `size()` 当前窗口尺寸。
* API 测试。
* 增加`self.patch()` 请求方法。
* 增加`self.json_to_dict()` 支持单引号JSON格式转字典。
* cache 增加文件锁,防止多线程读写错误(Windows不支持 fcntl)
* 支持 `XTestRunner=>1.6.2` 版本
* XML格式的报告支持 rerun 重跑参数。
* HTML 报告skip用例样式微调。
* HTML 重跑只显示最后一次结果。
* SMTP 发送报告增加 `ssl` 参数。
* `seldom.main()` 方法 ⚠ 不兼容更新
* 移除 `save_last_run` 参数。
* `browser` 参数支持`dict` 格式, 所有和浏览器配置相关的有发生修改。 包括
* 设置浏览器驱动地址。
* 设置 headless 模式。
* 设置 options 参数。
* 设置 selenium grid 地址。
### 3.1.3
* 功能:`file_data()` 增加`end_line`
参数,对于csv/excel文件支持读取到第几行结束。[#163](https://github.com/SeldomQA/seldom/issues/163)
* 优化:`self.assertElement()` 断言元素时间过长的问题。
* 优化:`self.assertJSON()` 断言日志,区分告警和错误。
* 移除:`self.jresponse()` 方法。
### 3.1.2(internal)
> 内部版本:移除了日志打印的 emoji 表情。
* 功能:`seldom.main()` 方法 path 参数支持斜杠路径`\`(windows系统用`\` 表示路径)。
### 3.1.1
* 功能:`confrun.py` 增加`start_run()/end_run()` 钩子函数,用于运行前/后相关配置。
* 优化:`@api_data()` 装饰器增加 `headers` 参数。
* 优化:`assertJSON()` 断言增加 `exclude` 参数,屏蔽检查的字段,例如 `["start_time", "token"]`。
* 修复:`rediscover()` 查找用例bug。
* 依赖:升级`XTestRunner==1.5.0` 支持飞书/微信发送消息。
### 3.1.0
* 功能:提供 `confrun.py` 运行配置文件,配合 `seldom` 命令使用。
* 功能:Web测试,增加 `self.get_log()` 方法。
* 升级:`webdriver_manager==3.8.5` ,支持Mac M1芯片的浏览器驱动。[#159](https://github.com/SeldomQA/seldom/issues/159)
* 修复:seldom-platform平台同步多个项目引起的Bug。[#158](https://github.com/SeldomQA/seldom/issues/158)
* 修复:Web测试, `self.close()` 关闭浏览器Bug。
### 3.0.1
* 功能:支持 `SQL Server` 数据库支持,需要单独安装`pymssql`库。
* 功能:http接口测试增加`curl()`方法,支持请求转 `cURL`。
* 功能:`seldom` 命令增加`--log-level` 参数,log类型:`TRACE`, `DEBUG`, `INFO`, `SUCCESS`, `WARNING`, `ERROR` 等。
### 3.0.0
* `seldom 3.0` 的核心是支持app测试,并且相关API已稳定,目的已达到,接下来将会在`3.0`基础上继续开发。
* 功能:`collect_cases()` 支持 `warning` 参数。
### 3.0.0beta2
* 修复:
* 接口测试: 接口返回文本`r.text` 中文乱码问题。[#146](https://github.com/SeldomQA/seldom/issues/146)
* app测试:感谢 @986379041
* `install_app()` 错误
* `close_app()` 错误
* 功能:
* `TestMainExtend` 类增加 `tester`参数。 [#149](https://github.com/SeldomQA/seldom/issues/149)
* 生成随机数,增加`get_month()` 和 `get_year()`方法。 [#152](https://github.com/SeldomQA/seldom/issues/152)
* seldom命令增加清除所有缓存。`> seldom --clear-cache true`。 [#153](https://github.com/SeldomQA/seldom/issues/153)
* 其他:
* seldom 运行用例,优化内存使用。
### 3.0.0beta1
* 支持App测试
* 依赖`Appium-Python-Client`库。
* `main()` 增加 `app_info`, `app_server` 参数。
* 增加`appium_lab` 模块。
* 增加`AppDriver` 类。
* 优化:基于pylint检查分析工具 优化代码。
* 其他:
* 生成随机数,增加`get_timestamp()` 获取当前时间戳。
* 数据库查询,增加`query_one()` 查询一条数据。
### 2.10.6 ~ 2.10.7
* 功能:`seldom`命令重大更新,支持更多参数和功能。
* 功能:`@file_data()` 当设置`Seldom.env`时支持更深一级遍历。
* 修复:`diff_json()` 对比数据错误。
### 2.10.4 ~ 2.10.5
* 重构log日志打印。 @Yongchin
* 彻底修复日志重复打印的问题。
* 移除`log.printf()` 非标准日志类型。
* 修复:
* `sender()` 发送完邮件,`seldom_log.log` 文件无法删除的问题。
* `TestMainExtend` 类`run_cases()`按照用例的顺序执行。@luna-CY
* 修复`request` 带上`url=` 参数时异常。 @986379041@qq.com
* 依赖:`webdriver_manager`依赖升级到`3.8.2`
* 移除:`Opera` 浏览器的支持,selenium 4 已经移除了对opera的单独驱动支持。
### 2.10.3
* 数据驱动:`@data()` 和 `@file_data()` 优化用例名称和描述。
* 增加`Seldom.env`环境配置变量,`@file_data()` 数据驱动装饰器支持环境变量。
* 修复:`Edge`浏览器启动错误。
* 修复:HTTP接口测试`self.post()`方法 `data`参数不是dict类型错误。
* 平台化支持:优化用例收集,具体查看文档。
### 2.10.2
*
更新:移动模式列表更新,去掉旧设备,增加新设备 [link](https://github.com/SeldomQA/seldom/blob/master/docs/vpdocs/other/other.md)
* 功能:测试报告显示断言信息。
* 功能:`main()` 通过`open=False`可以控制运行完测试 不自动化打开测试报告。
* Web 测试:
* 增加`self.new_browser()` 可以打开新的浏览器,但只能使用`selenium` 的 API
* 增加`switch_to_frame_parent` 切换到上一级表单,[#118](https://github.com/SeldomQA/seldom/issues/118)。
* 优化`assertNotElement` 执行慢的情况 [#120](https://github.com/SeldomQA/seldom/issues/120)
* HTTP 测试:
* 优化:JSON日志进行格式化打印。
### 2.10.1
* 修复:seldom log 问题引起,错误信息无法在控制台打印。
> 2.10.0 为了解决[107](https://github.com/SeldomQA/seldom/issues/107)
> 问题,我们经过反复的讨论和优化,甚至对相关库XTestRunner做了修改;以为完美解决了这个问题,没想到还是引起了一些严重的错误。为此,我们感到非常沮丧,退回到2.9.0的实现方案。请升级到2.10.1版本。
### 2.10.0
* seldom log功能:
* 修复打印日志显示固定文件的问题 [107](https://github.com/SeldomQA/seldom/issues/107)。
* log方法变更:`log.warn()` -> `log.warning()`。
* 功能:提供了`cache` 类来模拟缓存。
* 功能:`@data()` 装饰器支持 `dict` 格式。
* 功能:`self.jresponse()` 方法设计不合理,给以废弃提示;可以使用`self.jsonpath()`/`self.jmespath()` 替代。
* 优化:断言方法`assertSchema()`、`assertJSON()`支持`response`传参。
* 优化:`@check_response()` check检查失败打印`response`。
* 修复:`webdriver_manager` 没有设置上限版本,导致`webdriver_manager>=3.6.x` 报错; 如果使用的 `seldom<=2.9`
请重新安装`webdriver_manager==3.5.2`。
### 2.9.0
* seldom log功能:
* 开放seldom 的`log`能力,可以配置`颜色(colorlog)`、`格式(format)`、`等级(level)` 等。
* 重新定义了seldom打印日志的格式。
* 所有log统一记录到`/reports/seldom_log.log`文件,不再每次生成单独文件。
* 功能:提供了`@check_response()` 装饰器,为接口封装提供强大的支持。
* 功能:集成`genson`库,生成JsonSchema模板 [100](https://github.com/SeldomQA/seldom/issues/100) 。
* 功能:增加`assertInPath()` 断言方法。
* 功能:增加`jmespath()`方法,方便提取测试数据。
* 优化:`jresponse()` 增加对`jmespath` 语法的支持。
* 优化:支持`self.get()/self.post()/self.put()/self.delete()` 返回response对象。
### 2.8.0
* 功能:增加MongoDB 数据库操作 [93](https://github.com/SeldomQA/seldom/issues/93) 。
* 功能:支持单个用例执行 [94](https://github.com/SeldomQA/seldom/issues/94) 。
* 功能:`sendmail()` 增加`delete`参数,发送完邮件删除`reports/`
目录下面的报告和日志文件 [95](https://github.com/SeldomQA/seldom/issues/95) 。
* 功能:增加`jsonpath` 和 `jresponse()` ,更容易查找json数据 [96](https://github.com/SeldomQA/seldom/issues/96) 。
* 功能:创建项目脚手架增加api测试例子:`seldom -project mypro` 。
* 其他: 全新的seldom在线文档:https://seldomqa.github.io/ ,感谢 @nickliya
### 2.7.0
* 功能:引入`loguru` 库用于打印日志(之前使用python默认logging总有一些重复打印或不打印的问题)。
* 功能:web自动化增加一套方法链(method chaining)的API。
* 功能:支持手动指定浏览器驱动路径。
### 2.6.0
* 移除:自带的`HTMLTestRunner`,HTML报告采用`XTestRunner`。
* 移除:对`unittest-xml-reporting`库的依赖,XML报告使用`XTestRunner`。
* 修改:`SMTP`类发送邮件方法 `sender()` -> `sendmail()`, 发送邮件样式采用`XTestRunner`。
* 增加:`seldom.main()`方法增加`tester` 参数,用于设置测试人员名字,默认`Anonymous`。
* 增加:`seldom.main()`方法增加`language` 参数,用于设置报告中英文`en/zh-CN`,默认`en`。
* 增加:发送钉钉功能。
* 修改:接口测试 `self.session` -> `self.Session()`。
* 移除:接口测试 `self.request()` 方法移除(注:该方法原本不可用)。
### 2.5.1
* 功能:Http接口测试使用日志打印接口信息
* 功能:Http接口测试打印`json`参数 [83](https://github.com/SeldomQA/seldom/issues/83)
* 修复:Web UI测试`self.Key()` 无法定位元素的问题
### 2.5.0
* 功能:支持测试平台化。
* 功能:utils 增加`file`类,获取当前文件目录更方便。
* 修复:`self.select()` 操作下拉选择错误。
* 修复:`diff_json()` 对比json文件错误。
### 2.4.2
* 功能:增强`@file_data`使用方式,json/yaml支持内嵌`dict`数据。
### 2.4.1
* 优化:HTTP接口测试增加`cookies`信息打印。
* 优化:`@file_data()` 使用,支持指定目录。
* 修复:`visit()` 方法默认浏览器没有自动安装浏览器驱动的问题。
* 修复:`query_sql()` 执行SQL没有提交的问题。
### 2.4.0
* 适配selenium 4.0+ ,适配相关依赖库新版本。
* 测试用例支持`label`标签分类。
* 接口测试增加打印入参信息 [79](https://github.com/SeldomQA/seldom/issues/79) 。
* EdgeChromium浏览器支持`headless`模式。
* Web自动化测试增加元素截图`self.element_screenshot()`
* 优化HTML测试报告样式。
* 优化邮件模板样式。
### 2.3.3
* 增加 `assertNotText()` 断言方法 [75](https://github.com/SeldomQA/seldom/issues/75) 。
* 修复`main()`设置`rerun` 和 `save_last_run`参数,导致用例统计错误 [76](https://github.com/SeldomQA/seldom/issues/76) 。
### 2.3.2
* 接口调用如果是图片类型,不在打印内容。
* 增加`screenshot` 针对定位的元素截图, 用法`self.screenshot(id="xx")`。
* 测试报告:优化截图的样式。
* 发邮件功能,默认增加附件为测试报告。
### 2.3.1
* 修复`assertUrl()`、`assertInUrl()` 断言中文编码错误。
* 增加文件路径操作。
* `file_path()` 获取当前文件路径。
* `file_dir()` 获取当前文件目录。
* `file_dir_dir()` 获取当前文件目录的目录。
* `file_dir_dir_dir()` 获取当前文件目录的目录的目录。
* `init_env_path()` 添加路径到环境变量。
* 优化`main()` 方法中代码的执行顺序。
### 2.3.0
* 集成 `webdriver-manager`,不需要再单独安装浏览器驱动。
* seldom logo 显示版本号。
* 固定`selenium`版本号,暂没做`4.0.0`适配。
### 2.2.4
* 修复HTTP接口测试,指定`url`参数错误的问题。[71](https://github.com/SeldomQA/seldom/issues/71)
* 支持发送多人邮件。[72](https://github.com/SeldomQA/seldom/issues/72)
* 优化HTMLTestRunner, 重跑次数不记录为用例数。
* 修复pip安装缺少`description.rst` 问题。
### 2.2.3
* 支持控制台操作步骤显示在HTML报告中。[42](https://github.com/SeldomQA/seldom/issues/42)
* 修改`get_elements()`返回空列表。[69](https://github.com/SeldomQA/seldom/issues/69)
* 修复因为`colorama`/`emoji`导致的编码错误。[70](https://github.com/SeldomQA/seldom/issues/70)
### 2.2.2
* 优化db操作方法。
* 打印`logs`合并到 `reports` 目录。
### 2.2.1
* webdriver文件增加类型。
* 删除utils 错误代码。
* 修复:`diff_json()` 函数处理复杂数据报错 #66
* 修复:运行接口测试用例报 driver 错误 #68
* 修复:测试报告`popper.min.js` CDN 太慢的问题
### 2.2.0
* 增加接口测试方法`session`、`request`。
* 增加`seldom -h2c`参数,用于将har文件转成测试用例。
### 2.1.1
* 增加随机生成时间方法`get_past_time()`、`get_future_time()`
* 优化:截图方法`screenshots()`,可以在任意位置使用该方法生成截图,并显示在HTML测试报告中。
* 修复:接口测试`main()`中base_url 和 方法中的 url 同时存在的问题。
* 修复:优化MySQL数据库连接的问题。
* 修复:发送邮件时的错误。
* 修复:当`main()`中的timeout设置为1时,断言失败的问题。
### 2.1.0
* 增加数据库操作,同时支持`sqlite3`、`mysql`。
* 优化`file_data()`,兼容2.0.0用法。
### 2.0.1
* 优化 `file_data()`, 自动查找数据文件。
* 优化脚手架,创建项目例子更新。
### 2.0.0
* webdriver API 修改
* 移除 `self.get()`
* 增加 `self.visit()`
* 移除 `self.open_new_window()`
* 移除 `self.current_window_handle()`
* 移除 `self.new_window_handle()`
* 移除 `self.window_handles()`
* 修改 `self.switch_to_window()` 用法
* 优化打印日志,为每种操作加上 emoji
* 增加`expected_failure`用例装饰器,用于标记一条用例失败
* 增加 `file_dir()`, 返回当前文件所在目录的绝对路径。
* 运行完成自动通过浏览器打开HTML报告
* `main()`方法修改
* 修复`debug`参数类型错误异常提示
* 控制台更换字符logo*
* 整合 webdriver/request
* 上线 readthedocs 文档
### 2.0.0.beta
* 支持 HTTP接口测试
### 1.10.3
...
### 1.10.2
* HTMLTestRunner代码优化
* 修复bug
### 1.10.1
* webdriver代码重构
* 修复严重bug
### 1.10.0
* 增加断言元素方法:`assertElement`、`assertNotElement`
* 增加单个测试类、用例执行的方法
* 修复报告样式bug
* 命令行工具优化
### 1.9.0
* 测试报告重构
* 用例描述单独一列
* 增加单个用例运行时间
* 新的报告样式
* 脚手架工具创建项目更新
* 增加随机生成手机号方法
### 1.8.0
* 增加用例依赖装饰器
### 1.7.2
* bug修复版本
### 1.7.0
* 重构浏览器驱动,开放浏览器可配置能力。
### 1.6.0
* 浏览器增加简写
* 支持 logs 日志
* 支持 XML 测试报告
* 增加 file_data 方法实现参数化。
* 修复一些bug
### 1.5.6
* 封装test fixture方法
### 1.5.5
* 修改HTMLTestRunner 错误日志的展示
* 增加mobile web的支持
### 1.5.4
* 增加keys键盘操作
* 元素操作增加聚焦
* debug 模式增加慢操作
### 1.5.3
* 修复bug
* 增加 yaml_to_list()方法
### 1.5.2
* 修复bug
### 1.5.1
* 修复日志重复打印问题
* 修复测试报告不截图问题
* 日志增加emoji表情
### 1.5.0
* 自动化运行过程中,对操作的元素加边框,使其更醒目。
* 去掉对 `setUpClass()`方法的占用,代码做了较大重构。
* 在使用poium时,驱动的获取方式改变,这一点不向下兼容。
### 1.2.6
* 完善自动化发邮件功能
* 增加 type_enter() 方法
* 优化项目的代码的调用
* 修复 seldom + poium 日志问题
### 1.2.5
* 重新定制测试报告样式
* seldom.main()增加timeout参数
### 1.2.4
* 增加数据解析相关操作方法
* 增加跳过测试相关方法
* 增加发邮件功能
* 修复bug, 优化代码
### 1.2.3
* 增加 slow_click() 方法。
* seldom.main() 默认运行当前文件不需要传参。
* seldom.main(report="report-name.html") 允许自定义报告名称。
### 1.2.2
* fix bug
* add function: csv_to_list()/ excel_to_list()
### 1.2.0
Global launch browser
### 1.1.0
selenium grid support
Added safari support
### 1.0.0
The framework function has been basically improved. I'm glad to release version 1.0
### 0.3.6
Add cookie manipulation APIs
Optimized element wait
### 0.3.5
Added chrome/firefox browser driver download command
Driver file path Settings are supported
### 0.3.3
add skip case
### 0.3.2
Added a switch to display the last rerun result
Optimized assertion method
### 0.3.0
Update element positioning
### 0.2.0
Change the project name to seldom
Introducing the poium test library,
### 0.1.5
* Increased test case failure rerun
* Add use case failure screenshots
### 0.1.2
new framework
#### 0.0.9
Simplifying API calls
#### 0.0.8
add parameterized
Beautification test report
#### 0.0.7
Re based on unittest.
#### 0.0.6
add setup.py file, Specification of the installation process, a time to install all dependencies.
Delete unnecessary files
#### 0.0.5
Increase the support of multiple positioning methods
#### 0.0.4
Method to add default to wait.
Modify the realization of the individual methods
#### 0.0.3.1 version update:
* Repair part bug.
#### 0.0.3 version update:
* With the nose instead of unittest.
* Discard HTMLTestRunner,Integrated nose-html-reporting.
* modify the examples under demo.
#### 0.0.2 version update:
* all the elements of the operation selector xpath replaced by css, css syntax because more concise.
* when you run the test case no longer need to specify the directory, the default directory for the current test.
* modify the examples under demo.
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2019 Software Freedom Conservancy (SFC)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MANIFEST.in
================================================
recursive-include seldom/running/html *.html
================================================
FILE: README.md
================================================
[GitHub](https://github.com/SeldomQA/seldom) | [Gitee](https://gitee.com/fnngj/seldom) |

[](https://badge.fury.io/py/seldom) 
Seldom is an automation testing framework based on unittest.
> seldom 是基于unittest 的自动化测试框架。
### Features
⭐ web/app/api全功能测试框架
⭐ 提供脚手架快速创建自动化项目
⭐ 集成`XTestRunner`测试报告,现代美观
⭐ 提供丰富的断言
⭐ 提供强大的`数据驱动`
⭐ 平台化支持
### Install
```shell
pip install seldom
```
If you want to keep up with the latest version, you can install with GitHub/Gitee repository url:
```shell
> pip install -U git+https://github.com/SeldomQA/seldom.git@master
> pip install -U git+https://gitee.com/fnngj/seldom.git@master
```
### 🤖 Quick Start
1、查看帮助:
```shell
seldom --help
Usage: seldom [OPTIONS]
seldom CLI.
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────╮
│ --version -v Show version. │
│ --project-api -api TEXT Create a project of API type. [default: None] │
│ --project-app -app TEXT Create a project of App type [default: None] │
│ --project-web -web TEXT Create a project of Web type [default: None] │
│ --clear-cache -cc Clear all caches of seldom. │
│ --log-level -ll TEXT Set the log level [TRACE |DEBUG | INFO | SUCCESS | │
│ WARNING | ERROR]. │
│ [default: None] │
│ --mod -m TEXT Run tests modules, classes or even individual test │
│ methods from the command line. │
│ [default: None] │
│ --path -p TEXT Run test case file path. [default: None] │
│ --env -e TEXT Set the Seldom run environment `Seldom.env`. │
│ [default: None] │
│ --browser -b TEXT The browser that runs the Web UI automation tests │
│ [chrome | edge | firefox | chromium]. Need the --path. │
│ [default: None] │
│ --base-url -u TEXT The base-url that runs the HTTP automation tests. Need │
│ the --path. │
│ [default: None] │
│ --debug -d Debug mode. Need the --path/--mod. │
│ --rerun -rr INTEGER The number of times a use case failed to run again. │
│ Need the --path. │
│ [default: 0] │
│ --report -r TEXT Set the test report for output. Need the --path. │
│ [default: None] │
│ --collect -c Collect project test cases. Need the --path. │
│ --level -l TEXT Parse the level of use cases [data | case]. Need the │
│ --path. │
│ [default: data] │
│ --case-json -j TEXT Test case files. Need the --path. [default: None] │
│ --har2case -h2c TEXT HAR file converts an seldom test case. [default: None] │
│ --swagger2case -s2c TEXT Swagger file converts an seldom test case. │
│ [default: None] │
│ --api-excel TEXT Run the api test cases in the excel file. │
│ [default: None] │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or │
│ customize the installation. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
```
2、创建项目:
```shell
> seldom -api myapi # API automation test project.
> seldom -app myapp # or App automation test project.
> seldom -web myweb # or Web automation test project.
```
目录结构如下:
```shell
myweb/
├── test_dir/
│ ├── __init__.py
│ └── test_sample.py
├── test_data/
│ └── data.json
├── reports/
└── confrun.py
```
* `test_dir/` 测试用例目录。
* `test_data/` 测试数据文件目录。
* `reports/` 测试报告目录。
* `confrun.py` 运行配置文件。
3、运行项目:
* ❌️ 在`PyCharm`中右键执行。
* ✔️ 通过命令行工具执行。
```shell
> seldom -p test_dir # 运行 test_dir 测试目录
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v3.x.x
-----------------------------------------
@itest.info
...
2022-04-30 18:37:36 log.py | INFO | ✅ Find 1 element: id=sb_form_q -> input 'seldom'.
2022-04-30 18:37:39 log.py | INFO | 👀 assertIn title: seldom - 搜索.
.52022-04-30 18:37:39 log.py | INFO | 📖 https://cn.bing.com
2022-04-30 18:37:41 log.py | INFO | ✅ Find 1 element: id=sb_form_q -> input 'poium'.
2022-04-30 18:37:42 log.py | INFO | 👀 assertIn title: poium - 搜索.
.62022-04-30 18:37:42 log.py | INFO | 📖 https://cn.bing.com
2022-04-30 18:37:43 log.py | INFO | ✅ Find 1 element: id=sb_form_q -> input 'XTestRunner'.
2022-04-30 18:37:44 log.py | INFO | 👀 assertIn title: XTestRunner - 搜索.
.72022-04-30 18:37:44 log.py | INFO | 📖 http://www.itest.info
2022-04-30 18:37:52 log.py | INFO | 👀 assertIn url: http://www.itest.info/.
.82022-04-30 18:37:52 log.py | SUCCESS | generated html file: file:///D:\mypro\reports\2022_04_30_18_37_29_result.html
2022-04-30 18:37:52 log.py | SUCCESS | generated log file: file:///D:\mypro\reports\seldom_log.log
```
4、查看报告
你可以到 `mypro\reports\` 目录查看测试报告。

## 🔬 Demo
> seldom继承unittest单元测试框架,完全遵循unittest编写用例规范。
[demo](/demo) 提供了丰富实例,帮你快速了解seldom的用法。
### Web UI 测试
```python
import seldom
from seldom import Steps
class BaiduTest(seldom.TestCase):
def test_case_one(self):
"""a simple test case """
self.open("https://www.baidu.com")
self.type(id_="kw", text="seldom")
self.click(css="#su")
self.assertTitle("seldom_百度搜索")
def test_case_two(self):
"""method chaining """
Steps().open("https://www.baidu.com").find("#kw").type("seldom").find("#su").click()
self.assertTitle("seldom_百度搜索")
if __name__ == '__main__':
seldom.main(browser="chrome")
```
__说明:__
* `seldom.main()` 通过 `browser` 指定运行的浏览器。
### HTTP 测试
seldom 2.0 支持HTTP测试
```python
import seldom
class TestRequest(seldom.TestCase):
def test_put_method(self):
self.put('/put', data={'key': 'value'})
self.assertStatusCode(200)
def test_post_method(self):
self.post('/post', data={'key': 'value'})
self.assertStatusCode(200)
def test_get_method(self):
payload = {'key1': 'value1', 'key2': 'value2'}
self.get("/get", params=payload)
self.assertStatusCode(200)
def test_delete_method(self):
self.delete('/delete')
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(base_url="http://httpbin.org")
```
__说明:__
* `seldom.main()` 通过 `base_url` 指定接口项目基本URL地址。
### App 测试
seldom 3.0 支持App测试
```python
import seldom
from seldom.appium_lab.keyboard import KeyEvent
from seldom.appium_lab.android import UiAutomator2Options
class TestBingApp(seldom.TestCase):
def start(self):
self.ke = KeyEvent(self.driver)
def test_bing_search(self):
"""
test bing App search
"""
self.sleep(2)
self.click(id_="com.microsoft.bing:id/sa_hp_header_search_box")
self.type(id_="com.microsoft.bing:id/sapphire_search_header_input", text="seldomQA")
self.ke.press_key("ENTER")
self.sleep(1)
elem = self.get_elements(xpath='//android.widget.TextView')
self.assertIn("seldom", elem[0].text.lower())
if __name__ == '__main__':
capabilities = {
'deviceName': 'ELS-AN00',
'automationName': 'UiAutomator2',
'platformName': 'Android',
'appPackage': 'com.microsoft.bing',
'appActivity': 'com.microsoft.sapphire.app.main.MainSapphireActivity',
'noReset': True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, debug=True)
```
__说明:__
* `seldom.main()` 通过 `app_info` 指定App信息; `app_server` 指定appium server 地址。
## 📖 Document
[中文文档](https://seldomqa.github.io/)
### 项目实例
B站实战视频:
https://www.bilibili.com/video/BV1QHQVYoEHC
基于seldom的web UI自动化项目:
https://github.com/SeldomQA/seldom-web-testing
基于seldom的接口自动化项目:
https://github.com/defnngj/seldom-api-testing
## 微信(WeChat)
> 相关书籍推荐, 基于 SeldomQA 相关开源项目,虫师 编著。
<p>
<a href="https://item.jd.com/14859108.html">
<img alt="京东链接" src="./images/book.jpg" style="width: 220px; margin-right: 140px;" />
</a>
</p>
> 欢迎添加微信,交流和反馈问题。
<div style="display: flex;justify-content: space-between;width: 100%">
<p><img alt="微信" src="./images/wechat.jpg" style="width: 200px;height: 100%" ></p>
</div>
### Star History

### 感谢
感谢从以下项目中得到思路和帮助。
* [parameterized](https://github.com/wolever/parameterized)
* [utx](https://github.com/jianbing/utx)
### 贡献者
<a href="https://github.com/SeldomQA/seldom/graphs/contributors">
<img src="https://contrib.rocks/image?repo=SeldomQA/seldom" />
</a>
### 交流
QQ群:948994709
================================================
FILE: api_case/confrun.py
================================================
"""
seldom confrun.py hooks function
"""
def base_url():
"""
http test
api base url
"""
return "http://www.httpbin.org"
def debug():
"""
debug mod
"""
return False
def rerun():
"""
error/failure rerun times
"""
return 0
def report():
"""
setting report path
Used:
return "d://mypro/result.html" or "d://mypro/result.xml"
"""
return None
def timeout():
"""
setting timeout
"""
return 10
def title():
"""
setting report title
"""
return "seldom 执行 excel 接口用例"
def tester():
"""
setting report tester
"""
return "bugmaster"
def description():
"""
setting report description
"""
return ["windows", "api"]
def language():
"""
setting report language
return "en" or "zh-CN"
"""
return "en"
def failfast():
"""
fail fast
:return:
"""
return False
================================================
FILE: demo/README.md
================================================
## seldom demo
通过 demo 帮助你快速了解seldom的使用。
### 准备工作
* 目录树:
```shell
./demo
├── README.md
├── __init__.py
├── confrun.py # 运行配置钩子函数
├── reports # 测试报告
├── test_data # 测试数据
└── test_dir # 测试用例
├── __init__.py
├── api_case # http接口用例
├── app_case # app UI 自动化用例
└── web_case # web UI 自动化用例
```
* 请安装 seldom 最新版本。
```shell
> pip install -U seldom
```
* 确保 seldom 命令可用。
```shell
> seldom --version
seldom, version 3.1.1
```
### 使用方法
seldom 从 3.1 开始支持 `confrun.py` 配置运行钩子函数,并推荐你使用这种方式。
| 函数 | 类型 | 说明 |
|-------------|---------|-----------------------------------------------------------|
| start_run() | fixture | 运行测试之前执行 |
| end_run() | fixture | 运行测试之后执行 |
| browser() | web | 设置浏览器类型:gc(google chrome)/ff(firefox)/edge/ie/safari |
| base_url() | api | 设置http接口基本url: 例如 http://httpbin.org |
| app_info() | app | 基于appium,启动app信息 |
| app_server() | app | 基于appium,设置appium 服务地址+端口 |
| debug() | general | 是否开启debug模式:True/False |
| rerun() | general | 用例失败/错误重跑,默认:0 |
| report() | general | 指定报告生成地址,例如: `/User/tech/xxx.html`、 `/User/tech/xxx.xml` |
| timeout() | general | 全局运行超时时间,默认:10 |
| title() | general | 测试报告标题:html报告 |
| tester() | general | 测试人员名字:html报告 |
| description() | general | 测试报告描述:html报告 |
| language() | general | 测试报告语言:html报告,类型: `en`、`zh-CN` |
| whitelist() | general | 运行用例白名单 |
| blacklist() | general | 运行用例黑名单 |
__特殊配置__
特殊配置是针对不同的测试类型设置的配置。
* web UI 自动化配置
```python
# confrun.py
def browser():
"""
Web UI test:
browser: gc(google chrome)/ff(firefox)/edge/ie/safari
"""
return "gc"
```
运行测试:
```shell
> seldom --path test_dir/web_case/
> seldom --path test_dir/web_case/test_playwright_demo.py
```
* http 接口测试
```python
# confrun.py
def base_url():
"""
http test
api base url
"""
return "http://httpbin.org"
```
运行测试:
```shell
> seldom --path test_dir/web_case/
```
* app UI 自动化测试
```python
# confrun.py
def app_info():
"""
app UI test
appium app config
"""
desired_caps = {
'deviceName': 'JEF_AN20',
'automationName': 'UiAutomator2',
'platformName': 'Android',
'platformVersion': '10.0',
'appPackage': 'com.meizu.flyme.flymebbs',
'appActivity': '.ui.LoadingActivity',
'noReset': True,
}
return desired_caps
def app_server():
"""
app UI test
appium server/desktop address
"""
return "http://127.0.0.1:4723"
```
运行测试:
```shell
> seldom --path test_dir/app_case/
```
__通用配置__
通用配置是不管运行什么类型的测试都适用的配置。
```python
# confrun.py
def debug():
"""
debug mod
"""
return False
def rerun():
"""
error/failure rerun times
"""
return 0
def report():
"""
setting report path
Used:
return "d://mypro/result.html"
return "d://mypro/result.xml"
"""
return None
def timeout():
"""
setting timeout
"""
return 10
def title():
"""
setting report title
"""
return "seldom test report"
def tester():
"""
setting report tester
"""
return "bugmaster"
def description():
"""
setting report description
"""
return ["windows", "jenkins"]
def language():
"""
setting report language
return "en"
return "zh-CN"
"""
return "en"
def whitelist():
"""test label white list"""
return []
def blacklist():
"""test label black list"""
return []
```
================================================
FILE: demo/__init__.py
================================================
================================================
FILE: demo/confrun.py
================================================
"""
seldom confrun.py hooks function
"""
def start_run():
"""
Test the hook function before running
"""
...
def end_run():
"""
Test the hook function after running
"""
...
def debug():
"""
debug mod
"""
return False
def rerun():
"""
error/failure rerun times
"""
return 0
def report():
"""
setting report path
Used:
return "d://mypro/result.html"
return "d://mypro/result.xml"
"""
return None
def timeout():
"""
setting timeout
"""
return 10
def title():
"""
setting report title
"""
return "seldom test report"
def tester():
"""
setting report tester
"""
return "bugmaster"
def description():
"""
setting report description
"""
return ["windows", "jenkins"]
def language():
"""
setting report language
return "en"
return "zh-CN"
"""
return "en"
def whitelist():
"""test label white list"""
return []
def blacklist():
"""test label black list"""
return []
================================================
FILE: demo/reports/.keep
================================================
================================================
FILE: demo/run.py
================================================
import seldom
"""
说明:
path: 指定测试目录。
browser: Web测试,指定浏览器,默认chrome - web专用
base_url: Http测试,指定接口地址。 - api专用
app_info: 启动app配置。 - app专用
app_server: appium server 地址。 - app专用
title: 指定测试项目标题。
tester: 指定测试人员。
description: 指定测试环境描述。
debug: debug模式,设置为True不生成测试用例。
rerun: 测试失败重跑
"""
if __name__ == '__main__':
# web case 配置
seldom.main(path="./test_dir/web_case",
browser="chrome",
title="seldom Web demo",
tester="虫师",
description=["Browser: Chrome"],
rerun=2)
# api case 配置
# seldom.main(path="./test_dir/api_case",
# base_url="http://httpbin.org",
# title="seldom API demo",
# tester="虫师",
# rerun=2)
# app case 配置
# from seldom.appium_lab.android import UiAutomator2Options
# capabilities = {
# 'deviceName': 'ELS-AN00',
# 'automationName': 'UiAutomator2',
# 'platformName': 'Android',
# 'appPackage': 'com.microsoft.bing',
# 'appActivity': 'com.microsoft.sapphire.app.main.MainSapphireActivity',
# 'noReset': True,
# }
# options = UiAutomator2Options().load_capabilities(capabilities)
# seldom.main(path="./test_dir/app_case",
# app_server="http://127.0.0.1:4723",
# app_info=options,
# title="seldom App demo",
# tester="虫师",
# rerun=2)
================================================
FILE: demo/test_data/csv_data.csv
================================================
firstname,lastname
Forest,Hobbs
Ferdinand,Lozano
================================================
FILE: demo/test_data/json_data.json
================================================
{
"name": [
["Wayne", "Burch"],
["Jamie-louise", "Wong"]
],
"login":[
{
"scene": "tom用户登录",
"username": "Tom",
"password": "tom123"
},
{
"scene": "jerry用户登录",
"username": "Jerry",
"password": "jerry123"
}
]
}
================================================
FILE: demo/test_data/yaml_data.yaml
================================================
---
name:
- - Elnora
- West
- - Leon
- Richard
login:
- username: Tom
password: tom123
- username: Jerry
password: jerry123
================================================
FILE: demo/test_dir/__init__.py
================================================
================================================
FILE: demo/test_dir/api_case/__init__.py
================================================
================================================
FILE: demo/test_dir/api_case/test_http_demo.py
================================================
import seldom
from seldom import data
class TestRequest(seldom.TestCase):
"""
http api test demo
doc: https://requests.readthedocs.io/en/master/
"""
def test_put_method(self):
"""
test put request
"""
self.put('/put', data={'key': 'value'})
self.assertStatusCode(200)
def test_post_method(self):
"""
test post request
"""
self.post('/post', data={'key':'value'})
self.assertStatusCode(200)
def test_get_method(self):
"""
test get request
"""
payload = {'key1': 'value1', 'key2': 'value2'}
self.get("/get", params=payload)
self.assertStatusCode(200)
def test_delete_method(self):
"""
test delete request
"""
self.delete('/delete')
self.assertStatusCode(200)
class TestAssert(seldom.TestCase):
"""
Test Assert
"""
def test_data_assert(self):
"""
The JSON data returned by the assertion
:return:
"""
self.get("/get")
self.assertStatusCode(200)
assert_data = {"headers": {"Host": "httpbin.org", "User-Agent": "python-requests/2.26.0"}}
self.assertJSON(assert_data, exclude=["headers", "user-agent"]) # exclude 过滤掉 json中的部分字段。
def test_format_assert(self):
"""
Assert json-schema
help doc: https://json-schema.org/
"""
self.get("/get")
self.assertStatusCode(200)
# 数据校验
schema = {
"type": "object",
"properties": {
"headers": {
"Host": "httpbin.org",
"User-Agent": "python-requests/2.22.0"
},
"origin": {"type": "string"},
"url": {
"type": "string",
"minLength": 20
}
},
}
self.assertSchema(schema)
def test_path_assert(self):
"""
assert jmesPath
help doc: https://jmespath.org/
"""
payload = {"foot": "bread"}
self.get('/get', params=payload)
self.assertPath("args.foot", "bread")
class TestRespData(seldom.TestCase):
"""
Test response data
"""
def test_resp_data(self):
"""
Get the returned data
"""
payload = {'key1': 'value1', 'key2': 'value2'}
self.post("/post", data=payload)
self.assertStatusCode(200)
self.assertEqual(self.response["form"]["key1"], "value1")
self.assertEqual(self.response["form"]["key2"], "value2")
def test_data_dependency(self):
"""
Test for interface data dependencies
"""
headers = {"X-Account-Fullname": "bugmaster"}
self.get("/get", headers=headers)
self.assertStatusCode(200)
username = self.response["headers"]["X-Account-Fullname"]
self.post("/post", data={'username': username})
self.assertStatusCode(200)
class TestDDT(seldom.TestCase):
"""
Test Data Driver
"""
@data([
("key1", 'value1'),
("key2", 'value2'),
("key3", 'value3')
])
def test_data(self, key, value):
"""
Data-Driver Tests
"""
payload = {key: value}
self.post("/post", data=payload)
self.assertStatusCode(200)
self.assertEqual(self.response["form"][key], value)
if __name__ == '__main__':
seldom.main(base_url="http://httpbin.org", debug=True)
================================================
FILE: demo/test_dir/app_case/__init__.py
================================================
================================================
FILE: demo/test_dir/app_case/test_first_demo.py
================================================
from appium.options.android import UiAutomator2Options
import seldom
from seldom.appium_lab.keyboard import KeyEvent
class TestBingApp(seldom.TestCase):
"""
Test Bing APP
"""
def start(self):
self.ke = KeyEvent(self.driver)
def test_bing_search(self):
"""
test bing App search
"""
self.sleep(2)
self.click(id_="com.microsoft.bing:id/sa_hp_header_search_box")
self.type(id_="com.microsoft.bing:id/sapphire_search_header_input", text="seldomQA")
self.ke.press_key("ENTER")
self.sleep(1)
elem = self.get_elements(xpath='//android.widget.TextView')
self.assertIn("seldom", elem[0].text.lower())
if __name__ == '__main__':
capabilities = {
'deviceName': 'ELS-AN00',
'automationName': 'UiAutomator2',
'platformName': 'Android',
'appPackage': 'com.microsoft.bing',
'appActivity': 'com.microsoft.sapphire.app.main.MainSapphireActivity',
'noReset': True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, debug=True)
================================================
FILE: demo/test_dir/app_case/test_po_demo.py
================================================
from poium import Page, Element
import seldom
from seldom.appium_lab.keyboard import KeyEvent
from seldom.appium_lab.android import UiAutomator2Options
class BingPage(Page):
"""BBS Page"""
search_button = Element("id=com.microsoft.bing:id/sa_hp_header_search_box")
search_input = Element("id=com.microsoft.bing:id/sapphire_search_header_input")
search_count = Element('//android.widget.TextView[@resource-id="count"]')
class TestBingApp(seldom.TestCase):
"""
Test Bing App
"""
def start(self):
self.bing_page = BingPage(self.driver)
self.ke = KeyEvent(self.driver)
def test_bbs(self):
"""
test bbs search
"""
self.sleep(2)
self.bing_page.search_button.click()
self.sleep(1)
self.bing_page.search_input.send_keys("seldom")
self.ke.press_key("ENTER")
self.sleep(1)
counts = self.bing_page.search_count
self.assertIn("个结果", counts.text.lower())
if __name__ == '__main__':
capabilities = {
'deviceName': 'ELS-AN00',
'automationName': 'UiAutomator2',
'platformName': 'Android',
'appPackage': 'com.microsoft.bing',
'appActivity': 'com.microsoft.sapphire.app.main.MainSapphireActivity',
'noReset': True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, debug=True)
================================================
FILE: demo/test_dir/app_case/test_u2_demo.py
================================================
"""
需要安装 uiautomator2: https://github.com/openatx/uiautomator2
> pip install uiautomator2
"""
import seldom
import uiautomator2 as u2
class MyAppTest(seldom.TestCase):
def start(self):
# 链接设备
self.d = u2.connect('192.168.31.234')
# 启动App
self.d.app_start("com.meizu.mzbbs")
def end(self):
# 停止app
self.d.app_stop("com.meizu.mzbbs")
def test_app(self, user):
""" 使用 uiautomator2 """
# 搜索
self.d(resourceId="com.meizu.flyme.flymebbs:id/nw").click()
# 输入关键字
self.d(resourceId="com.meizu.flyme.flymebbs:id/nw").set_text("flyme")
# 搜索按钮
self.d(resourceId="com.meizu.flyme.flymebbs:id/o1").click()
self.sleep(2)
if __name__ == '__main__':
seldom.main(debug=True)
================================================
FILE: demo/test_dir/web_case/__init__.py
================================================
================================================
FILE: demo/test_dir/web_case/test_data_demo.py
================================================
import seldom
from seldom import testdata
class RandomDataTest(seldom.TestCase):
"""
Randomly generate test data
"""
def test_case(self):
"""
used testdata test
"""
self.open("https://www.runoob.com/try/try.php?filename=tryhtml_input")
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=testdata.first_name())
self.type(name="lastname", text=testdata.last_name())
self.sleep(1)
if __name__ == '__main__':
seldom.main(browser="gc", debug=True)
================================================
FILE: demo/test_dir/web_case/test_ddt_demo.py
================================================
import seldom
from seldom import data, file_data
class BingTest(seldom.TestCase):
"""Bing search test case"""
@data([
("First case description", "seldom"),
("Second case description", "selenium"),
("Third case description", "unittest"),
])
def test_bing_tuple(self, desc, search_key):
"""
used tuple test data
:param desc: case desc
:param search_key: search keyword
"""
self.open("https://cn.bing.com")
self.type(id_="sb_form_q", text=search_key, enter=True)
self.assertInTitle(search_key)
@data([
["First case description", "seldom"],
["Second case description", "selenium"],
["Third case description", "unittest"],
])
def test_bing_list(self, desc, search_key):
"""
used list test data
:param desc: case desc
:param search_key: search keyword
"""
self.open("https://cn.bing.com")
self.type(id_="sb_form_q", text=search_key, enter=True)
self.assertInTitle(search_key)
@data([
{"scene": "First case description", "search_key": "seldom"},
{"scene": "Second case description", "search_key": "selenium"},
{"scene": "Third case description", "search_key": "unittest"},
])
def test_bing_dict(self, scene, search_key):
"""
used dict test data
:param scene: case desc
:param search_key: search keyword
"""
self.open("https://cn.bing.com")
self.type(id_="sb_form_q", text=search_key, enter=True)
self.assertInTitle(search_key)
class FileDataTest(seldom.TestCase):
"""form input test case"""
def start(self):
self.test_url = "https://www.w3school.com.cn/tiy/t.asp?f=eg_html_form_submit"
@file_data("json_data.json", key="name")
def test_json_list(self, firstname, lastname):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=firstname, clear=True)
self.type(name="lastname", text=lastname, clear=True)
self.sleep(1)
@file_data("json_data.json", key="login")
def test_json_dict(self, _, username, password):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=username, clear=True)
self.type(name="lastname", text=password, clear=True)
self.sleep(1)
@file_data("yaml_data.yaml", key="name")
def test_yaml_list(self, firstname, lastname):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=firstname, clear=True)
self.type(name="lastname", text=lastname, clear=True)
self.sleep(1)
@file_data("yaml_data.yaml", key="login")
def test_yaml_dict(self, username, password):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=username, clear=True)
self.type(name="lastname", text=password, clear=True)
self.sleep(1)
@file_data("csv_data.csv", line=2)
def test_csv(self, firstname, lastname):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=firstname, clear=True)
self.type(name="lastname", text=lastname, clear=True)
self.sleep(1)
@file_data(file="excel_data.xlsx", sheet="Sheet1", line=2)
def test_excel(self, firstname, lastname):
"""
used file_data test
"""
self.open(self.test_url)
self.switch_to_frame(id_="iframeResult")
self.type(name="firstname", text=firstname, clear=True)
self.type(name="lastname", text=lastname, clear=True)
self.sleep(1)
if __name__ == '__main__':
seldom.main(browser="gc", debug=False)
================================================
FILE: demo/test_dir/web_case/test_first_demo.py
================================================
import seldom
from seldom import Steps
class BingTest(seldom.TestCase):
"""Bing search test case"""
def test_case(self):
"""a simple test case """
self.open("https://cn.bing.com")
self.type(id_="sb_form_q", text="seldom", enter=True)
self.sleep(2)
self.assertInTitle("seldom")
def test_case_two(self):
"""method chaining """
Steps(url="https://cn.bing.com").open().find("#sb_form_q").type("seldom").submit().sleep(2)
self.assertInTitle("seldom")
if __name__ == '__main__':
seldom.main(browser="gc")
================================================
FILE: demo/test_dir/web_case/test_fixture_demo.py
================================================
import seldom
class BaiduTest(seldom.TestCase):
"""
* start_class/end_class
* start/end
"""
@classmethod
def start_class(cls):
"""start class"""
print("test class start")
cls.max_window(cls)
@classmethod
def end_class(cls):
"""end class"""
...
def start(self):
"""start"""
print("test case start")
self.index_page = "https://www.baidu.com/"
self.news_page = "https://news.baidu.com/"
def end(self):
"""end"""
...
def test_open_index(self):
"""open baidu index page"""
self.open(self.index_page)
def test_open_news(self):
"""open baidu news page"""
self.open(self.news_page)
if __name__ == '__main__':
seldom.main(debug=True)
================================================
FILE: demo/test_dir/web_case/test_playwright_demo.py
================================================
"""
需要安装 playwright: https://playwright.dev/
> pip install playwright
"""
import seldom
from playwright.sync_api import sync_playwright
from playwright.sync_api import expect
class Playwright(seldom.TestCase):
def start(self):
p = sync_playwright().start()
self.browser = p.chromium.launch()
self.page = self.browser.new_page()
def end(self):
self.browser.close()
def test_start(self):
page = self.page
page.goto("https://playwright.dev")
print(page.title())
expect(page).to_have_title("Fast and reliable end-to-end testing for modern web apps | Playwright")
get_started = page.locator('text=Get Started')
expect(get_started).to_have_attribute('href', '/docs/intro')
get_started.click()
expect(page).to_have_url('https://playwright.dev/docs/intro')
if __name__ == '__main__':
seldom.main()
================================================
FILE: demo/test_dir/web_case/test_po_demo.py
================================================
"""
page object model
Using the poium Library
https://github.com/SeldomQA/poium
```
> pip install poium>=1.6.1
```
"""
import seldom
from poium import Page, Element, Elements
class BaiduPage(Page):
"""baidu page"""
input = Element("#kw", describe="搜索输入框")
button = Element("id=su", describe="搜索按钮")
result = Elements("//div/h3/a", describe="搜索结果")
class BaiduTest(seldom.TestCase):
"""Baidu search test case"""
def test_baidu_page(self):
"""
A simple test
"""
page = BaiduPage()
page.open("https://www.baidu.com")
self.sleep(3)
# assert element is exist
self.assertTrue(page.input.is_exist())
self.assertTrue(page.button.is_exist())
# operation element
page.input.send_keys("(title:seldom)")
page.button.click()
self.sleep(3)
# assert title
self.assertTitle("(title:seldom)_百度搜索")
# Loop assertion result
for r in page.result:
# assert text in
self.assertIn("seldom", r.text.lower())
if __name__ == '__main__':
seldom.main(browser='chrome')
================================================
FILE: description.rst
================================================
seldom
---------------
Seldom is an automation testing framework based on unittest.
Installation
------------
$ pip install seldom
Documentation
++++++++++++++++++
https://seldomqa.github.io
================================================
FILE: docs/.gitignore
================================================
node_modules
.temp
.cache
.DS_Store
vpdocs/.vuepress/dist
================================================
FILE: docs/README.md
================================================
## ☘️Introduction
此目录用于**存放 & 编辑** seldom 相关文档
## 📖 Document
[中文文档](https://seldomqa.github.io/)
[English document(readthedocs)](https://seldomqa.readthedocs.io/en/latest/index.html)
## 结构
```shell
docs/
├── README.md
├── conf.py # rst文档配置文件
├── deploy.sh # vuepress文档部署脚本
├── index.rst
├── markdown2rst.py # md转rst脚本
├── package.json
├── requirements.txt # python模块依赖
├── rst_docs # 用于存放rst文档
├── vpdocs # 用于vuepress文档
│ ├── README.md
│ ├── advanced
│ ├── db
│ ├── getting-started
│ ├── http
│ ├── introduce.md
│ ├── other
│ └── platform
└── yarn.lock
```
## 如何贡献文档
1. clone 本项目
```bash
git clone https://github.com/SeldomQA/seldom.git
```
2. 进入到文档目录&启动项目
```bash
cd docs
npm install
npm run dev
npm run build
```
3. 编辑相关文档(编辑 vpdocs 目录下的文档)
================================================
FILE: docs/compare.md
================================================
## seldom vs pytest
| 功能 | seldom | pytest |
|---------------|--------------------------------------------|---------------------------------------|
| web UI测试 | 支持 ✅ | 支持(需安装 selenium) ⚠️ |
| web UI断言 | 支持(assertText、assertTitle、assertElement) ✅ | 不支持 ❌ |
| playwright | 支持(需安装playwright) ⚠️ | 支持(playwright提供playwright-pytest插件) ✅ |
| 失败截图 | 支持(自动实现) ✅ | 支持(需要设置) ✅ |
| http接口测试 | 支持 ✅ | 支持(需安装 requests) ⚠️ |
| http接口断言 | 支持(assertJSON、assertPath、assertSchema) ✅ | 不支持 ❌ |
| app UI测试 | 支持 ✅ | 支持(需安装 appium) ⚠️ |
| Page Object模式 | 支持(推荐poium) ✅ | 支持(推荐poium) ✅ |
| 脚手架 | 支持(快速创建项目) ✅ | 不支持 ❌ |
| 生成随机测试数据 | 支持`testdata` ✅ | 不支持 ❌ |
| 发送消息 | 支持(email、钉钉、飞书、微信)✅ | 不支持 ❌ |
| log日志 | 支持 ✅ | 不支持 ❌ |
| 数据库操作 | 支持(sqlite3、MySQL、SQL Server) ✅ | 不支持 ❌ |
| 用例依赖 | 支持`@depend()` ✅ | `@pytest.mark.dependency()`支持 ✅ |
| 失败重跑 | 支持`rerun` ✅ | pytest-rerunfailures 支持 ✅ |
| 用例分类标签 | 支持`@label()` ✅ | `@pytest.mark.xxx`支持 ✅ |
| HTML测试报告 | 支持 ✅ | pytest-html、allure ✅ |
| XML测试报告 | 支持 ✅ | 自带 `--junit-xml` ✅ |
| 数据驱动方法 | `@data()` ✅ | `@pytest.mark.parametrize()` ✅ |
| 数据驱动文件 | `@file_data()`(JSON\YAML\CSV\Excel) ✅ | 不支持 ❌ |
| 钩子函数 | `confrun.py`用例运行钩子 ⚠️ | `conftest.py` 功能更强大 ✅ |
| 命令行工具CLI | 支持`seldom` ✅ | 支持`pytest` ✅ |
| 并发执行 | 不支持 ❌ | pytest-xdist、pytest-parallel ✅ |
| 平台化 | 支持(seldom-platform)✅ | 不支持 ❌ |
| 第三方插件 | seldom(unittest)的生态比较糟糕 ⚠️ | pytest有丰富插件生态 ✅ |
__说明__
* ✅ : 表示支持。
* ⚠️: 支持,但支持的不好,或没有对方好。
* ❌ : 不支持,表示框架没有该功能,第三方插件也没有。
================================================
FILE: docs/deploy.sh
================================================
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
npm run build
# 进入生成的文件夹
cd vpdocs/.vuepress/dist
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://SeldomQA.github.io
git push -f https://github.com/SeldomQA/SeldomQA.github.io.git master
cd -
================================================
FILE: docs/package.json
================================================
{
"name": "vuepress-docs",
"version": "1.0.0",
"description": "docs by vuepress",
"main": "index.js",
"author": "Yongchin",
"license": "MIT",
"scripts": {
"dev": "vuepress dev vpdocs",
"build": "vuepress build vpdocs"
},
"type": "module",
"devDependencies": {
"@vuepress/bundler-vite": "^2.0.0-rc.20",
"@vuepress/plugin-search": "^2.0.0-rc.83",
"@vuepress/theme-default": "^2.0.0-rc.84",
"sass-embedded": "^1.85.1",
"vuepress": "^2.0.0-rc.20"
}
}
================================================
FILE: docs/vpdocs/.vuepress/config.js
================================================
import { defineUserConfig } from 'vuepress'
import { viteBundler } from '@vuepress/bundler-vite'
import { defaultTheme } from '@vuepress/theme-default'
import { searchPlugin } from '@vuepress/plugin-search'
export default defineUserConfig({
title: "seldom文档",
description: "seldom 是基于unittest 的自动化测试框架。",
base: "/",
head: [
['link', { rel: 'icon', href: '/logo.jpeg' }]
],
bundler: viteBundler({
viteOptions: {},
}),
plugins: [
searchPlugin({
// 配置项
}),
],
theme: defaultTheme({
repo: "SeldomQA/seldom",
docsBranch: "vuepress-docs/docs/vpdocs",
logo: "/logo.jpeg",
navbar: [
{ text: "介绍", link: "/introduce" },
{ text: "安装", link: "/getting-started/installation" },
],
sidebar: [
"/introduce",
{
text: "开始",
children: [
"/getting-started/installation",
"/getting-started/create_project",
"/getting-started/quick_start",
"/getting-started/advanced",
"/getting-started/data_driver",
"/getting-started/dependent_func",
"/getting-started/seldom_cli",
],
},
{
text: "web UI 测试",
children: [
"/web-testing/browser_driver",
"/web-testing/seldom_api",
"/web-testing/chaining",
"/web-testing/page_object",
"/web-testing/other",
],
},
{
text: "App UI 测试",
children: [
"/app-testing/start",
"/app-testing/appium_lab",
"/app-testing/page_object",
"/app-testing/extensions",
"/app-testing/adb_lib",
],
},
{
text: "HTTP接口测试",
children: [
"/api-testing/start",
"/api-testing/assert",
"/api-testing/api_object",
"/api-testing/more",
"/api-testing/api_case",
"/api-testing/webscocket",
],
},
{
text: "更多能力",
children: [
"/more-ability/db_operation",
"/more-ability/test_library",
"/more-ability/benchmark",
],
},
"/platform/platform",
"/version/CHANGES",
],
editLinks: true,
editLinkText: "在 GitHub 上编辑此页",
lastUpdated: "上次更新",
}),
})
================================================
FILE: docs/vpdocs/README.md
================================================
---
home: true
heroText: Seldom
heroImage: /image/book.jpg
actions:
- text: 快速上手→
link: /getting-started/quick_start.html
type: primary
- text: 项目简介
link: /introduce.html
type: secondary
features:
- title: 全能
details: seldom支持web/app/api等类型的自动化测试。
- title: 快速
details: 提供脚手架快速创建自动化项目。
- title: 报告
details: 集成XTestRunner测试报告,现代美观。
- title: 断言
details: 提供丰富的断言,方便验证测试结果。
- title: 数据驱动
details: 支持Excel/CSV/JSON/YAML数据文件。
- title: 平台化
details: seldom提供了平台化支持。
footer: MIT Licensed | Copyright © 2025-重定向科技
---
================================================
FILE: docs/vpdocs/api-testing/api_case.md
================================================
# 支持Excel测试用例
> seldom > 3.8.0
在编写接口测试用例的时候,有时候测试用例非常简单,比如单接口的测试,不需要登录token,不存在用例数据依赖,也不需要参数加密,此时,使用`Excel`
文件编写用例更为高效。
seldom支持了这种用例的编写。
### 编写Excel用例
[查看例子](https://github.com/SeldomQA/seldom/tree/master/api_case)
首先,创建一个Excel文件,格式如下。
| name | api | method | headers | param_type | params | assert | exclude |
|-----------------|-------|--------|---------|------------|--------|--------|---------|
| 简单GET接口 | /get | GET | {} | data | {} | {} | [] |
| 简单POST接口-json参数 | /post | POST | {} | json | {} | {} | [] |
| ... | | | | | | | |
__参数说明__
| 字段 | 说明 | 列子 |
|--------------|-------------------------------------------------------|------------------------------------------------------|
| `name` | 用例的名称,会在测试报告中展示。 | |
| `api` | 接口的地址,可以写完整的URL地址, 也可以只定义路径,`base_url` 在 `confrun.py` | 例如:`http://www.httpbin.org/get` or `/get` |
| `method` | 接口的请求方法,必须大写,不允许为空 | 支持:`GET`、`POST`、`PUT`、`DELETE` |
| `headers` | 请求头,不允许为空,默认为 `{}`,字段必须双引号`"`。 | 例如:`{"user-agent": "my-app/0.0.1"}` |
| `param_type` | 接口参数类型,必须小写,不允许为空。 | 例如:`data`、 `json` |
| `params` | 接口参数,不允许为空,默认为 `{}`,字段必须双引号`"`。 | 例如:`{"id": 1, "name": "jack"}` |
| `assert` | 断言接口返回,允许为空 或 `{}`, | 例如:`{"status": 200, "success": True, "data": [...]}` |
| `exclude` | 断言过滤字段,一些特殊的字段会导致断言失败,需要过滤掉。 | 例如:`["X-Amzn-Trace-Id", "timestamp"]` |
__confrun.py配置__
```python
def base_url():
"""
http test
api base url
"""
return "http://www.httpbin.org"
def debug():
"""
debug mod
"""
return False
def rerun():
"""
error/failure rerun times
"""
return 0
def report():
"""
setting report path
Used:
return "d://mypro/result.html" or "d://mypro/result.xml"
"""
return None
def timeout():
"""
setting timeout
"""
return 10
def title():
"""
setting report title
"""
return "seldom 执行 excel 接口用例"
def tester():
"""
setting report tester
"""
return "bugmaster"
def description():
"""
setting report description
"""
return ["windows", "api"]
def language():
"""
setting report language
return "en" or "zh-CN"
"""
return "zh-CN"
def failfast():
"""
fail fast
:return:
"""
return False
```
### 运行测试用例
* 目录结构
```shell
mypro/
├── api_case.xlsx
└── confrun.py
```
* 运行测试
```shell
> cd mypro
> seldom --api-excel api_case.xlsx
```
* 运行日志
```shell
seldom --api-excel .\api_case.xlsx
run .\api_case.xlsx file.
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v3.x.x
-----------------------------------------
@itest.info
2024-07-06 21:00:35 | INFO | runner.py | TestLoader: ...\Lib\site-packages\seldom\file_runner\api_excel.py
2024-07-06 21:00:35 | INFO | parameterization.py | find data file: .\api_case.xlsx
XTestRunner Running tests...
----------------------------------------------------------------------
2024-07-06 21:00:35 | INFO | api_excel.py | execute api case: [简单GET接口]
2024-07-06 21:00:35 | INFO | request.py | -------------- Request -----------------[🚀]
2024-07-06 21:00:35 | INFO | request.py | [method]: GET [url]: http://www.httpbin.org/get
2024-07-06 21:00:35 | DEBUG | request.py | [headers]:
{
"user-agent": "my-app/0.0.1"
}
2024-07-06 21:00:35 | DEBUG | request.py | [params]:
{
"key": "value"
}
2024-07-06 21:00:35 | INFO | request.py | -------------- Response ----------------[🛬️]
2024-07-06 21:00:35 | INFO | request.py | successful with status 200
2024-07-06 21:00:35 | DEBUG | request.py | [type]: json [time]: 0.481752
2024-07-06 21:00:35 | DEBUG | request.py | [response]:
{
"args": {
"key": "value"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "www.httpbin.org",
"User-Agent": "my-app/0.0.1",
"X-Amzn-Trace-Id": "Root=1-66893ff2-60ed7c5378ca01452917ea0c"
},
"origin": "14.155.89.115",
"url": "http://www.httpbin.org/get?key=value"
}
2024-07-06 21:00:35 | INFO | case.py | 👀 assertJSON -> {'args': {'key': 'value'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'www.httpbin.org', 'User-Agent': 'my-app/0.0.1', 'X-Amzn-Trace-Id': 'Root=1-668906ef-2e2d8c4c3f36a228264da1ab'}, 'origin': '14.155.89.115', 'url': 'http://www.httpbin.org/get?key=value'}.
...
```
* 生成测试报告

================================================
FILE: docs/vpdocs/api-testing/api_object.md
================================================
# API Object
API Object Models,简称AOM,AOM是一种设计模式,它围绕着将API、路由或功能交互及其相关行为封装在结构良好的对象中。AOM旨在增强API测试和集成的直观性和弹性。在实践中,AOM需要精心设计专门的API对象,以有效地保护用户免受与API 请求、响应、端点交互和身份验证过程相关的复杂性的影响。
seldom 支持AOM, 并且提供了一些好用的功能,辅助你使用AOM.
* 目录结构如下
```shell
mypro/
├── api/
│ ├── __init__.py
│ ├── auth_object.py
│ └── xxx_object.py
├── test_dir/
│ ├── test_auth.py
│ └── test_xxx.py
│ ...
```
* 创建 API Object
```python
# api/auth_object.py
from seldom.testdata import get_int
from seldom.request import HttpRequest
from seldom.request import check_response
class AuthAPIObject(HttpRequest):
def __init__(self, api_key):
self.api_key = api_key
@check_response(ret="form.token")
def get_token(self, user_id:str) -> str:
"""
模拟:根据用户ID生成登录token
:param user_id:
:return:
"""
data = {"user_id": user_id, "token": "t" + str(get_int(10000, 99999))}
r = self.post("/post?key=" + self.api_key, data=data)
return r
```
定义API接口,根据`get_token()`用于生成token,这里我们通过随机数模拟的生成的token。`check_response()`装饰器用于装饰接口,`form.token` 用于提取API的返回值。
* 创建测试用例
```python
# test_dir/test_auth.py
import seldom
from api.auth_object import AuthAPIObject
class TestAPI(seldom.TestCase):
def test_case(self):
auth_object = AuthAPIObject(api_key="abc123")
token = auth_object.get_token(user_id="123")
print("token", token)
if __name__ == '__main__':
seldom.main(debug=True, base_url="https://httpbin.org")
```
在用例层调用`AuthAPIObject`类下面的对象,测试API。
* AOM 原则
首先,API只允许通过的APIObject进行封装,那么在封装之前可以检索一下是否有封装了,如果有,进一步确认是否满足自己的调用需求,我们一般在测试API的时候一般各种参数验证,当API作为依赖接口调用的时候,一般参数比较少且固定,所以,API在封装的时候要兼顾到这两种情况。
其次,用例层只能通过APIObject的封装调用API,像登录token这种大部分API会用到的信息,可以通过类初始化时传入,后续调用类下面方法的时候就不需要关心的。如果是多个API组成一个场景,也可以再进行一层业务层的封装。
================================================
FILE: docs/vpdocs/api-testing/assert.md
================================================
# 接口断言
断言接口返回的数据是HTTP接口自动化测试非常重要的工作,提供强大的断言方法可以提高用例的编写效率。
## assertJSON
`assertJSON()` 断言接口返回的某部分数据。
* 请求参数
```json
{
"name": "tom",
"hobby": [
"basketball",
"swim"
]
}
```
* 返回结果
```json
{
"args": {
"hobby": [
"basketball",
"swim"
],
"name": "tom"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.25.0",
"X-Amzn-Trace-Id": "Root=1-62851614-1ca9fdb276238c60406c118f"
},
"origin": "113.87.15.99",
"url": "http://httpbin.org/get?name=tom&hobby=basketball&hobby=swim"
}
```
我的目标是断言`name` 和 `hobby` 部分的内容。
```python
import seldom
class TestAPI(seldom.TestCase):
def test_assert_json(self):
# 接口参数
payload = {"name": "tom", "hobby": ["basketball", "swim"]}
# 接口调用
self.get("http://httpbin.org/get", params=payload)
# 断言数据
assert_data = {
"hobby": ["swim", "basketball"],
"name": "tom"
}
self.assertJSON(assert_data, self.response["args"], exclude=["xxx"])
```
* exclude 用于设置跳过的检查字段,例如一些 时间、随机数 等,每次调用都不一样,但并不影响结果的正确性。通过 exclude
来设置屏蔽这些字段的检查。
## assertPath
`assertPath` 是基于 `jmespath` 实现的断言,功能非常强大。
* jmespath: https://jmespath.org/specification.html
接口返回数据如下:
```json
{
"args": {
"hobby": [
"basketball",
"swim"
],
"name": "tom"
}
}
```
seldom中可以通过path进行断言:
```python
import seldom
class TestAPI(seldom.TestCase):
def test_assert_path(self):
payload = {'name': 'tom', 'hobby': ['basketball', 'swim']}
self.get("http://httpbin.org/get", params=payload)
self.assertPath("args.name", "tom")
self.assertPath("args.hobby[0]", "basketball")
self.assertInPath("args.hobby[0]", "ball")
```
* `args.hobby[0]` 提取接口返回的数据。
* `assertPath()` 判断提取的数据是否等于`basketball`;
* `assertInPath()` 判断提取的数据是否包含`ball`。
## assertSchema
当你不关心数据本身是什么,而是关心数据的结构和类型时,可以使用 `assertSchema` 断言方法。 `assertSchema` 是基于 `jsonschema`
实现的断言方法。
* jsonschema: https://json-schema.org/learn/
```python
import seldom
from seldom.utils import genson
class TestAPI(seldom.TestCase):
def test_assert_schema(self):
# 接口参数
payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}
# 调用接口
self.get("/get", params=payload)
# 生成数据结构和类型
schema = genson(self.response["args"])
print("json Schema: \n", schema)
# 断言数据结构和类型
assert_data = {
"$schema": "http://json-schema.org/schema#",
"type": "object",
"properties": {
"age": {
"type": "string"
},
"hobby": {
"type": "array", "items": {"type": "string"}
},
"name": {
"type": "string"
}
},
"required":
["age", "hobby", "name"]
}
self.assertSchema(assert_data, self.response["args"])
```
* `genson`: 可以生成`jsonschema`数据结构和类型(`seldom 2.9` 新增)。
================================================
FILE: docs/vpdocs/api-testing/more.md
================================================
# 更多功能
### har to case
对于不熟悉 Requests 库的人来说,通过Seldom来写接口测试用例还是会有一点难度。于是,seldom 提供了`har` 文件转 `case` 的命令。
首先,打开fiddler 工具进行抓包,选中某一个请求。
然后,选择菜单栏:`file` -> `Export Sessions` -> `Selected Sessions...`

选择导出的文件格式。

点击`next` 保存为`demo.har` 文件。
最后,通过`seldom -h2c` 转为`demo.py` 脚本文件。
```shell
> seldom -h2c demo.har
2021-06-14 18:05:50 [INFO] Start to generate testcase.
2021-06-14 18:05:50 [INFO] created file: ...\demo.py
```
`demo.py` 文件。
```python
import seldom
class TestRequest(seldom.TestCase):
def start(self):
self.url = "http://httpbin.org/post"
def test_case(self):
headers = {"User-Agent": "python-requests/2.25.0", "Accept-Encoding": "gzip, deflate",
"Accept": "application/json", "Connection": "keep-alive", "Host": "httpbin.org",
"Content-Length": "36", "Origin": "http://httpbin.org", "Content-Type": "application/json",
"Cookie": "lang=zh"}
cookies = {"lang": "zh"}
self.post(self.url, json={"key1": "value1", "key2": "value2"}, headers=headers, cookies=cookies)
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main()
```
### swagger to case
> seldom 3.6 版本支持。
seldom 提供了`swagger` 转 `case` 的命令。 使用 `seldom -s2c` 命令。
```shell
> seldom -s2c swagger.json
2024-03-04 00:02:22 | INFO | core.py | Start to generate testcase.
2024-03-04 00:02:22 | INFO | core.py | created file: ...\swagger.py
```
将swagger文档转为 seldom 自动化测试用例。
```python
import seldom
class TestRequest(seldom.TestCase):
def test_pet_petId_uploadImage_api_post(self):
url = f"https://petstore.swagger.io/pet/{petId}/uploadImage"
params = {}
headers = {}
headers["Content-Type"] = "multipart/form-data"
data = {"additionalMetadata": additionalMetadata, "file": file}
r = self.post(url, headers=headers, params=params, data=data)
print(r.status_code)
def test_pet_api_post(self):
url = f"https://petstore.swagger.io/pet"
params = {}
headers = {}
headers["Content-Type"] = "application/json"
data = {}
r = self.post(url, headers=headers, params=params, data=data)
print(r.status_code)
```
需要注意的是,转换的seldom自动化测试用例有一些`变量`,需要用户根据实际情况进行定义。
### 请求转 cURL
seldom 支持将请求转成`cCURL`命令, 你可以方便的通过`cURL`命令执行,或者导入到其他接口工具,例如,postman 支持`cURL`命令导入。
```python
# test_http.py
import seldom
class TestRequest(seldom.TestCase):
"""
http api test demo
doc: https://requests.readthedocs.io/en/master/
"""
def test_get_curl(self):
"""
test get curl
"""
self.get('http://httpbin.org/get', params={'key': 'value'})
curl = self.curl()
print(curl)
self.post('http://httpbin.org/post', data={'key': 'value'})
curl = self.curl()
print(curl)
# or
r = self.delete('http://httpbin.org/delete', params={'key': 'value'})
curl = self.curl(r.request)
print(curl)
r = self.put('http://httpbin.org/put', json={'key': 'value'}, headers={"token": "123"})
curl = self.curl(r.request)
print(curl)
if __name__ == '__main__':
seldom.main(debug=True)
```
* 日志结果
```shell
> python test_http.py
...
curl -X GET 'Content-Type: application/json' -H 'token: 123' -d '{"key": "value"}' http://httpbin.org/get
curl -X POST 'Content-Type: application/x-www-form-urlencoded' -H -d key=value http://httpbin.org/post
curl -X DELETE 'http://httpbin.org/delete?key=value'
curl -X PUT -H 'Content-Type: application/json' -H 'token: 123' -d '{"key": "value"}' http://httpbin.org/put
```
### 接口数据依赖
在场景测试中,我们需要利用上一个接口的数据,调用下一个接口。
* 简单的接口依赖
```python
import seldom
class TestRespData(seldom.TestCase):
def test_data_dependency(self):
"""
Test for interface data dependencies
"""
headers = {"X-Account-Fullname": "bugmaster"}
self.get("/get", headers=headers)
self.assertStatusCode(200)
username = self.response["headers"]["X-Account-Fullname"]
self.post("/post", data={'username': username})
self.assertStatusCode(200)
```
seldom提供了`self.response`用于记录上个接口返回的结果,直接拿来用即可。
* 封装接口依赖
1. 创建公共模块
```python
# common.py
from seldom.request import check_response
from seldom.request import HttpRequest
class Common(HttpRequest):
@check_response(
describe="获取登录用户名",
status_code=200,
ret="headers.Account",
check={"headers.Host": "httpbin.org"},
debug=True
)
def get_login_user(self):
"""
调用接口获得用户名
"""
headers = {"Account": "bugmaster"}
r = self.get("http://httpbin.org/get", headers=headers)
return r
if __name__ == '__main__':
c = Common()
c.get_login_user()
```
* 运行日志
```shell
2023-02-14 23:51:48 request.py | DEBUG | Execute get_login_user - args: (<__main__.Common object at 0x0000023263075100>,)
2023-02-14 23:51:48 request.py | DEBUG | Execute get_login_user - kwargs: {}
2023-02-14 23:51:48 request.py | INFO | -------------- Request -----------------[🚀]
2023-02-14 23:51:48 request.py | INFO | [method]: GET [url]: http://httpbin.org/get
2023-02-14 23:51:48 request.py | DEBUG | [headers]:
{
"Account": "bugmaster"
}
2023-02-14 23:51:49 request.py | INFO | -------------- Response ----------------[🛬️]
2023-02-14 23:51:49 request.py | INFO | successful with status 200
2023-02-14 23:51:49 request.py | DEBUG | [type]: json [time]: 0.601097
2023-02-14 23:51:49 request.py | DEBUG | [response]:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Account": "bugmaster",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-63ebae14-1e629b132c21f68e23ffeb33"
},
"origin": "173.248.248.88",
"url": "http://httpbin.org/get"
}
2023-02-14 23:51:49 request.py | DEBUG | Execute get_login_user - response:
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Account': 'bugmaster', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.28.1', 'X-Amzn-Trace-Id': 'Root=1-63ebae14-1e629b132c21f68e23ffeb33'}, 'origin': '173.248.248.88', 'url': 'http://httpbin.org/get'}
2023-02-14 23:51:49 request.py | INFO | Execute get_login_user - 获取登录用户名 success!
```
`@check_response` 专门用于处理封装的方法。
__参数说明:__
* `describe`: 封装方法描述。
* `status_code`: 判断接口返回的 HTTP 状态码,默认`200`。
* `ret`: 提取接口返回的字段,参考`jmespath` 提取规则。
* `check`: 检查接口返回的字段。参考`jmespath` 提取规则。
* `debug`: 开启`debug`,打印更多信息。
2. 引用公共模块
```python
import seldom
from common import Common
class TestRequest(seldom.TestCase):
def start(self):
self.c = Common()
def test_case(self):
# 调用 get_login_user() 获取
user = self.c.get_login_user()
self.post("http://httpbin.org/post", data={'username': user})
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(debug=True)
```
### Session使用
在实际测试过程中,大部分接口需要登录,`Session` 是一种非常简单记录登录状态的方式。
```python
import seldom
class TestCase(seldom.TestCase):
def start(self):
self.s = self.Session()
self.s.get('/cookies/set/sessioncookie/123456789')
def test_get_cookie1(self):
self.s.get('/cookies')
def test_get_cookie2(self):
self.s.get('/cookies')
if __name__ == '__main__':
seldom.main(debug=True, base_url="https://httpbin.org")
```
用法非常简单,你只需要在每个接口之前调用一次`登录`, `self.s`对象就记录下了登录状态,通过`self.s` 再去调用其他接口就不需要登录。
### 提取接口返回数据
当接口返回的数据比较复杂时,我们需要有更方便方式去提取数据,seldom提供 `jmespath`、`jsonpath` 来简化数据提取。
* 接口返回数据
```json
{
"args": {
"hobby": [
"basketball",
"swim"
],
"name": "tom"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.25.0",
"X-Amzn-Trace-Id": "Root=1-62851614-1ca9fdb276238c60406c118f"
},
"origin": "113.87.15.99",
"url": "http://httpbin.org/get?name=tom&hobby=basketball&hobby=swim"
}
```
* 常规提取
```python
import seldom
class TestAPI(seldom.TestCase):
def test_extract_responses(self):
"""
提取 response 数据
"""
payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}
self.get("http://httpbin.org/get", params=payload)
# response
response1 = self.response["args"]["name"]
response2 = self.response["args"]["hobby"]
response3 = self.response["args"]["hobby"][0]
print(f"response1 --> {response1}")
print(f"response2 --> {response2}")
print(f"response3 --> {response3}")
# jmespath
jmespath1 = self.jmespath("args.name")
jmespath2 = self.jmespath("args.hobby")
jmespath3 = self.jmespath("args.hobby[0]")
jmespath4 = self.jmespath("hobby[0]", response=self.response["args"])
print(f"\njmespath1 --> {jmespath1}")
print(f"jmespath2 --> {jmespath2}")
print(f"jmespath3 --> {jmespath3}")
print(f"jmespath4 --> {jmespath4}")
# jsonpath
jsonpath1 = self.jsonpath("$..name")
jsonpath2 = self.jsonpath("$..hobby")
jsonpath3 = self.jsonpath("$..hobby[0]")
jsonpath4 = self.jsonpath("$..hobby[0]", index=0)
jsonpath5 = self.jsonpath("$..hobby[0]", index=0, response=self.response["args"])
print(f"\njsonpath1 --> {jsonpath1}")
print(f"jsonpath2 --> {jsonpath2}")
print(f"jsonpath3 --> {jsonpath3}")
print(f"jsonpath4 --> {jsonpath4}")
print(f"jsonpath5 --> {jsonpath5}")
...
```
说明:
* `response`: 保存接口返回的数据,可以直接以,字典列表的方式提取。
* `jmespath()`: 根据 JMESPath 语法规则,默认提取接口返回的数据,也可指定`resposne`数据提取。
* `jsonpath()`: 根据 JsonPath 语法规则,默认提取接口返回的数据, `index`指定下标,也可指定`resposne`数据提取。
运行结果:
```shell
2022-05-19 00:57:08 log.py | DEBUG | [response]:
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-62852563-2fe77d4b1ce544696af60f10'}, 'origin': '113.87.15.99', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
response1 --> tom
response2 --> ['basketball', 'swim']
response3 --> basketball
jmespath1 --> tom
jmespath2 --> ['basketball', 'swim']
jmespath3 --> basketball
jmespath4 --> basketball
jsonpath1 --> ['tom']
jsonpath2 --> [['basketball', 'swim']]
jsonpath3 --> ['basketball']
jsonpath4 --> basketball
jsonpath5 --> basketball
```
运行结果
```shell
...
2022-04-10 21:05:17.683 | DEBUG | seldom.logging.log:debug:34 - [response]:
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-6252d60c-551433d744b6869e5d1944d7'}, 'origin': '113.87.12.14', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
2022-04-10 21:05:17.686 | DEBUG | seldom.logging.log:debug:34 - [jresponse]:
['basketball']
2022-04-10 21:05:17.689 | DEBUG | seldom.logging.log:debug:34 - [jresponse]:
['18']
```
### genson
通过 `assertSchema()` 断言时需要写JSON
Schema,但是这个写起来需要学习成本,seldom集成了[GenSON](https://github.com/wolverdude/GenSON) ,可以帮你自动生成。
* 例子
```python
import seldom
from seldom.utils import genson
class TestAPI(seldom.TestCase):
def test_assert_schema(self):
payload = {"hobby": ["basketball", "swim"], "name": "tom", "age": "18"}
self.get("/get", params=payload)
print("response \n", self.response)
schema = genson(self.response)
print("json Schema \n", schema)
self.assertSchema(schema)
```
* 运行日志
```shell
...
response
{'args': {'age': '18', 'hobby': ['basketball', 'swim'], 'name': 'tom'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.25.0', 'X-Amzn-Trace-Id': 'Root=1-626574d0-4c04bb7e76a53e8042c9d856'}, 'origin': '173.248.248.88', 'url': 'http://httpbin.org/get?hobby=basketball&hobby=swim&name=tom&age=18'}
json Schema
{'$schema': 'http://json-schema.org/schema#', 'type': 'object', 'properties': {'args': {'type': 'object', 'properties': {'age': {'type': 'string'}, 'hobby': {'type': 'array', 'items': {'type': 'string'}}, 'name': {'type': 'string'}}, 'required': ['age', 'hobby', 'name']}, 'headers': {'type': 'object', 'properties': {'Accept': {'type': 'string'}, 'Accept-Encoding': {'type': 'string'}, 'Host': {'type': 'string'}, 'User-Agent': {'type': 'string'}, 'X-Amzn-Trace-Id': {'type': 'string'}}, 'required': ['Accept', 'Accept-Encoding', 'Host', 'User-Agent', 'X-Amzn-Trace-Id']}, 'origin': {'type': 'string'}, 'url': {'type': 'string'}}, 'required': ['args', 'headers', 'origin', 'url']}
```
### mock URL
> seldom 3.2.3 支持
seldom 运行允许通过`confrun.py`文件中`mock_url()` 配置mock URL映射。
* `confrun.py` 配置要映射的mock URL。
```python
def mock_url():
"""
mock url
:return:
"""
config = {
"http://httpbin.org/get": "http://127.0.0.1:8000/api/data",
}
return config
```
* test_api.py
```python
import seldom
class TestRequest(seldom.TestCase):
"""
http api test demo
"""
def test_get_method(self):
payload = {'key1': 'value1', 'key2': 'value2'}
self.get("/get", params=payload)
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(base_url="http://httpbin.org")
```
* 运行
```shell
> python test_api.py
2023-07-30 14:47:08 | INFO | request.py | -------------- Request -----------------[🚀]
2023-07-30 14:47:08 | INFO | request.py | [method]: GET [url]: http://httpbin.org/get
2023-07-30 14:47:08 | DEBUG | request.py | [params]:
{
"key1": "value1",
"key2": "value2"
}
2023-07-30 14:47:08 | DEBUG | request.py | mock url: http://127.0.0.1:8000/api/data
2023-07-30 14:47:08 | INFO | request.py | -------------- Response ----------------[🛬️]
2023-07-30 14:47:08 | INFO | request.py | successful with status 200
2023-07-30 14:47:08 | DEBUG | request.py | [type]: json [time]: 0.002738
2023-07-30 14:47:08 | DEBUG | request.py | [response]:
[{'item_name': 'apple'}, {'item_name': 'banana'}, {'item_name': 'orange'}, {'item_name': 'watermelon'}, {'item_name': 'grape'}]
2023-07-30 14:47:08 | INFO | case.py | 👀 assertStatusCode -> 200.
```
通过日志可以看到 `http://httpbin.org/get` 替换成为 `http://127.0.0.1:8000/api/data` 执行。 当你不想mock的时候只需要修改
mock_url() 即可,对于用例来说无影响。
### 配置`proxies`代理
> seldom 3.11.0
__单个方法设置代理__
seldom 支持在每个请求方法中设置代理。
```shell
import seldom
class TestHttpAssert(seldom.TestCase):
def test_req_proxy(self):
"""
test request proxy
"""
payload = {"name": "tom", "hobby": ["basketball", "swim"]}
proxies = {
"https": "http://localhost:1080",
"http": "http://localhost:1080",
}
self.get("/get", params=payload, proxies=proxies)
```
__全局设置代理__
当我们要所有用例都使用代理时,每个方法都单独设置就很麻烦了,可以使用`confrun.py`全局设置。
* 目录结构
```shell
├───reports
├───test_data
├───test_dir
│ ├───...
├───confrun.py # 配置文件
└───run.py
```
* `confrun.py` 配置要映射的mock URL。
```python
def proxies():
"""
http proxies
"""
proxies_conf = {
"https": "http://localhost:1080",
"http": "http://localhost:1080",
}
return proxies_conf
```
通过`run.py`文件全局运行测试,这里的代理配置将作用于所有请求方法。
### 保存响应结果
> seldom > 3.13
有时候接口的response非常的长,终端显示不完整,那我们就可以使用`save_response()`将结果保存到文件中。
```python
import seldom
class TestSaveResp(seldom.TestCase):
def test_save_response(self):
"""将response保存到文件中"""
resp = self.get("/get")
self.save_response(resp)
if __name__ == '__main__':
seldom.main(base_url="https://httpbin.org")
```
### 保存响应结果
> seldom > 3.13
当我们本地使用了host切换,需要知道当前的请求是否指向了host地址,可以使用`ip_address()`方法。
```python
import seldom
class TestReqIP(seldom.TestCase):
def test_get_ip_address(self):
"""检查当前请求的IP地址"""
self.get("/get")
self.ip_address()
if __name__ == '__main__':
seldom.main(base_url="https://httpbin.org")
```
### @retry装饰器
`@retry()` 装饰器用于用法失败充实,例如封装的登录方法,允许API调用失败后再次尝试。
示例如下:
```python
from seldom.request import HttpRequest
from seldom.request import check_response, retry
class LoginAPIObject(HttpRequest):
@retry(times=2, wait=3)
@check_response(ret="form.token")
def user_login(self, username: str, password: str) -> str:
"""
模拟:登录API
"""
params = {"username": username, "token": password}
r = self.post("/error", json=params)
return r
if __name__ == '__main__':
login = LoginAPIObject()
login.user_login("tom", "abc123")
```
* `@retry()`装饰器,`times`参数指定重试次数,默认`3`次,`wait`参数指定重试间隔,默认`1s`。
* `@retry()`装饰器可以单独使用,也可以和 `@check_response()`装饰器一起使用,如果一起使用的话,需要在上方。
运行结果:
```shell
2024-03-04 22:36:09 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:09 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:09 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
2024-03-04 22:36:09 | WARNING | request.py | Attempt to execute <user_login> failed with error: 'Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?'. Attempting retry number 1...
2024-03-04 22:36:12 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:12 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:12 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
2024-03-04 22:36:12 | WARNING | request.py | Attempt to execute <user_login> failed with error: 'Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?'. Attempting retry number 2...
2024-03-04 22:36:15 | INFO | request.py | -------------- Request -----------------[🚀]
2024-03-04 22:36:15 | INFO | request.py | [method]: POST [url]: /error
2024-03-04 22:36:15 | DEBUG | request.py | [json]:
{
"username": "tom",
"token": "abc123"
}
Traceback (most recent call last):
File "D:\github\seldom\api\auth_object.py", line 20, in <module>
login.user_login("tom", "abc123")
....
File "C:\Users\fnngj\.virtualenvs\seldom-wKum2rzm\Lib\site-packages\requests\models.py", line 439, in prepare_url
raise MissingSchema(
requests.exceptions.MissingSchema: Invalid URL '/error': No scheme supplied. Perhaps you meant https:///error?
```
从运行结果可以看到,调用接口重试了2次,如果仍然错误,抛出异常。
## 加密工具
> seldom > 3.11.0
在进行接口测试的时候,经常设计参数的加密,例如:`MD5`、`AES`等。Seldom 框架提供完整的加密解密功能,支持以下功能:
* 哈希算法
* MD5
* SHA1/SHA224/SHA256/SHA384/SHA512
* HMAC
* 对称加密
* AES (CBC/ECB/CFB/OFB/CTR)
* DES
* 3DES
* 非对称加密
* RSA
* 编码转换
* Base16/Base32/Base64/Base85
* URL编码
* HTML编码
__示例__
```python
import unittest
# 导入待测试的模块
from seldom.utils.encrypt import (
CipherMode,
HashUtil,
AESUtil,
EncodeUtil,
)
class TestHashUtil(unittest.TestCase):
"""测试 HashUtil 类"""
def test_md5(self):
text = "hello world"
expected = "5eb63bbbe01eeed093cb22bb8f5acdc3"
self.assertEqual(HashUtil.md5(text), expected)
def test_sha256(self):
text = "hello world"
expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
self.assertEqual(HashUtil.sha256(text), expected)
class TestAESUtil(unittest.TestCase):
"""测试 AESUtil 类"""
def test_encrypt_decrypt_cbc(self):
key = "mysecretkey"
text = "hello world"
encrypted = AESUtil.encrypt(key, text, mode=CipherMode.CBC)
decrypted = AESUtil.decrypt(key, encrypted, mode=CipherMode.CBC)
self.assertEqual(decrypted, text)
class TestEncodeUtil(unittest.TestCase):
"""测试 EncodeUtil 类"""
def test_base64_encode_decode(self):
text = "hello world"
encoded = EncodeUtil.base64_encode(text)
decoded = EncodeUtil.base64_decode(encoded)
self.assertEqual(decoded, text)
def test_url_encode_decode(self):
text = "hello world"
encoded = EncodeUtil.url_encode(text)
decoded = EncodeUtil.url_decode(encoded)
self.assertEqual(decoded, text)
def test_html_encode_decode(self):
text = "<html>hello world</html>"
encoded = EncodeUtil.html_encode(text)
decoded = EncodeUtil.html_decode(encoded)
self.assertEqual(decoded, text)
if __name__ == '__main__':
unittest.main()
```
同时`示例`看到,我们可以非常低成本的使用各种加解密算法。
__运行结果__
```shell
> python .\test_encrypt.py
2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [encrypt] method, generated data: jUTwE9UV8c/00d9Kl9UOhdTOoOwWYSVOJ7io72MtWeE=
2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [decrypt] method, generated data: hello world
.2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [base64_encode] method, generated data: aGVsbG8gd29ybGQ=
2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [base64_decode] method, generated data: hello world
.2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [html_encode] method, generated data: <html>hello world</html>
2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [html_decode] method, generated data: <html>hello world</html>
.2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [url_encode] method, generated data: hello%20world
2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [url_decode] method, generated data: hello world
.2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [md5] method, generated data: 5eb63bbbe01eeed093cb22bb8f5acdc3
.2025-01-07 18:20:12 | INFO | encrypt.py | MainThread | ✅ [sha256] method, generated data: b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9
.
----------------------------------------------------------------------
Ran 6 tests in 0.005s
OK
```
================================================
FILE: docs/vpdocs/api-testing/start.md
================================================
# HTTP测试
## 优势
seldom 非常适合个人接口自动化项目,它有以下优势。
* 可以写更少的代码
* 提供详细的运行日志
* 提供专门为接口设计的断言
* 强大的数据驱动
* 自动生成HTML/XML测试报告
* 支持生成随机数据
* 支持`har`/`swagger`文件转case
* 支持数据库操作
这些是seldom支持的功能,我们只需要集成HTTP接口库,并提供强大的断言即可。`seldom 2.0` 加入了HTTP接口自动化测试支持。
Seldom 完全兼容 [Requests](https://docs.python-requests.org/en/master/) API 如下:
| seldom | requests |
|----------------|--------------------|
| self.get() | requests.get() |
| self.post() | requests.post() |
| self.put() | requests.put() |
| self.delete() | requests.delete() |
| self.patch() | requests.patch() |
| self.session() | requests.session() |
## Seldom VS Request+unittest
* unittest + requests 接口自动化示例:
```python
import unittest
import requests
class TestAPI(unittest.TestCase):
def test_get_method(self):
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.get("http://httpbin.org/get", params=payload)
self.assertEqual(r.status_code, 200)
if __name__ == '__main__':
unittest.main()
```
* seldom 接口自动化测试示例:
```python
# test_req.py
import seldom
class TestAPI(seldom.TestCase):
def test_get_method(self):
payload = {'key1': 'value1', 'key2': 'value2'}
self.get("http://httpbin.org/get", params=payload)
self.assertStatusCode(200)
if __name__ == '__main__':
seldom.main(debug=True)
```
主要简化点在,接口的返回数据的处理。当然,seldom真正的优势在断言、日志和报告。
* 运行日志
打开debug模式`seldom.run(debug=True)` 运行上面的用例。
```shell
> python test_req.py
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v3.x.x
-----------------------------------------
@itest.info
test_get_method (test_req.TestAPI) ... 2023-02-14 23:37:07 request.py | INFO |
-------------- Request -----------------[🚀]
2023-02-14 23:37:07 request.py | INFO | [method]: GET [url]: http://httpbin.org/get
2023-02-14 23:37:07 request.py | DEBUG | [params]:
{
"key1": "value1",
"key2": "value2"
}
2023-02-14 23:37:08 request.py | INFO | -------------- Response ----------------[🛬️]
2023-02-14 23:37:08 request.py | INFO | successful with status 200
2023-02-14 23:37:08 request.py | DEBUG | [type]: json [time]: 0.785683
2023-02-14 23:37:08 request.py | DEBUG | [response]:
{
"args": {
"key1": "value1",
"key2": "value2"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-63ebaaa4-325e25be64b104e770c25f8f"
},
"origin": "173.248.248.88",
"url": "http://httpbin.org/get?key1=value1&key2=value2"
}
2023-02-14 23:37:08 case.py | INFO | 👀 assertStatusCode -> 200.
ok
----------------------------------------------------------------------
Ran 1 test in 0.795s
OK
2023-02-14 23:37:08 runner.py | SUCCESS | A run the test in debug mode without generating HTML report!
```
通过日志/报告都可以看到详细的HTTP接口调用信息。
================================================
FILE: docs/vpdocs/api-testing/webscocket.md
================================================
# WebSocket
> seldom > 3.6.0 支持该功能
有些时间我们需要通过`WebSocket`实现长连接,很高兴的告诉告诉你seldom支持`WebSocket`测试了。
### WebSocket 生命周期
WebSocket 生命周期中包含几个关键的事件,这些事件允许开发人员在连接的不同阶段执行代码。以下是WebSocket API中定义的主要事件:
* `open`: 当WebSocket连接成功建立时触发。这个事件表明客户端与服务器之间的连接已经打开,可以开始数据传输。
* `message`: 当客户端接收到服务器发送的消息时触发。这个事件用于处理从服务器接收到的所有消息。
* `error`: 当发生错误,导致WebSocket连接关闭之前或连接无法成功建立时触发。这个事件可以用来处理和响应WebSocket过程中出现的任何异常或错误情况。
* `close`: 当连接被关闭时触发,无论是客户端还是服务器端主动关闭连接,或是因为某种原因连接被迫关闭。这个事件表明WebSocket连接已经彻底关闭,可以进行清理和后续处理。
### seldom测试WebSocket
在seldom中测试WebSocket非常简单。
* 首先,需要一个WebSocket服务。
通过`aiohttp`实现`websocket_server.py`。
```shell
# websocket_server.py
from aiohttp import web
import aiohttp
async def websocket_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
print("message", msg.data)
if msg.data == 'close':
await ws.close()
else:
await ws.send_str(f"Message text was: {msg.data}")
elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' %
ws.exception())
print('websocket connection closed')
return ws
app = web.Application()
app.router.add_get('/ws', websocket_handler)
web.run_app(app, port=8765)
```
* 然后,通过seldom编写WebSocket测试用例。
```shell
import seldom
from seldom.logging import log
from seldom.websocket_client import WebSocketClient
class WebSocketTest(seldom.TestCase):
def start(self):
# 创建WebSocket客户端线程
self.client = WebSocketClient("ws://0.0.0.0:8765/ws")
self.client.start()
# 等待客户端连接建立
self.sleep(1) # 这里假设服务器可以在1秒内响应连接
def tearDown(self):
# 发送关闭消息
self.client.send_message("close")
# 停止WebSocket客户端线程
self.client.stop()
self.client.join()
def test_send_and_receive_message(self):
# 发送消息
self.client.send_message("Hello, WebSocket!")
self.client.join(1) # 等待接收消息
self.client.send_message("How are you?")
self.client.join(1) # 等待接收消息
# 验证是否收到消息
log.info(self.client.received_messages)
self.assertEqual(len(self.client.received_messages), 2)
self.assertIn("Hello, WebSocket!", self.client.received_messages[0])
self.assertIn("How are you?", self.client.received_messages[1])
if __name__ == '__main__':
seldom.main(debug=True)
```
* 运行日志
```shell
> python test_websocket.py
test_send_and_receive_message (test_websocket.WebSocketTest.test_send_and_receive_message) ...
2024-04-05 23:36:33 | INFO | case.py | 💤️ sleep: 1s.
2024-04-05 23:36:33 | INFO | websocket_client.py | WebSocket connection opened.
2024-04-05 23:36:36 | INFO | test_websocket.py | ['Message text was: Hello, WebSocket!', 'Message text was: How are you?']
ok
----------------------------------------------------------------------
Ran 1 test in 3.006s
OK
2024-04-05 23:36:36 | SUCCESS | runner.py | A run the test in debug mode without generating HTML report!
```
================================================
FILE: docs/vpdocs/app-testing/adb_lib.md
================================================
# ADB 操作
App(Android)测试必然需要用到adb命令, seldom根据需要封装了几个常用的操作。
* 获取设备信息
```python
from seldom.utils.adbutils import ADBUtils
adb = ADBUtils()
devices = adb.refresh_devices()
print("当前连接设备:", devices)
# 设置默认设备 - 多设备的情况下,后续操作需要设置设备ID
if devices:
adb.set_default_device(devices[0][0])
```
打印信息:
```shell
当前连接设备: [('MDX0220413011925', 'ELS-AN00')]
```
* 获取当前启动的app信息
```shell
from seldom.utils.adbutils import ADBUtils
adb = ADBUtils()
app_info = adb.get_app_info()
for info in app_info:
print(info['package'], info["activity"])
```
打印信息
```shell
com.huawei.android.launcher com.huawei.android.launcher.unihome.UniHomeLauncher
com.hpbr.bosszhipin com.hpbr.bosszhipin.module.main.activity.MainActivity
com.android.mms com.android.mms.ui.ConversationList
com.tencent.mm com.tencent.mm.ui.LauncherUI
com.delivery.aggregator com.delivery.aggregator.activity.QYMainActivity
com.huawei.browser com.huawei.browser.BrowserMainActivity
com.huawei.android.launcher .unihome.UniHomeLauncher
com.hpbr.bosszhipin .module.main.activity.MainActivity
com.android.mms .ui.ConversationList
com.tencent.mm .ui.LauncherUI
com.delivery.aggregator .activity.QYMainActivity
com.huawei.browser .BrowserMainActivity
```
* 启动&关闭app
```python
import time
from seldom.utils.adbutils import ADBUtils
adb = ADBUtils()
package = "com.microsoft.bing"
if adb.launch_app(package):
print(f"成功启动 {package}")
time.sleep(5)
if adb.close_app(package):
print(f"成功关闭 {package}")
```
打印信息:
```shell
成功启动 com.microsoft.bing
成功关闭 com.microsoft.bing
```
================================================
FILE: docs/vpdocs/app-testing/appium_lab.md
================================================
# appium API
appium API继承 selenium API,所以,操作方法是通用的。在seldom 中,请参考web UI 中的seldom API。
## appium 定位
* 支持定位类型
seldom 支持定位如下,包括selenium/appium。
| 类型 | 定位 | **kwargs |
|-----------------|----------------------|-----------------------------|
| selenium/appium | id | id_="id" |
| selenium | mame | name="name" |
| selenium/appium | class | class_name="class" |
| selenium | tag | tag="input" |
| selenium | link_text | link_text="文字链接" |
| selenium | partial_link_text | partial_link_text="文字链" |
| selenium/appium | xpath | xpath="//*[@id='11']" |
| selenium | css | cass="input#id" |
| appium | ios_uiautomation | ios_uiautomation = "xx" |
| appium | ios_predicate | ios_predicate = "xx" |
| appium | ios_class_chain | ios_class_chain = "xx" |
| appium | android_uiautomator | android_uiautomator = "xx" |
| appium | android_viewtag | android_viewtag = "xx" |
| appium | android_data_matcher | android_data_matcher = "xx" |
| appium | android_view_matcher | android_view_matcher = "xx" |
| appium | windows_uiautomation | windows_uiautomation = "xx" |
| appium | accessibility_id | accessibility_id = "xx" |
| appium | image | image = "xx" |
| appium | custom | custom = "xx" |
* 定位用法
```python
import seldom
from seldom.appium_lab.android import UiAutomator2Options
class TestBBS(seldom.TestCase):
def test_bbs(self):
"""定位方法用法"""
self.click(id_="com.meizu.flyme.flymebbs:id/nw")
self.sleep(2)
self.type(android_uiautomator='new UiSelector().resourceId("com.meizu.flyme.flymebbs:id/nw")', text="flyme")
...
if __name__ == '__main__':
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, debug=True)
```
## appium lab
`appium_lab` 封装了常用App操作
* 基本用法
```python
import seldom
from seldom.appium_lab import AppiumLab
class TestBBS(seldom.TestCase):
def start(self):
# 导入 AppiumLab
self.appium_lab = AppiumLab(self.driver)
def test_bbs(self):
# 点击输入框
self.click(id_="com.meizu.flyme.flymebbs:id/nw")
self.sleep(2)
# 判断当前虚拟键盘是否显示
keyboard = self.appium_lab.is_keyboard_shown()
print(keyboard)
# 收起当前键盘
self.appium_lab.hide_keyboard()
self.sleep(3)
if __name__ == '__main__':
...
```
* 启动appium server
```python
from seldom.appium_lab.appium_service import AppiumService
if __name__ == '__main__':
# 启动 Appium Server
app_ser = AppiumService(
addr="127.0.0.1",
port="4723",
use_plugins="images",
args=["--allow-cors", "--tmp", "C:\Windows\Temp"])
app_ser.start_service()
```
参数说明:
* `addr`: appium server 地址, 默认: `127.0.0.1`
* `port`: appium server 端口, 默认:`4723`
* `log`: 设置 appium server 日志, 默认:`appium_server_1734493548.log`
* `use_plugins`: 设置使用的插件,默认None,不使用。
* `args`: 支持添加更多的参数,例如 `args=["--allow-cors", "--tmp", "C:\Windows\Temp"]`
启动日志:
```shell
2024-12-18 11:52:54 | INFO | appium_service.py | MainThread | 🚀 launch appium server: ['--address', '127.0.0.1', '--port', '4723', '--log', 'D:\\github\\seldomQA\\seldom\\seldom\\appium_lab\\appium_server_1734493974.log', '--use-plugins', 'iamges,ocr', '--allow-cors']
```
`AppiumLab` 类中分以下几类操作:
__Action__
`Action`中提供基本滑动/触摸操作。
```python
from seldom.appium_lab import AppiumLab
appium_lab = AppiumLab()
# 触摸坐标位
appium_lab.tap(x=100, y=200)
# 上划
appium_lab.swipe_up()
# 下划
appium_lab.swipe_down()
# 左划
appium_lab.swipe_left()
# 右划
appium_lab.swipe_right()
# 从x坐标滑动到y坐标
appium_lab.drag_from_to()
```
__Switch__
`Switch`中提供基本上下文切换操作。
```python
from seldom.appium_lab import AppiumLab
appium_lab = AppiumLab()
# 返回当前上下文
context = appium_lab.context()
# 切换原生app
appium_lab.switch_to_app()
# 切换webview
appium_lab.switch_to_web()
# 切换flutter
appium_lab.switch_to_flutter()
# 切换OCR
appium_lab.switch_to_ocr()
```
__Find__
`Find`中提供基于文本的查找,一个元素可以没有ID、name,但一定有显示的文本,这里提供了一组基于文本的查找。
```python
from seldom.appium_lab import AppiumLab
appium_lab = AppiumLab()
# Android
appium_lab.find_view(text="xxx标题").click()
appium_lab.find_view(content_desc="xxx标题").click()
appium_lab.find_edit_text(text="xxx标题").click()
appium_lab.find_button(text="xxx标题").click()
appium_lab.find_button(content_desc="xxx标题").click()
appium_lab.find_text_view(text="xxx标题").click()
appium_lab.find_image_view(text="xxx标题").click()
appium_lab.find_check_box(text="xxx标题").click()
# iOS
appium_lab.find_static_text(text="xxx标题").click()
appium_lab.find_other(text="xxx标题").click()
appium_lab.find_text_field(text="xxx标题").click()
appium_lab.find_image(text="xxx标题").click()
appium_lab.find_ios_button(text="xxx标题").click()
```
__keyboard__
`keyboard`中提供基于键盘的输入和操作。
```python
from seldom.appium_lab import AppiumLab
appium_lab = AppiumLab()
# 基于键盘输入(支持大小写)
appium_lab.key_text("Hello123")
# 手机home键
appium_lab.home()
# 手机返回键
appium_lab.back()
# 判断当前虚拟键盘是否显示(True/False)
ret = appium_lab.is_keyboard_shown()
print(ret)
# 收起虚拟键盘
appium_lab.hide_keyboard()
# 返回当前窗口尺寸
size = appium_lab.size()
```
## appium driver
`AppDriver` 封装了App相关的操作。
```python
import seldom
class TestApp(seldom.TestCase):
"""
Test App
"""
def test_bbs_search(self):
"""
appium api
"""
# app置于后台10s
self.background_app(10)
# 检查设备上是否安装了应用程序
self.is_app_installed("bundle_id")
# 安装app
self.install_app("/app/path/xxx.apk")
# 删除app
self.remove_app("app_id")
# 如果app正在运行,终止运行
self.terminate_app("app_id")
# 如果app未运行,则激活它或者在后台运行
self.activate_app("app_id")
# 查询app 状态
state = self.query_app_state("app_id")
print(state)
# 从指定的设备返回应用程序字符串语言
language, string = self.app_strings()
print(language, string)
# 点击图片
self.click_image("/you/path/xxx.png")
```
> 目前 seldom 集成的 appium API
> 并不完整,在使用过程中如有问题,欢迎提 [issues](https://github.com/SeldomQA/seldom/issues)。
================================================
FILE: docs/vpdocs/app-testing/extensions.md
================================================
# appium 扩展
appium支持扩展,通过扩展来增强appium定位元素的能力。
## appium images-plugin
使用此插件支持的`-image`定位器策略,可以通过Appium指定想要定位的元素的图片文件。
* 安装Appium images-plugin插件。
```shell
> appium plugin install images
```
* 查看已安装的Appium插件。
```shell
> appium plugin list --installed
✔ Listing installed plugins
- images@2.1.8 [installed (npm)]
```
* 启动Appium server时指定使用OCR插件。
```shell
> appium server --address '127.0.0.1' -p 4723 --use-plugins=images
```
* 目录结构
```tree
├───test_appium_images.py
└───phone.jpg
```
* 编写App自动化测试脚本
```python
# test_appium_images.py
import seldom
from seldom.utils.file_extend import file
from seldom.appium_lab.android import UiAutomator2Options
class TestApp(seldom.TestCase):
def test_app_images(self):
self.wait(10)
file_path = file.join(file.dir, "phone.jpg")
self.click_image(file_path)
if __name__ == '__main__':
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options)
```
通过`click_image()` 来点击图片匹配到整个页面上的元素的坐标位。
## Appium OCR plugin
* 安装Appium OCR plugin插件。
```shell
> appium plugin install images--source=npm appium-ocr-plugin
```
* 查看已安装的Appium插件。
```shell
> appium plugin list --installed
✔ Listing installed plugins
- ocr@0.2.0 [installed (npm)]
```
* 启动Appium server时指定使用OCR插件。
```shell
> appium server --address '127.0.0.1' -p 4723 --use-plugins=ocr
```
* 编写App自动测试脚本。
```python
# test_appium_orc.py
import seldom
from seldom.appium_lab.switch import Switch
from seldom.appium_lab.ocr_plugin import OCRCommand
from seldom.appium_lab.android import UiAutomator2Options
class TestApp(seldom.TestCase):
def start(self):
self.switch = Switch(self.driver)
def test_orc_case(self):
ocr = self.driver.ocr_command({})
print(ocr)
self.switch.switch_to_ocr()
self.click(xpath='//words/item[text() = "Flyme"]')
if __name__ == '__main__':
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, extensions=[OCRCommand])
```
根据上面代码示例,打印ocr变量得到一个JSON结构体。
```json
{
"words": [
{
"text": "mEngine",
"confidence": 88.47775268554688,
"bbox": {
"x0": 86,
"y0": 509,
"x1": 308,
"y1": 560
}
},
{
"text": "Flyme",
"confidence": 91.3454818725586,
"bbox": {
"x0": 316,
"y0": 1132,
"x1": 420,
"y1": 1172
}
},
{
"text": "A9",
"confidence": 34.86248779296875,
"bbox": {
"x0": 1017,
"y0": 2565,
"x1": 1078,
"y1": 2595
}
}
],
"lines": [
{
"text": "mEngine BY Ni0vEh 1 Bl\n\n",
"confidence": 21.003677368164062,
"bbox": {
"x0": 86,
"y0": 500,
"x1": 674,
"y1": 560
}
},
{
"text": "Flyme\n\n",
"confidence": 91.3454818725586,
"bbox": {
"x0": 316,
"y0": 1132,
"x1": 420,
"y1": 1172
}
},
{
"text": "A9\n",
"confidence": 34.86248779296875,
"bbox": {
"x0": 1017,
"y0": 2565,
"x1": 1078,
"y1": 2595
}
}
],
"blocks": [
{
"text": "mEngine BY Ni0vEh 1 Bl\n\n",
"confidence": 21.003677368164062,
"bbox": {
"x0": 86,
"y0": 500,
"x1": 674,
"y1": 560
}
},
{
"text": "Flyme\n\n",
"confidence": 91.3454818725586,
"bbox": {
"x0": 316,
"y0": 1132,
"x1": 420,
"y1": 1172
}
},
{
"text": "A9\n",
"confidence": 34.86248779296875,
"bbox": {
"x0": 1017,
"y0": 2565,
"x1": 1078,
"y1": 2595
}
}
]
}
```
JSON结构体说明:
* wrods - Tesseract识别的单个单词的列表。
* lines - Tesseract识别的文本行的列表。
* blocks - Tesseract识别连续文本块的列表。
每项都引用一个OCR对象,它们本身包含3个数据:
- text:识别的文本。
- confidence:Tesseract对于给定文本的OCR处理结果的置信度(范围在0到100之间)。
- bbox:发现文本的边界框,`边界框`标记为x0、x1、y0和y1的值的对象。分别表文本的上下左右坐标位置,其中。这里,x0表示发现文本的左边x坐标,x1表示右边x坐标,y0表示上部y坐标,y1表示下部y坐标。
================================================
FILE: docs/vpdocs/app-testing/page_object.md
================================================
# Page Object
在编写App自动化测试时,推荐使用`page object models`(简称 PO设计模式)。你可以看到seldom并没有完全封装appium的API,我们可以借助
poium 来实现基于元素的定位。
github: https://github.com/SeldomQA/poium
__pip 安装__
```shell
> pip install poium
```
__使用poium__
在seldom中基于poium实现元素的定位和操作。
```python
import seldom
from seldom.appium_lab.android import UiAutomator2Options
from poium import Page, Element, Elements
class BBSPage(Page):
search_input = Element(id_="com.meizu.flyme.flymebbs:id/nw")
search_button = Element(id_="com.meizu.flyme.flymebbs:id/o1")
search_result = Elements(id_="com.meizu.flyme.flymebbs:id/a29")
class TestBBS(seldom.TestCase):
def start(self):
self.bbs_page = BBSPage(self.driver)
def test_bbs(self):
self.sleep(5)
self.bbs_page.search_input.click()
self.bbs_page.search_input.send_keys("flyme")
self.bbs_page.search_button.click()
elems = self.bbs_page.search_result
for title in elems:
self.assertIn("flyme", title.text.lower())
if __name__ == '__main__':
# 定义运行App
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options, debug=True)
```
__定位方法__
poium 支持的定位方法。
```shell
# selenium
css = "xx"
id_ = "xx"
name = "xx"
xpath = "xx"
link_text = "xx"
partial_link_text = "xx"
tag = "xx"
class_name = "xx"
# appium
ios_uiautomation = "xx"
ios_predicate = "xx"
ios_class_chain = "xx"
android_uiautomator = "xx"
android_viewtag = "xx"
android_data_matcher = "xx"
android_view_matcher = "xx"
windows_uiautomation = "xx"
accessibility_id = "xx"
image = "xx"
custom = "xx"
```
__`Element`类参数__
* timeout: 设置超时检查次数,默认为5。
* index: 设置元素索引,当你的定位方式默认匹配到多个元素时,默认返回第1个,即为0.
* describe: 设置元素描述,默认为undefined, 建议为每个元素增加描述。
================================================
FILE: docs/vpdocs/app-testing/start.md
================================================
# app 测试
`seldom 3.0` 基于appium支持APP测试。
appium 官方网站:https://appium.io/
## 环境安装
app 的自动化测试环境相比较 web 要复杂一些,请参考appium官方。
1. 安装node
https://nodejs.org/en/
```shell
> node --version
v16.17.0
```
2. 安装appium
```shell
> npm i --location=global appium # appium 2.x
```
3. 启动appium
```shell
> appium server --address '127.0.0.1' -p 4723
[Appium] Welcome to Appium v2.2.2
[Appium] Non-default server args:
[Appium] {
[Appium] address: '127.0.0.1'
[Appium] }
...
```
4. 移动设备
准备一台设备(Android/iOS手机)通过USB数据线连接电脑。通过以下工具确认手机与电脑是否连接。
* adb
```shell
> adb devices
List of devices attached
UMXDU000000000000 device
```
* taobao-iphone-device
```shell
> tidevice list
List of apple devices attached
00008030-00000000000000 xxx的iPhoneSE
```
## 编写测试
基于seldom编写app自动化测试, 由于appium 继承自selenium,所以,部分API共用。
```python
import seldom
from seldom.appium_lab.android import UiAutomator2Options
class TestBBS(seldom.TestCase):
def test_bbs_search(self):
self.click(id_="com.meizu.flyme.flymebbs:id/nw")
self.type(id_="com.meizu.flyme.flymebbs:id/nw", text="flyme")
self.click(id_="com.meizu.flyme.flymebbs:id/o1")
self.sleep(2)
elems = self.get_elements(id_="com.meizu.flyme.flymebbs:id/a29")
for title in elems:
print(title.text)
self.assertIn("lyme", title.text)
if __name__ == '__main__':
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
seldom.main(app_server="http://127.0.0.1:4723", app_info=options)
```
> 注:上面的测试用例隐含了appium的一些知识点,你需要对appium有足够的了解。
* 运行日志
```shell
python test_app.py
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v3.0.0
-----------------------------------------
@itest.info
XTestRunner Running tests...
----------------------------------------------------------------------
2022-10-03 00:01:30 webdriver.py | INFO | 💤️ sleep: 5s.
2022-10-03 00:01:35 webdriver.py | INFO | ✅ Find 1 element: id=com.meizu.flyme.flymebbs:id/nw -> click.
2022-10-03 00:01:36 webdriver.py | INFO | ✅ Find 1 element: id=com.meizu.flyme.flymebbs:id/nw -> input 'flyme'.
2022-10-03 00:01:37 webdriver.py | INFO | ✅ Find 1 element: id=com.meizu.flyme.flymebbs:id/o1 -> click.
2022-10-03 00:01:37 webdriver.py | INFO | 💤️ sleep: 2s.
2022-10-03 00:01:39 webdriver.py | INFO | ✅ Find 5 element: id=com.meizu.flyme.flymebbs:id/a29 .
flyme的屏幕色彩显示应该是比较差的
魅族17的Flyme9状态栏下拉问题。
flyme9.3连上耳机来电话还是会外放
flyme自带录屏功能吗?
关于Flyme 8.18.0A稳定版
Generating HTML reports...
.12022-10-03 00:01:40 runner.py | SUCCESS | generated html file: file:///D:\github\seldom\reports\2022_10_03_00_01_23_result.html
2022-10-03 00:01:40 runner.py | SUCCESS | generated log file: file:///D:\github\seldom\reports\seldom_log.log
```
================================================
FILE: docs/vpdocs/develop.md
================================================
## ☘️Introduction
基于 vuepress2.0+ 的 **seldom [操作文档](https://seldomqa.github.io/)**
你可以使用 Markdown 书写文档,并通过 VuePress 部署为可预览的页面。
## 📖使用说明
### 1. 安装
1. clone本项目并安装依赖
```bash
git clone https://github.com/SeldomQA/seldom.git
cd docs
yarn install
```
### 2. 开发
正式开发前,可以先阅读 [VuePress官方文档](https://v2.vuepress.vuejs.org/zh/)。
在`docs/vpdocs`文件夹内,修改你想修改的`.md`文档并保存。
然后执行以下命令进行预览或打包
```bash
yarn run dev # 预览
yarn run build # 生成静态页面
```
## 部署
**Github-Pages手动本地部署部署说明:**
本地进入项目中执行`deploy.sh`即可自动部署到github pages。
deploy.sh 的详情如下(**请自行判断启用注释掉的命令**):
```shell
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
# 生成静态文件
npm run build
# 进入生成的文件夹
cd vpdocs/.vuepress/dist
git init
git add -A
git commit -m 'deploy'
# 如果发布到 https://SeldomQA.github.io
git push -f git@github.com:SeldomQA/SeldomQA.github.io.git master
cd -
```
更多部署方式可以参阅 [VuePress文档|部署](https://v1.vuepress.vuejs.org/guide/deploy.html)。
---
Author:[@Yongchin](https://github.com/nickliya)
================================================
FILE: docs/vpdocs/getting-started/advanced.md
================================================
# 高级用法
### fixture
有时自动化测试用例的运行需要一些前置&后置步骤,seldom提供了相应的方法。
seldom重写了unittest的`fixture`,所以,请使用seldom的`fixture`,对应表格。
| unittest | seldom | 说明 |
|--------------------|------------------|-----------------------------|
| setUpClass(cls) | start_class(cls) | 测试类开始执行。 |
| tearDownClass(cls) | end_class(cls) | 测试类结束执行。 |
| setUp(self) | start(self) | 测试方法(用例)开始执行。 |
| tearDown(self) | end(self) | 测试方法(用例)结束执行。 |
| - | start_run() | `confrun.py`文件配置,整个用例开始前运行。 |
| - | end_run() | `confrun.py`文件配置,整个用例结束后运行。 |
__示例1__
针对每条测试类/测试用例的fixture使用示例。
```python
# test_fixture.py
import seldom
class TestCase(seldom.TestCase):
@classmethod
def start_class(cls):
print("测试类开始执行")
@classmethod
def end_class(cls):
print("测试类结束执行")
def start(self):
print("一条测试用例开始")
def end(self):
print("一条测试结果")
def test_case_one(self):
...
def test_case_two(self):
...
if __name__ == '__main__':
seldom.main(debug=True)
```
> 警告:不要把用例的操作步骤写到`start_class/end_class`中! 因为它不属于某条用例的一部分,一旦里面的操作步骤运行失败,会影响用例的执行。
__运行结果__
```shell
> python test_fixture.py
...
测试类开始执行
test_case_one (zzz_case.TestCase.test_case_one) ... 一条测试用例开始
一条测试结果
ok
test_case_two (zzz_case.TestCase.test_case_two) ... 一条测试用例开始
一条测试结果
ok
测试类结束执行
...
```
__示例2__
有时候我们需要整个测试`开始前`或`结束后`完成一些工作,可以通过下面的方式配置。
* 目录结构
```
mypro/
├── test_dir/
│ ├── __init__.py
│ ├── test_sample.py
├── confrun.py
└── run.py
```
* `confrun.py` 配置前后置动作
```python
from seldom.logging import log
from seldom.utils import cache
def start_run():
"""
Test the hook function before running
"""
log.info("start_run")
cache.set({"token": "token123"})
def end_run():
"""
Test the hook function after running
"""
log.info("end_run")
cache.clear("token")
```
> 示例中用于添加和清除 cache, 根据实际需求你可以加上任何动作。
* `run.py` 执行用例
```python
import seldom
if __name__ == '__main__':
seldom.main(path="./test_dir")
```
* 运行结果
```shell
> python run.py
...
2024-12-06 17:55:04 | INFO | confrun.py | MainThread | start_run # confrun.py 所有用例前的动作
2024-12-06 17:55:04 | INFO | cache.py | MainThread | 💾 Set cache data: token = token123
2024-12-06 17:55:04 | INFO | runner.py | MainThread | TestLoader: ./test_dir
XTestRunner Running tests...
----------------------------------------------------------------------
2024-12-06 17:55:04 | INFO | cache.py | MainThread | 💾 Get cache data: token = token123
Generating HTML reports...
.12024-12-06 17:55:04 | SUCCESS | runner.py | MainThread | generated html file: file:///D:\github\seldomQA\seldom\reports\2024_12_06_17_55_03_result.html
2024-12-06 17:55:04 | SUCCESS | runner.py | MainThread | generated log file: file:///D:\github\seldomQA\seldom\reports\seldom_log.log
2024-12-06 17:55:04 | INFO | confrun.py | MainThread | end_run # confrun.py 所有用例后的动作
2024-12-06 17:55:04 | INFO | cache.py | MainThread | 💾 Clear cache data: token
```
### 跳过测试
seldom 提供了跳过用例的装饰用于跳过暂时不执行的用例。
__装饰器__
* `seldom.skip()`:无条件地跳过一个测试。
* `seldom.skip_if()`: 如果条件为真,则跳过测试。
* `seldom.skip_unless()`: 跳过一个测试,除非条件为真。
* `seldom.expected_failure()`: 预期测试用例会失败。
* `self.skipTest()`: 根据条件跳过测试。
__使用方法__
```python
# test_skip.py
import seldom
@seldom.skip(reason="跳过类")
class SkipTest(seldom.TestCase):
def test_case(self):
...
class YouTest(seldom.TestCase):
@seldom.skip(reason="跳过用例")
def test_skip_case(self):
...
def test_if_skip(self):
login = False
if login is False:
self.skipTest(reason="登录失败,跳过后续执行")
if __name__ == '__main__':
seldom.main(debug=True)
```
### 重复执行
当然某一段测试需要重复执行,使用`for`循环是常规的操作,seldom提供了`rerun()` 方法可以更优雅的完成这个工作。
```python
import seldom
from seldom import rerun
class TestCase(seldom.TestCase):
@rerun(100)
def test_search_seldom(self):
self.open("https://www.baidu.com")
self.type_enter(id_="kw", text="seldom")
```
通过`@rerun()` 装饰 `test_searchseldom()` 可以执行 100 次,统计结果仍为1条用例,如果想统计为 100 条用例,请使用`@data()`
装饰器。
### 随机测试数据
测试数据是测试用例的重要部分,有时不能把测试数据写死在测试用例中,比如注册新用户,一旦执行过用例那么测试数据就已经存在了,所以每次执行注册新用户的数据不能是一样的,这就需要随机生成一些测试数据。
seldom 提供了随机获取测试数据的方法。
```python
import seldom
from seldom import testdata
class YouTest(seldom.TestCase):
def test_case(self):
"""a simple test case """
word = testdata.get_word()
print(word)
if __name__ == '__main__':
seldom.main()
```
通过`get_word()` 随机获取一个单词,然后对这个单词进行搜索。
**更多的方法**
```python
from seldom.testdata import *
# 随机一个名字
print("名字:", first_name())
print("名字(男):", first_name(gender="male"))
print("名字(女):", first_name(gender="female"))
print("名字(中文男):", first_name(gender="male", language="zh"))
print("名字(中文女):", first_name(gender="female", language="zh"))
# 随机一个姓
print("姓:", last_name())
print("姓(中文):", last_name(language="zh"))
# 随机一个姓名
print("姓名:", username())
print("姓名(中文):", username(language="zh"))
# 随机一个生日
print("生日:", get_birthday())
print("生日字符串:", get_birthday(as_str=True))
print("生日年龄范围:", get_birthday(start_age=20, stop_age=30))
# 日期
print("日期(当前):", get_date())
print("日期(昨天):", get_date(-1))
print("日期(明天):", get_date(1))
print("当月:", get_month())
print("上个月:", get_month(-1))
print("下个月:", get_month(1))
print("今年:", get_year())
print("去年:", get_year(-1))
print("明年:", get_year(1))
# 数字
print("数字(8位):", get_digits(8))
# 邮箱
print("邮箱:", get_email())
# 浮点数
print("浮点数:", get_float())
print("浮点数范围:", get_float(min_size=1.0, max_size=2.0))
# 随机时间
print("当前时间:", get_now_datetime())
print("当前时间(格式化字符串):", get_now_datetime(strftime=True))
print("未来时间:", get_future_datetime())
print("未来时间(格式化字符串):", get_future_datetime(strftime=True))
print("过去时间:", get_past_datetime())
print("过去时间(格式化字符串):", get_past_datetime(strftime=True))
# 随机数据
print("整型:", get_int())
print("整型32位:", get_int32())
print("整型64位:", get_int64())
print("MD5:", get_md5())
print("UUID:", get_uuid())
print("单词:", get_word())
print("单词组(3个):", get_words(3))
print("手机号:", get_phone())
print("手机号(移动):", get_phone(operator="mobile"))
print("手机号(联通):", get_phone(operator="unicom"))
print("手机号(电信):", get_phone(operator="telecom"))
# 在线时间
print("当前时间戳:", online_timestamp())
print("当前日期时间:", online_now_datetime())
```
* 运行结果
```
名字: Hayden
名字(男): Brantley
名字(女): Julia
名字(中文男): 觅儿
名字(中文女): 若星
姓: Lee
姓(中文): 白
姓名: Genesis
姓名(中文): 廉高义
生日: 2000-03-11
生日字符串: 1994-11-12
生日年龄范围: 1996-01-12
日期(当前): 2022-09-17
日期(昨天): 2022-09-16
日期(明天): 2022-09-18
数字(8位): 48285099
邮箱: melanie@yahoo.com
浮点数: 1.5315717275531858e+308
浮点数范围: 1.6682402084146244
当前时间: 2022-09-17 23:33:22.736031
当前时间(格式化字符串): 2022-09-17 23:33:22
未来时间: 2054-05-02 11:33:47.736031
未来时间(格式化字符串): 2070-08-28 16:38:45
过去时间: 2004-09-03 12:56:23.737031
过去时间(格式化字符串): 2006-12-06 07:58:37
整型: 7831034423589443450
整型32位: 1119927937
整型64位: 3509365234787490389
MD5: d0f6c6abbfe1cfeea60ecfdd1ef2f4b9
UUID: 5fd50475-2723-4a36-a769-1d4c9784223a
单词: habitasse
单词组(3个): уж pede. metus.
手机号: 13171039843
手机号(移动): 15165746029
手机号(联通): 16672812525
手机号(电信): 17345142737
当前时间戳 1695137988672
当前日期时间 2023-09-19 23:39:48
```
### 用例的依赖
> 在 seldom 1.8.0 版本实现了该功能。
在编写用例的时候不推荐你编写依赖的用例,但是,有些时候我们并不能完全消除这些依赖。seldom 增加了用例依赖的方法。
**depend**
`depend` 装饰器用来设置依赖的用例。
```python
import seldom
from seldom import depend
class TestDepend(seldom.TestCase):
def test_001(self):
print("test_001")
@depend("test_001")
def test_002(self):
print("test_002")
@depend("test_002")
def test_003(self):
print("test_003")
if __name__ == '__main__':
seldom.main(debug=True)
```
`test_002` 依赖于 `test_001` , `test_003`又依赖于`test_002`。当被依赖的用例,错误、失败、跳过,那么依赖的用例自动跳过。
**if_depend**
`if_depend` 装饰器不会依赖用例的执行状态,可以自己定义是否要跳过依赖的用例。
```python
import seldom
from seldom import if_depend
class TestIfDepend(seldom.TestCase):
Test001 = True
def test_001(self):
TestIfDepend.Test001 = False # 修改Test001为 False
@if_depend("Test001")
def test_002(self):
...
if __name__ == '__main__':
seldom.main(debug=True)
```
1. 首先,定义变量 `Test001`,默认值为`True`。
2. 在`test_001`用例中,可以根据一些条件来选择是否修改`Test001`的值,如果改为`False`, 那么依赖的用例将被跳过。
3. 在`test_002`用例中,通过`if_depend`装饰器来判断`Test001`的值,如果为为`False`, 那么装饰的用例跳过,否则执行。
**@depend 和 @data()**
`@depend()` 装饰器可以和 `@data()` 装饰器混合使用。
```python
import seldom
from seldom import data, depend
class DataDriverTest(seldom.TestCase):
def test_001(self):
self.assertEqual(1, 2)
@data([
("First", "seldom"),
("Second", "selenium"),
("Third", "unittest"),
])
@depend("test_001") # 依赖 test_001 的结果
def test_002(self, name, keyword):
"""
Used tuple test data
:param name: case desc
:param keyword: case data
"""
print(f"{name} - test data: {keyword}")
if __name__ == '__main__':
seldom.main(debug=True)
```
使用要求:
1. 被依赖的用例不能用 @data() 装饰器,否则就是一组用例了,只能指定单个用例。
2. `@depend()` 要放到 `@data()` 下面使用。
### 用例分类标签
> 在 seldom 2.4.0 版本实现了该功能。
**使用方式**
```python
# test_label.py
import seldom
from seldom import label
class MyTest(seldom.TestCase):
@label("base")
def test_label_base(self):
self.assertEqual(1 + 1, 2)
@label("slow")
def test_label_slow(self):
self.assertEqual(1, 2)
def test_no_label(self):
self.assertEqual(2 + 3, 5)
if __name__ == '__main__':
# seldom.main(debug=True, whitelist=["base"]) # whitelist
seldom.main(debug=True, blacklist=["slow"]) # blacklist
```
如果只运行标签为`base`的用例,设置白名单(whitelist)。
```shell
> python test_label.py
test_label_base (btest_label.MyTest) ... ok
test_label_slow (btest_label.MyTest) ... skipped "label whitelist {'base'}"
test_no_label (btest_label.MyTest) ... skipped "label whitelist {'base'}"
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK (skipped=2)
```
如果只想屏蔽标签为`slow`的用例,设置黑名单(blacklist)。
```shell
> python test_label.py
test_label_base (btest_label.MyTest) ... ok
test_label_slow (btest_label.MyTest) ... skipped "label blacklist {'slow'}"
test_no_label (btest_label.MyTest) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK (skipped=1)
```
### 发送邮件
> 在 seldom 1.2.4 版本实现了该功能。
如果你想将测试完成的报告发送到指定邮箱,那么可以调用发邮件的方法实现。
```python
import seldom
from seldom import SMTP
# ...
if __name__ == '__main__':
report_path = "/you/path/report.html"
seldom.main(report=report_path)
smtp = SMTP(user="send@126.com", password="abc123", host="smtp.126.com", ssl=True)
smtp.sendmail(to="receive@mail.com", subject="Email title", attachments=report_path, delete=False)
```
__SMTP()类__
- `user`: 邮箱用户名。
- `password`: 邮箱密码。
- `host`: 邮箱服务地址。
- `ssl`: `True` 使用 `SMTP_SSL()`,`False` 使用 `SMTP()`,两种方式应对不同的邮箱服务。
__sendmail()方法__
- `subject`: 邮件标题,默认:`Seldom Test Report`。
- `to`: 添加收件人,支持多个收件人: `["aa@mail.com", "bb@mail.com"]`。
- `attachments`: 设置附件,默认发送 HTML 测试报告。
- `delete`: 是否删除报告&日志。(在服务器上运行自动化,每次都会产生一份报告和日志,手动删除比较麻烦。)
> `debug`模式不会生成测试报告, 自动化发邮件不支持`debug` 模式,自然也无法将报告发送到指定邮箱了。
### 发送钉钉
> 在 seldom 2.6.0 版本实现了该功能。
seldom 还提供了发送钉钉的 API。
帮助文档:
https://open.dingtalk.com/document/group/enterprise-created-chatbot
```python
import seldom
from seldom import DingTalk
# ...
if __name__ == '__main__':
seldom.main()
ding = DingTalk(
access_token="690900b5ce6d5d10bb1218b8e64a4e2b55f96a6d116aaf50",
key="xxxx",
app_secret="xxxxx",
at_mobiles=[13700000000, 13800000000],
is_at_all=False,
)
ding.sender()
```
__参数说明:__
- `access_token`: 钉钉机器人的 access_token
- `key`: 如果钉钉机器人安全设置了关键字,则需要传入对应的关键字。
- `app_secret`: 如果钉钉机器人安全设置了签名,则需要传入对应的密钥。
- `at_mobiles`: 发送通知钉钉中要@人的手机号列表,如:[137xxx, 188xxx]。
- `is_at_all`: 是否@所有人,默认为 False, 设为 True 则会@所有人。
### seldom日志
> 在 seldom 2.9.0 版本提供了日志的配置能力。
在项目中你可以使用seldom提供的`log` 打印日志。
* 使用log
```python
from seldom.logging import log
log.trace("this is trace info.")
log.info("this is info.")
log.error("this error info.")
log.debug("this debug info.")
log.success("this success info.")
log.warning("this warning info.")
```
* 运行日志
```shell
2022-04-30 16:31:49 test_log.py | TRACE | this is trace info.
2022-04-30 16:31:49 test_log.py | INFO | this is info.
2022-04-30 16:31:49 test_log.py | ERROR | this error info.
2022-04-30 16:31:49 test_log.py | DEBUG | this debug info.
2022-04-30 16:31:49 test_log.py | SUCCESS | this success info.
2022-04-30 16:31:49 test_log.py | WARNING | this warning info.
```
* 关闭日志颜色
```python
from seldom.logging import log_cfg
from seldom.logging import log
log_cfg.set_level(colorlog=False) # 关闭日志颜色
log.trace("this is trace info.")
# ...
```
* 自定义日志格式
```python
from seldom.logging import log_cfg
from seldom.logging import log
# 定义日志格式
format = "<green>{time:YYYY-MM-DD HH:mm:ss}</> {file} |<level> {level} | {message}</level>"
log_cfg.set_level(format=format)
log.trace("this is trace info.")
```
* 日志级别
```python
from seldom.logging import log_cfg
from seldom.logging import log
# 设置日志级别
log_cfg.set_level(level="DEBUG")
log.trace("this is trace info.")
log.error("this error info.")
```
> log level: TRACE < DEBUG < INFO < SUCCESS < WARNING < ERROR
### 缓存 cache
> 在 seldom 2.10.0 版本实现了该功能。
实际测试过程中,往往需要需要通过cache去记录一些数据,从而减少不必要的操作。例如
登录token,很多条用例都会用到登录token,那么就可以借助缓存来暂存登录token,从而减少重复动作。
* cache
```python
from seldom.utils import cache
# 清除指定缓存
cache.clear()
# 获取指定缓存
token = cache.get("token")
print(f"token: {token}")
# 判断为空写入缓存
if token is None:
cache.set({"token": "123"})
# 设置存在的数据(相当于更新)
cache.set({"token": "456"})
# value复杂格式设置存在的数据
cache.set({"user": [{"name": "tom", "age": 11}]})
# 获取所有缓存
all_token = cache.get()
print(f"all: {all_token}")
# 清除指定缓存
cache.clear("token")
```
> 注:seldom 提供的 `cache` 本质上是通过json文件来临时记录数据,没有失效时间。你需要在适当的位置做清除操作。例如,整个用例开始时清除。
* memery_cache
使用内存的实现的cache 装饰器。
```python
import time
import seldom
from seldom.utils import memory_cache
@memory_cache()
def add(x, y):
print("calculating: %s + %s" % (x, y))
time.sleep(2)
c = x + y
return c
class MyTest(seldom.TestCase):
def test_case(self):
"""test cache 1"""
r = add(1, 2)
self.assertEqual(r, 3)
def test_case2(self):
"""test cache 2"""
r = add(1, 2)
self.assertEqual(r, 3)
def test_case3(self):
"""test cache 3"""
r = add(1, 2)
self.assertEqual(r, 3)
if __name__ == '__main__':
seldom.main(debug=True)
```
* disk_cache
使用磁盘实现的cache 装饰器。
```python
import time
import seldom
from seldom.utils import disk_cache
@disk_cache()
def add(x, y):
print("calculating: %s + %s" % (x, y))
time.sleep(2)
c = x + y
return c
class MyTest(seldom.TestCase):
def test_case(self):
"""test cache 1"""
r = add(1, 2)
self.assertEqual(r, 3)
def test_case2(self):
"""test cache 2"""
r = add(1, 2)
self.assertEqual(r, 3)
def test_case3(self):
"""test cache 3"""
r = add(1, 2)
self.assertEqual(r, 3)
if __name__ == '__main__':
dc = disk_cache()
# 清除所有函数缓存
# dc.clear()
# 清除 `add()` 函数缓存
dc.clear("add")
seldom.main(debug=True)
```
================================================
FILE: docs/vpdocs/getting-started/create_project.md
================================================
# 创建项目
seldom已经安装完成,那么现在已经迫不及待的想体验seldom的使用。
### 自动生成项目
seldom 通过`seldom`命令提供了脚手架,可以快速的帮我们创建自动化测试项目。
1. 查看帮助:
```shell
> seldom --help
Usage: seldom [OPTIONS]
seldom CLI.
Options:
--version Show version.
--project-api TEXT Create an API automation test project.
--project-app TEXT Create an App automation test project.
--project-web TEXT Create an Web automation test project.
-cc, --clear-cache BOOLEAN Clear all caches of seldom.
-p, --path TEXT Run test case file path.
-c, --collect / -nc, --no-collect
Collect project test cases. Need the
`--path`.
-l, --level [data|method] Parse the level of use cases. Need the
--path.
-j, --case-json TEXT Test case files. Need the `--path`.
-e, --env TEXT Set the Seldom run environment `Seldom.env`.
-b, --browser [chrome|firefox|ie|edge|safari]
The browser that runs the Web UI automation
tests. Need the `--path`.
-u, --base-url TEXT The base-url that runs the HTTP automation
tests. Need the `--path`.
-d, --debug / -nd, --no-debug Debug mode. Need the `--path`.
-rr, --rerun INTEGER The number of times a use case failed to run
again. Need the `--path`.
-r, --report TEXT Set the test report for output. Need the
`--path`.
-m, --mod TEXT Run tests modules, classes or even
individual test methods from the command
line.
-ll, --log-level [TRACE|DEBUG|INFO|SUCCESS|WARNING|ERROR]
Set the log level.
-h2c, --har2case TEXT HAR file converts an seldom test case.
-s2c, --swagger2case TEXT Swagger file converts an seldom test case.
--api-excel TEXT Run the api test cases in the excel file.
--help Show this message and exit.
```
2. 创建项目:
```shell
> seldom -P mypro
```
目录结构如下:
```shell
mypro/
├── test_dir/
│ ├── __init__.py
│ ├── test_web_sample.py
│ ├── test_api_sample.py
├── test_data/
│ ├── data.json
├── reports/
└── confrun.py
```
* `test_dir/` 测试用例目录。
* `test_data/` 测试数据文件目录。
* `reports/` 测试报告目录。
* `confrun.py` 运行测试用例配置文件。
3. 克隆项目
如果无法使用`seldom`命令,可以通过git克隆相关项目进行学习。
* seldom-web-testing
```shell
> git clone https://github.com/SeldomQA/seldom-web-testing
```
* seldom-api-testing
```shell
> git clone https://github.com/defnngj/seldom-api-testing
```
### 创建测试用例
根据上面的创建的项目,可以在`test_dir`目录下继续创建测试用例:`test_sample.py`。
```py
import seldom
class YouTest(seldom.TestCase):
def test_case(self):
"""a simple test case """
...
if __name__ == '__main__':
seldom.main()
```
根据自己的需求编写`Web UI`、`App UI`或`HTTP接口`自动化测试。
================================================
FILE: docs/vpdocs/getting-started/data_driver.md
================================================
# 数据驱动
数据驱动是测试框架非常重要的功能之一,它可以有效的节约大量重复的测试代码。seldom针对该功能做强大的支持。
### @class_data() 方法
`class_data()` 装饰测试类,测试类下面的任何方法可以共用 `class_data()` 中定义的变量。
* 用法一
```python
import seldom
from seldom import data_class
@data_class([
{"username": "user_1", "password": "abc123"},
{"username": "user_2", "password": "abc456"},
])
class DDTTest(seldom.TestCase):
def test_data_func(self):
""" data driver case """
print("username->", self.username)
print("password->", self.password)
if __name__ == '__main__':
seldom.main(debug=True)
```
* 用法二
```python
import seldom
from seldom import data_class
@data_class(("username", "password"), [
("user_1", "abc123"),
("user_1", "abc456"),
])
class DDTTest(seldom.TestCase):
def test_data_func(self):
""" data driver case """
print("username->", self.username)
print("password->", self.password)
if __name__ == '__main__':
seldom.main(debug=True)
```
### @data()方法
当测试数据量比较少的情况下,可以通过`@data()`管理测试数据。
**参数化测试用例**
```python
import seldom
from seldom import data
class DataDriverTest(seldom.TestCase):
@data([
("First case", "seldom"),
("Second case", "selenium"),
("Third case", "unittest"),
])
def test_tuple_data(self, name, keyword):
"""
Used tuple test data
:param name: case desc
:param keyword: case data
"""
print(f"test data: {keyword}")
@data([
["First case", "seldom"],
["Second case", "selenium"],
["Third case", "unittest"],
])
def test_list_data(self, name, keyword):
"""
Used list test data
"""
print(f"test data: {keyword}")
@data([
{"scene": 'First case', 'keyword': 'seldom'},
{"scene": 'Second case', 'keyword': 'selenium'},
{"scene": 'Third case', 'keyword': 'unittest'},
])
def test_dict_data(self, scene, keyword):
"""
used dict test data
"""
print(f"case desc: {scene}")
print(f"test data: {keyword}")
@data([
[1, 2], [3, 4], [5, 6]
],
cartesian=True)
def test_cartesian_product(self, one, two, three):
"""
cartesian product
"""
print(f"test data: {one}, {two}, {three}")
```
通过`@data()` 装饰器来参数化测试用例。
**动态生成测试数据**
除了使用固定的数据外,也可以动态生成一些测试数据用于自动化测试。
```python
import seldom
from seldom import data
from seldom import testdata
def test_data() -> list:
"""
自动生成测试数据
return [{},{}]
"""
login_data = []
for i in range(5):
login_data.append({
"scene": f"login{i}",
"username": testdata.get_email(),
"password": testdata.get_int(100000, 999999)
})
return login_data
class MyTest(seldom.TestCase):
@data(test_data())
def test_login(self, _, username, password):
"""test login"""
print(f"test username: {username}")
print(f"test password: {password}")
```
### @file_data() 方法
当测试数据量比较大的情况下,可以通过`@file_data()`管理测试数据。
__CSV 文件参数化__
seldom 支持将`csv`文件的参数化。
表格内容如下(data.csv):
| username | password |
|----------|----------|
| admin | admin123 |
| guest | guest123 |
```python
import seldom
from seldom import file_data
class YouTest(seldom.TestCase):
@file_data("data.csv", line=2, end_line=10)
def test_login(self, username, password):
"""a simple test case """
print(username)
print(password)
# ...
```
- file: 指定 csv 文件的路径。
- line: 指定从第几行开始读取,默认第 1 行。
- end_line: 指定读取到第几行的数据,默认None, 最后一行。
**excel 文件参数化**
seldom 支持将`excel`文件的参数化。
```python
import seldom
from seldom import file_data
class YouTest(seldom.TestCase):
@file_data("data.xlsx", sheet="Sheet1", line=2, end_line=10)
def test_login(self, username, password):
"""a simple test case """
print(username)
print(password)
# ...
```
- file : 指定 excel 文件的路径。
- sheet: 指定 excel 的标签页,默认名称为 Sheet1。
- line : 指定从第几行开始读取,默认第 1 行。
- end_line: 指定读取到第几行的数据,默认None, 最后一行。
**JSON 文件参数化**
seldom 支持将`JSON`文件的参数化。
json 文件:
```json
{
"login1": [
[
"admin",
"admin123"
],
[
"guest",
"guest123"
]
],
"login2": [
{
"username": "Tom",
"password": "tom123"
},
{
"username": "Jerry",
"password": "jerry123"
}
]
}
```
> 注:`login1` 和 `login2` 的调用方法一样。 区别是前者更简洁,后者更易读。
```python
import seldom
from seldom import file_data
class YouTest(seldom.TestCase):
@file_data("data.json", key="login1")
def test_login(self, username, password):
"""a simple test case """
print(username)
print(password)
# ...
```
- file : 指定 JSON 文件的路径。
- key: 指定字典的 key,默认不指定解析整个 JSON 文件。
**YAML 文件参数化**
seldom 支持`YAML`文件的参数化。
data.yaml 文件:
```yaml
login1:
- - admin
- admin123
- - guest
- guest123
login2:
- username: Tom
password: tom123
- username: Jerry
password: jerry123
```
同`JSON`用法一样,`YAML`书写更加简洁。
```python
import seldom
from seldom import file_data
class YouTest(seldom.TestCase):
@file_data("data.yaml", key="login1")
def test_login(self, username, password):
"""a simple test case """
print(username)
print(password)
# ...
```
- file : 指定 YAML 文件的路径。
- key: 指定字典的 key,默认不指定解析整个 YAML 文件。
__解释: `@file_data()`是如何查找测试数据文件的?__
```shell
mypro/
├── test_dir/
│ ├── module/
│ │ ├── case/
│ │ │ ├── test_sample.py (使用@file_data)
├── test_data/
│ ├── module_data/
│ │ ├── data.csv (测试数据文件所以位置)
...
```
在 `test_sample.py` 中使用`@file_data("data.csv")`默认只能向上查找两级目录,即到`module`目录下遍历查找`data.csv`
文件。显然这中情况下是无法找到`data.csv` 文件的。
如果用例层级比较深,只需要指定文件目录的`“相对路径”`即可,使用方式:`@file_data("test_data/module_data/data.csv")`
,不要加`./`的前缀。
**支持配置测试环境**
在自动化测试过程中,我们往往需要一套代码在不同的环境下运行,seldom支持根据环境使用不同的数据文件。
* 数据文件目录结构(一)
```shell
.
└── test_data
├── develop
│ └── test_data.json
├── product
│ └── test_data.json
└── test
└── test_data.json
```
* 数据文件目录结构(二)
```shell
.
├── develop
│ └── test_data
│ └── test_data.json
├── product
│ └── test_data
│ └── test_data.json
└── test
└── test_data
└── test_data.json
```
* 配置测试环境
```python
import seldom
from seldom import file_data
class MyTest(seldom.TestCase):
# 数据文件目录结构(一)
@file_data("test_data.json")
def test_case(self, req, resp):
f"""a simple test case"""
...
# 数据文件目录结构(二)
@file_data("test_data/test_data.json")
def test_case(self, req, resp):
f"""a simple test case"""
...
if __name__ == '__main__':
# test/develop/product 设置当前环境
seldom.main(debug=True, env="product")
```
`env` 默认为`None`,当设置了`环境变量`,`@file_data()`会带上`环境变量`的目录名,例如:
* `test_data.json` 查找的文件为 `product/test_data.json`
* `test_data/test_data.json` 查找的文件为 `product/test_data/test_data.json`
> * `env` 可以随意命名,但最好遵循一定的规范,例如`test/develop/product`用于区分不同的环境。
> * 我们还可以利用`env`环境变量实现更多的配置,下面的示例。
```python
import seldom
from seldom import Seldom
class MyTest(seldom.TestCase):
def test_env(self):
if Seldom.env == "product":
username = "admin"
elif Seldom.env == "develop":
username = "guest"
else:
username = "tom"
...
if __name__ == '__main__':
seldom.main(debug=True, env="product")
```
### @api_data()方法
越来越多的公司落地 数据工厂,通过造数平台/数据工厂 来创建管理测试数据;`@api_data()` 装饰器支持通过URL获取驱动数据。
* 接口数据
`http://127.0.0.1:8080/v1/public/data_service/get_case_data?data_id=1`
```json
{
"success": true,
"error": {
"code": "",
"message": ""
},
"result": [
{
"scene": "测试1",
"email": "li123@126.com",
"password": "abc123"
},
{
"scene": "测试2",
"email": "li456@126.com",
"password": "abc456"
}
]
}
```
* 调用接口数据
```python
import seldom
from seldom import api_data
class TestApi(seldom.TestCase):
@api_data(url="http://127.0.0.1:8080/v1/public/data_service/get_case_data",
params={"data_id": 1},
headers={"X-Account-Email": "li.li@gmail.com"},
ret="result")
def test_case(self, scene, email, password):
"""
test get request
"""
print("name:", scene)
print("email:", email)
print("password:", password)
if __name__ == '__main__':
seldom.main(debug=True)
```
__`api_data()`参数说明__
* url: 返回数据的接口url地址;默认仅支持`GET` 接口。
* params: 请求参数。
* header: 请求头。
* ret: 提取接口返回的数据,默认仅支持 list 类型。
### 使用函数构造数据
如果数据驱动使用的数据比较简单其有规律,可以通过自定义函数生成,并且把函数传给 `@data()` 装饰器即可。
```python
import seldom
from seldom import data
def register():
"""生成注册账号信息"""
users = []
for i in range(10):
users.append({
"username": f"user{i}",
"password": f"abc123{i}",
"password2": f"abc123{i}"}
)
return users
class DDTTest(seldom.TestCase):
@data(register())
def test_data_func(self, username, password, password2):
""" data driver case """
print("username->", username)
print("password->", password)
print("password2->", password2)
if __name__ == '__main__':
seldom.main(debug=True)
```
### 支持第三方 ddt 库
seldom 仍然允许你使用第三方参数化库,例如:[ddt](https://github.com/datadriventests/ddt)。
安装:
```shell
> pip install ddt
```
创建测试文件`test_data.json`:
```json
{
"test_data_1": {
"word": "seldom"
},
"test_data_2": {
"word": "unittest"
},
"test_data_3": {
"word": "selenium"
}
}
```
在 seldom 使用`ddt`。
```python
import seldom
from ddt import ddt, file_data
@ddt
class YouTest(seldom.TestCase):
@file_data("test_data.json")
def test_case(self, word):
"""a simple test case """
self.open("https://www.baidu.com")
self.type(id_="kw", text=word)
self.click(css="#su")
self.assertTitle(word + "_百度搜索")
if __name__ == '__main__':
seldom.main()
```
更多的用法请查看 ddt 文档:https://ddt.readthedocs.io/en/latest/example.html
================================================
FILE: docs/vpdocs/getting-started/dependent_func.md
================================================
# 方法的依赖
> 在 seldom 3.4.0 版本实现了该功能。
在复杂的测试场景中,常常会存在用例依赖,以一个接口自动化平台为例,依赖关系:
`创建用例` --> `创建模块` --> `创建项目` --> `登录`。
__用例依赖的问题__
* 用例的依赖对于的执行顺序有严格的要求,比如让被依赖的方法先执行。
* 一旦使用用例依赖,依赖的用例就无法单独执行了,按照用例的设计原则,每条用例都应该独立执行。
__正确的做法__
`我们应该将依赖的操作封装成方法调用`。如果能通过装饰器实现调用,那就很有趣了。
[aomaker](https://github.com/ae86sen/aomaker) 提供了这种装饰器的实现,seldom 进行了复刻,只是的定位和用法用有所不同。
### 类内部方法调用
我们可以在测试类下面,创建普通的方法。然后通过`@dependent_func()`装饰器调用他。
```python
import seldom
from seldom.testdata import get_md5
from seldom.utils import cache, dependent_func
class DependentTest(seldom.TestCase):
@staticmethod
def user_login(username, password):
"""
模拟用户登录,获取登录token
"""
return get_md5(username+password)
@dependent_func(user_login, username="tom", password="t123")
def test_case(self,):
"""
sample test case
"""
token = cache.get("user_login")
print("token", token)
if __name__ == '__main__':
seldom.main(debug=True)
cache.clear()
```
__说明__
这个例子涉及到不少知识点。
1. `test_case()` 用例依赖 `user_login()` 方法,通过 `@dependent_func()` 装饰器调用 `user_login` 方法。
2. `user_login()` 方法运行的时候需要参数(username、password),可以直接在 `@dependent_func()` 装饰器中设置参数:`username="tom"`、 `password="t123"`。
3. `user_login()` 方法运行运行完会生成 token, 保存于 cache中,通过 ` cache.get()` 可以获取到token, 默认通过方法名`user_login` 作为key获取。
4. 为了简化代码,生成token 是通过 `get_md5()` 根据传入的参数生成的一个 md5 值。
5. `cache.clear()` 用于清空缓存, 再次调用 `user_login()` 方法直接不执行,应为cache已经有上次的执行结果了。
__执行日志__
```shell
python zzz_demo.py
...
test_case (zzz_demo.DependentTest.test_case)
sample test case ... 2023-11-15 23:26:36 | INFO | dependence.py | 🔗 <test_case> depends on <user_login>, execute.
2023-11-15 23:26:36 | INFO | cache.py | 💾 Set cache data: user_login = 35e0ff9c4cba89998dda8255d0eb5408
2023-11-15 23:26:36 | INFO | cache.py | 💾 Get cache data: user_login = 35e0ff9c4cba89998dda8255d0eb5408
token 35e0ff9c4cba89998dda8255d0eb5408
ok
----------------------------------------------------------------------
Ran 1 test in 0.005s
OK
2023-11-15 23:26:36 | SUCCESS | runner.py | A run the test in debug mode without generating HTML report!
2023-11-15 23:26:36 | INFO | cache.py | 💾 Clear all cache data
```
### 外部类方法依赖
* 创建依赖方法
```python
# common.py
from seldom.testdata import get_md5
class Login:
@staticmethod
def account_login(username, password):
"""
模拟用户&密码登录,获取登录token
"""
return get_md5(username+password)
login=Login()
```
* 用例引用依赖方法
```python
import seldom
from seldom.utils import cache, dependent_func
from common import Login # 方式1:引用依赖类
# from common import login # 方式2:引用初始化好的类对象
class DependentTest(seldom.TestCase):
@dependent_func(Login().account_login, key_name="token1", username="tom", password="t123")
# @dependent_func(login.account_login, key_name="token1", username="tom", password="t123")
def test_case(self):
"""
Used tuple test data
"""
token = cache.get("token1")
print("token", token)
if __name__ == '__main__':
seldom.main(debug=True)
```
__说明__
1. `Common` 类的`account_login()`方法可以不设置为静态方法,导入时需要类需要加括号:`Common().user_login`。 或者先初始化类对象`login=Login()` 再调用。
2. `key_name` 指定缓存的 `key`,如果指定为`token1`, 从缓存读取也使用这个`cache.get("token1")`。
### 多重方法依赖
复杂的场景当然是需要多重依赖的。
1. 被依赖的方法可以进一步使用 `dependent_func()`装饰器进行多重复依赖。
`查询模块` --> `查询项目` --> `登录`
```python
# common.py
from seldom.testdata import get_md5, get_int
from seldom.utils import cache, dependent_func
class Login:
@staticmethod
def account_login(username, password):
"""
模拟用户&密码登录,获取登录token
"""
return get_md5(username+password)
class DepFunc:
@staticmethod
@dependent_func(Login.account_login, key_name="token", username="jack", password="456")
def get_project_id():
token = cache.get("token")
print(f"使用token:{token} 查询项目, 返回项目ID")
return get_int(1, 1000)
@staticmethod
@dependent_func(get_project_id, key_name="pid")
def get_module_id():
pid = cache.get("pid")
print(f"使用项目ID:{pid} 查询模块, 返回模块ID")
return get_int(1, 1000)
```
在用例中直接调用 `DepFunc.get_module_id` 方法即可。
```python
import seldom
from seldom.utils import cache, dependent_func
from common import DepFunc
class DependentTest(seldom.TestCase):
@dependent_func(DepFunc.get_module_id, key_name="mid")
def test_case(self):
"""
sample test case
"""
mid = cache.get("mid")
print(f"模块ID: {mid}")
if __name__ == '__main__':
seldom.main(debug=True)
cache.clear()
```
2. 测试用例也可以同时使用多个 `@dependent_func()` 装饰器依赖多个方法,顺序由上到下执行,这种方式主要用于被依赖的方法之间没有依赖关系。
```python
# common.py
from seldom.testdata import get_int, username
class DataFunc:
@staticmethod
def get_name():
return username(language="zh")
@staticmethod
def get_age():
return get_int(1, 99)
```
在用例中使用多个`@dependent_func()`依赖装饰器。
```python
import seldom
from seldom.utils import cache, dependent_func
from common import DataFunc
class DependentTest(seldom.TestCase):
@dependent_func(DataFunc.get_name, key_name="name")
@dependent_func(DataFunc.get_age, key_name="age")
def test_case(self):
"""
sample test case
"""
name = cache.get("name")
age = cache.get("age")
print(f"名字: {name}, 年龄: {age}")
if __name__ == '__main__':
seldom.main(debug=True)
cache.clear()
```
### 参数化使用
参数化 `@data()`、 `@file_data()` 是seldom最重要的功能之一,能否和 `@dependent_func()` 一起使用? 当然可以。
```python
import seldom
from seldom import data
from seldom.testdata import get_md5
from seldom.utils import cache, dependent_func
class DependentTest(seldom.TestCase):
@staticmethod
def user_login(username, password):
"""
模拟用户登录,获取登录token
"""
return get_md5(username+password)
@data([
("case1", "foo"),
("case2", "bar"),
])
@dependent_func(user_login, username="tom", password="t123")
def test_case(self, _, keyword):
"""
Used tuple test data
"""
token = cache.get("user_login")
print(keyword, "token", token)
if __name__ == '__main__':
seldom.main(debug=True)
cache.clear()
```
__说明__
1. `@data()` 装饰器必须写在 `@dependent_func()` 的上面。
2. 运行两条用例,`user_login()` 被执行过一次后,第二次则不需要重复执行,直接返回结果。
================================================
FILE: docs/vpdocs/getting-started/installation.md
================================================
# Installation
seldom的安装非常简单。
* 快速安装
目前已经上传 pypi.org ,可以使用pip命令安装。
```shell
> pip install seldom
```
* 体验最新代码
如果你想随时体验最新的代码,可以使用下面的命令。
```shell
> pip install -U git+https://github.com/defnngj/seldom.git@master
```
* 安装依赖
随着seldom 加入更多的功能,seldom不得不依赖其他的开源库。你可以在 requirements.txt 文件里面看到这些依赖。
```shell
Appium-Python-Client>=4.1.0
XTestRunner>=1.7.2
loguru>=0.7.0
openpyxl>=3.0.3
pyyaml>=6.0
jsonschema>=4.10.0
jmespath>=0.10.0
pymysql>=1.0.0
genson==1.2.2
click~=8.1.3
python-dateutil==2.8.2
```
先通过 `pip` 命令安装这些依赖库,可以加快seldom的安装。
```shell
> pip install -r requirements.txt
```
* 检查安装
最后,我们可以通过`pip show seldom`命令检查安装。
```shell
> pip show seldom
Name: seldom
Version: 3.x.x
Summary: Seldom automation testing framework based on unittest.
Home-page: https://seldomqa.github.io
Author: bugmaster
Author-email: fnngj@126.com
License: Apache-2.0
Location: C:\Python311\Lib\site-packages
Requires: Appium-Python-Client, click, genson, jmespath, jsonschema, loguru, openpyxl, pymysql, python-dateutil, pyyaml, requests, websocket-client, XTestRunner
Required-by:
```
================================================
FILE: docs/vpdocs/getting-started/quick_start.md
================================================
# 快速开始
### 基本规范
`seldom`继承`unittest`单元测试框架,所以他的编写规范与[unittest](https://docs.python.org/3/library/unittest.html)基本保持一致。
```shell
# test_sample.py
import seldom
class YouTest(seldom.TestCase):
def test_case(self):
"""a simple test case """
self.assertEqual(1+1, 2)
if __name__ == '__main__':
seldom.main()
```
基本规范:
1. 创建测试类`YouTest`并继承`seldom.TestCase`类。
2. 创建测试方法`test_case`, 必须以`test`开头。
3. `seldom.main()`是框架运行的入口方法,接下来详细介绍。
### `main()` 方法
`main()`方法是seldom运行测试的入口, 它提供了一些最基本也是最重要的配置。
```python
import seldom
# ...
if __name__ == '__main__':
seldom.main(path="./",
case="test_file.MyClassTest.test_case",
browser="chrome",
base_url=None,
debug=False,
timeout=10,
app_server=None,
app_info=None,
report=None,
title="百度测试用例",
tester="虫师",
description="测试环境:chrome",
rerun=0,
language="en",
whitelist=[],
blacklist=[],
open=True,
extensions=None,
failfast=False,
env="test",
benchmark=False
)
```
__参数说明__
* `path` : 指定测试目录或文件, 与`case`参数互斥。`seldom > 3.7.0 支持 list 传多个目录或文件`。
* `case` : 指定测试用例, 与`path`参数互斥。
* `browser` : 指定浏览器("chrome"、"firefox" 等), Web测试。
* `base_url` : 设置全局的基本URL, HTTP测试。
* `app_info` : app 启动信息,参考`desired_capabilities`配置, app测试。
* `app_server` : appium server 启动地址(默认 http://127.0.0.1:4723), app测试。
* `report` : 自定义测试报告的名称,默认格式为`2020_04_04_11_55_20_result.html`。
* `title` : 指定测试报告标题。
* `tester` : 指定测试人员, 默认`Anonymous`。
* `description` : 指定测试报告描述。
* `debug` : debug模式,设置为True不生成测试HTML测试,默认为`False`。
* `rerun` : 设置失败重新运行次数,默认为 `0`。
* `language` : 设置HTML报告中英文,默认`en`, 中文`zh-CN`。
* `timeout` : 设置超时时间,默认`10`秒。
* `whitelist` : 用例标签(label)设置白名单。
* `blacklist` : 用例标签(label)设置黑名单。
* `open` : 是否使用浏览器自动打开测试报告,默认`True`。
* `extensions`: 加载扩展,appium使用。
* `failfast`: 当执行到失败的用例时,停止执行,仅在 `debug=True`时有效。
* `env`: 设置运行环境变量。
* `benchmark`: 是否进行基准测试。
* `device`: 设置移动设备ID(例如:`MDX0220413010000`)
### `confrun.py` 配置文件
> seldom 3.1.0 提供过了 `confrun.py` 用于配置运行环境。 配置函数与 `seldom.main()` 的参数一致。
在这个文件中可以定义运行相关的钩子函数。
```py
"""
seldom confrun.py hooks function
"""
from seldom.appium_lab.android import UiAutomator2Options
def start_run():
"""
Test the hook function before running
"""
...
def end_run():
"""
Test the hook function after running
"""
...
def browser():
"""
Web UI test:
browser: gc(google chrome)/ff(firefox)/edge/ie/safari
"""
return "gc"
def base_url():
"""
http test
api base url
"""
return "http://httpbin.org"
def app_info():
"""
app UI test
appium app config
"""
capabilities = {
"automationName": "UiAutomator2",
"platformName": "Android",
"appPackage": "com.meizu.flyme.flymebbs",
"appActivity": "com.meizu.myplus.ui.splash.SplashActivity",
"noReset": True,
}
options = UiAutomator2Options().load_capabilities(capabilities)
return options
def app_server():
"""
app UI test
appium server/desktop address
"""
return "http://127.0.0.1:4723"
def debug():
"""
debug mod
"""
return False
def rerun():
"""
error/failure rerun times
"""
return 0
def report():
"""
setting report path
Used:
return "d://mypro/result.html"
return "d://mypro/result.xml"
"""
return None
def timeout():
"""
setting timeout
"""
return 10
def title():
"""
setting report title
"""
return "seldom test report"
def tester():
"""
setting report tester
"""
return "bugmaster"
def description():
"""
setting report description
"""
return ["windows", "jenkins"]
def language():
"""
setting report language
return "en"
return "zh-CN"
"""
return "en"
def whitelist():
"""test label white list"""
return []
def blacklist():
"""test label black list"""
return []
def mock_url():
"""
Replace the fixed url with the mock url
:return:
"""
config = {
"http://httpbin.org/get": "http://127.0.0.1:8000/api/data",
}
return config
def failfast():
"""Use case exe failed to stop, only support debug=True"""
return False
```
以上配置根据需求自动化项目类型配置,相互可能冲突的钩子函数:
* Web UI测试: `browser()`
* http 接口测试: `base_url()`
* app UI测试: `app_info()`, `app_server()`
参数表格:
| seldom.main() (参数) | confrun.py (函数) | 类型 | 说明 |
|--------------------|------------------|------|------------------------------------------------------|
| path | - | 通用 | 指定测试目录或文件, 与`case`参数互斥。 |
| case | - | 通用 | 指定测试用例, 与`path`参数互斥。 |
| browser | browser() | Web | 指定web测试运行的浏览器。 |
| base_url | base_url() | HTTP | 指定HTTP接口测试的基本URL。 |
| - | mock_url() | HTTP | 配置HTTP接口 mock URL。 |
| - | proxies() | HTTP | 配置HTTP接口proxies代理。 |
| app_info | app_info() | App | app 启动信息,参考appium `desired_capabilities`配置, app测试。 |
| app_server | app_server() | App | appium server 启动地址(默认 http://127.0.0.1:4723), app测试。 |
| report | report() | 通用 | 自定义测试报告的名称,例如`result.html/result.xml`。 |
| title | title() | 通用 | 指定HTML报告标题。 |
| tester | tester() | 通用 | 指定HTML报告测试人员。 |
| description | description() | 通用 | 指定HTML报告描述。 |
| language | language() | 通用 | 设置HTML报告中英文,默认`en`, 中文`zh-CN`。 |
| debug | debug() | 通用 | debug模式,设置为True不生成测试HTML测试,默认为`False`。 |
| rerun | rerun() | 通用 | 设置失败重新运行次数。 |
| timeout | timeout() | 通用 | 设置自动化全局超时时间,默认`10`秒。作用于元素定位、断言等。 |
| whitelist | whitelist() | 通用 | 用例标签(label)设置白名单。 |
| blacklist | blacklist() | 通用 | 用例标签(label)设置黑名单。 |
| open | - | 通用 | 是否使用浏览器自动打开测试报告,默认`True`。 |
| extensions | - | App | 加载扩展,appium使用。 |
| failfast | - | 通用 | 当执行到失败的用例时,停止执行,仅在 `debug=True`时有效。 |
| env | - | 通用 | 设置运行环境变量。 |
| benchmark | - | 通用 | 是否进行基准测试。 |
### 运行测试
seldom 的运行有三种方式:
* `main()` 方法:在`.py` 文件中使用`seldom.main()` 方法。
* `seldom` 命令:通过`sedom` 命令指定要运行的目录&文件&类&方法。
* ~~`pycharm`右键执行:这种方式无法读取到配置,有严重缺陷。~~
> 强烈建议使用前两种!!
__1. `main()`方法运行测试__
* 目录结构
```
mypro/
├── test_dir/
│ ├── __init__.py
│ ├── test_sample.py
└── run.py # 运行配置文件
```
创建 `test_sample.py` 文件,在测试文件中使用`main()`方法,如下:
```py
# test_sample.py
import seldom
from seldom import data
class YouTest(seldom.TestCase):
def test_case(self):
"""a simple test case """
self.assertEqual(1 + 1, 2)
@data([
("case1", "seldom"),
("case2", "XTestRunner"),
])
def test_ddt(self, name, search):
""" ddt case """
print(f"name: {name}, search_key: {search}")
if __name__ == '__main__':
# 运行当前文件中的用例
seldom.main() # 默认运行当前文件中所有用例
seldom.main(case="test_sample") # 指定当前文件
seldom.main(case="test_sample.YouTest") # 指定测试类
seldom.main(case="test_sample.YouTest.test_case") # 指定测试用例
# 使用参数化的用例
seldom.main(case="test_sample.YouTest.test_ddt") # 错误用法
seldom.main(case="test_sample.YouTest.test_ddt_0") # 正确用法,0表示第一条数据用例
```
创建 `run.py` 文件,用于全局的指定要运行的用例。
```python
import seldom
if __name__ == '__main__':
# 指定运行其他目录&文件
seldom.main(path="./") # 指定当前文件所在目录下面的用例。
seldom.main(path="./test_dir/") # 指定当前目录下面的test_dir/ 目录下面的用例。
seldom.main(path="./test_dir/test_sample.py") # 指定测试文件中的用例。
seldom.main(path="D:/seldom_sample/test_dir/test_sample.py") # 指定文件的绝对路径。
```
`seldom.main()` 提供哪些参数,请参考前面的文档。
* 运行测试文件
```shell
> cd mypro/ # 进入项目根目录
> python ./test_dir/test_sample.py # 运行指定测试文件
> python run.py # 运行run.py文件
```
__2. seldom命令执行__
* 目录结构
```
mypro/
├── test_dir/
│ ├── __init__.py
│ ├── test_sample.py
└── confrun.py # 运行配置文件
```
`seldom -p`命令指定目录和文件。
`seldom -m`命令可以提供更细粒度的运行。
```shell
> cd mypro/ # 进入项目根目录
> seldom -p test_dir # 运行目录
> seldom -p test_dir/test_sample.py # 运行文件
> seldom -m test_dir.test_sample # 运行文件
> seldom -m test_dir.test_sample.YouTest # 运行 SampleTest 测试类
> seldom -m test_dir.test_sample.YouTest.test_case # 运行 test_case 测试方法
```
运行相关的配置,可以在`confrun.py` 文件中配置。
__3. 在pyCharm中运行测试__
> 强烈不建议这种方式,除非你的测试用例没有任何依赖。
步骤一:配置测试用例通过 unittest 运行。

步骤二:在文件中选择测试类或用例执行。

> 警告:运行用例打开的浏览器,需要手动关闭, seldom不做用例关闭操作。
### 失败重跑
Seldom支持`错误`&`失败`重跑。
```python
# test_sample.py
import seldom
class YouTest(seldom.TestCase):
def test_error(self):
"""error case"""
self.assertEqual(a, 2)
def test_fail(self):
"""fail case """
self.assertEqual(1 + 1, 3)
if __name__ == '__main__':
seldom.main(rerun=3)
```
参数说明:
* rerun: 指定重跑的次数,默认为 `0`。
运行日志:
```shell
> python test_sample.py
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v3.x.x
-----------------------------------------
@itest.info
XTestRunner Running tests...
----------------------------------------------------------------------
ERetesting... test_error (test_sample.YouTest)..1
ERetesting... test_error (test_sample.YouTest)..2
ERetesting... test_error (test_sample.YouTest)..3
EFRetesting... test_fail (test_sample.YouTest)..1
FRetesting... test_fail (test_sample.YouTest)..2
FRetesting... test_fail (test_sample.YouTest)..3
Generating HTML reports...
F2022-07-12 00:22:52 log.py | SUCCESS | generated html file: file:///D:\github\seldom\reports\2022_07_12_00_22_51_result.html
2022-07-12 00:22:52 log.py | SUCCESS | generated log file: file:///D:\github\seldom\reports\seldom_log.log
```
### 测试报告
seldom 默认生成HTML测试报告,在运行测试文件下自动创建`reports`目录。
* 运行测试用例前
```shell
mypro/
└── test_sample.py
```
* 运行测试用例后
```shell
mypro/
├── reports/
│ ├── 2020_01_01_11_20_33_result.html
│ ├── seldom_log.log
└── test_sample.py
```
通过浏览器打开 `2020_01_01_11_20_33_result.html` 测试报告,查看测试结果。

__debug模式__
如果不想每次运行都生成HTML报告,可以打开`debug`模式。
```py
import seldom
seldom.main(debug=True)
```
__定义测试报告__
```py
import seldom
seldom.main(report="./report.html",
title="百度测试用例",
tester="虫师",
description="测试环境:windows 10/ chrome")
```
* report: 配置报告名称和路径。
* title: 自定义报告的标题。
* tester: 指定自动化测试工程师名字。
* description: 添加报告信息,支持列表, 例如:["OS: windows","Browser: chrome"]。
__XML测试报告__
如果需要生成XML格式的报告,只需要修改报告的后缀名为`.xml`即可。
```py
import seldom
seldom.main(report="report.xml")
```
### 多线程运行
多线程无疑可以缩短用例的运行时间,一般由两种方式实现。
1. 设置线程数,交由框架去分配用例,或按照测试用例、测试类、测试模块分配给线程执行。
* 优点:简单,例如 pytest-xdist ,只需要指定 `线程数` 即可。
* 缺点:无法控制用例的拆分粒度,如果在设计用例时,不同的用例有依赖,刚好被分到的不同的线程,那么必定导致用例失败。
2. 自己分好线程,分别调用框架执行。
* 优点:手动划分线程,可以按照目录、文件、甚至测试类或方法 拆分线程。
* 缺点:首先会比较麻烦,而且多个线程的执行结果无法很好的合并到一起。
seldom 推荐第二种方法,把线程的划分方式交给用户,无疑是更灵活的方法。至于报告的合并统计就每有什么好办法了。
* 用例维度使用多线程。
```python
import seldom
from seldom.extend_lib import threads
class MyTest(seldom.TestCase):
def test_baidu(self):
self.open("https://www.baidu.com")
self.sleep(3)
def test_bing(self):
self.open("https://www.bing.com")
self.sleep(4)
@threads(2) # !!!核心!!!! 设置线程数
def run_case(case: str, browser: str):
"""
根据传入的case执行用例
"""
seldom.main(case=case, browser=browser, debug=True)
if __name__ == "__main__":
# 将两条用例拆分,分别用不同的浏览器执行
cases = {
"test_thread_case.MyTest.test_baidu": "chrome",
"test_thread_case.MyTest.test_bing": "edge"
}
for key, value in cases.items():
run_case(key, value)
```
* 目录或文件维度使用多线程。
```python
import seldom
from seldom.extend_lib import threads
@threads(3) # !!!核心!!!! 设置线程数
def run_case(path: str):
"""
根据传入的path执行用例
"""
seldom.main(path=path, debug=True)
if __name__ == "__main__":
# 定义3个测试文件,分别丢给3个线程执行。
paths = [
"./test_dir/more_case/test_case1.py",
"./test_dir/more_case/test_case2.py",
"./test_dir/more_case/test_case3.py"
]
for p in paths:
run_case(p)
```
================================================
FILE: docs/vpdocs/getting-started/seldom_cli.md
================================================
# seldom CLI
`seldom 2.10.7` 对命令行工具做了增强,可以使用命令行的方式运行用例。
## seldom 帮助
* `seldom --help` 查看帮助使用
```shell
> seldom --help
Usage: seldom [OPTIONS]
seldom CLI.
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --version -v Show version. │
│ --project-api -api TEXT Create a project of API type. [default: None] │
│ --project-app -app TEXT Create a project of App type [default: None] │
│ --project-web -web TEXT Create a project of Web type [default: None] │
│ --clear-cache -cc Clear all caches of seldom. │
│ --log-level -ll TEXT Set the log level [TRACE |DEBUG | INFO | SUCCESS | WARNING | ERROR]. │
│ [default: None] │
│ --mod -m TEXT Run tests modules, classes or even individual test methods from the command │
│ line. │
│ [default: None] │
│ --path -p TEXT Run test case file path. [default: None] │
│ --env -e TEXT Set the Seldom run environment `Seldom.env`. [default: None] │
│ --browser -b TEXT The browser that runs the Web UI automation tests [chrome | edge | firefox | │
│ chromium]. Need the --path. │
│ [default: None] │
│ --base-url -u TEXT The base-url that runs the HTTP automation tests. Need the --path. │
│ [default: None] │
│ --debug -d Debug mode. Need the --path/--mod. │
│ --rerun -rr INTEGER The number of times a use case failed to run again. Need the --path. │
│ [default: 0] │
│ --report -r TEXT Set the test report for output. Need the --path. [default: None] │
│ --collect -c Collect project test cases. Need the --path. │
│ --level -l TEXT Parse the level of use cases [data | case]. Need the --path. [default: data] │
│ --case-json -j TEXT Test case files. Need the --path. [default: None] │
│ --har2case -h2c TEXT HAR file converts an seldom test case. [default: None] │
│ --swagger2case -s2c TEXT Swagger file converts an seldom test case. [default: None] │
│ --api-excel TEXT Run the api test cases in the excel file. [default: None] │
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or customize the │
│ installation. │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
如果无法使用`seldom` 命令。
1. 请确保你已经安装了seldom
```shell
> pip install seldom
```
2. 如果仍然无法使用`seldom`命令,请用`where`检查安装位置。
```shell
> where seldom
C:\Python311\Scripts\seldom.exe
```
## seldom 使用
### 创建项目
- `-api/-app/-web/`
```shell
> seldom -api myapi # API automation test project.
> seldom -app myapp # or App automation test project.
> seldom -web myweb # or Web automation test project.
```
注:`har` 是fiddler 抓包工具导出的一种格式,即 `HTTPArchive`。
### 运行测试目录&文件
* `-p\--path`
```shell
> seldom -p ./test_dir/ # 指定运行目录
> seldom -p ./test_dir/test_first_demo.py # 指定运行文件
```
不支持斜杠`\`表示路径
### 运行文件&类&方法
* `-m\--mod`
```shell
> seldom -m test_dir # 目录名
> seldom -m test_dir.test_sample # 目录名.文件名,不要.py后缀
> seldom -m test_dir.test_sample.SampleTest # 目录名.文件名.类名
> seldom -m test_dir.test_sample.SampleTest.test_case # 目录名.文件名.类名.方法名
```
### 调试模式
* ` -d, --debug`
```shell
> seldom -p test_sample.py -d # 开启debug模式(默认不指定-d关闭)
```
### 运行浏览器
* `-b/--browser`
```shell
> seldom -p test_sample.py -b firefox # firefox浏览器
```
> 支持`[chrome|chrimium|firefox|edge]` 浏览器。
### 运行URL
* `-u/--base-url`
```shell
> seldom -p test_http_demo.py -u http://httpbin.org # base-url
```
### 测试报告
* `-r/--report`
```shell
> seldom -p test_first_demo.py -r result.html # HTML报告
> seldom -p test_first_demo.py -r result.xml # XML报告
```
### 失败/错误重跑次数
* `-rr/--rerun`
```shell
> seldom -p test_first_demo.py -rr 2 # rerun重跑次数
```
### 数据驱动运行环境
* `-e/--env`
```shell
> seldom -p test_ddt_demo.py -e production # 运行环境
```
> 注:参考`数据驱动` 一章 `Seldom.env` 的用法。
### 收集测试用例
```shell
> seldom -p test_dir -c -l method -j case.json
Collect use cases for the test_dir directory.
add env Path: .
__ __
________ / /___/ /___ ____ ____
/ ___/ _ \/ / __ / __ \/ __ ` ___/
(__ ) __/ / /_/ / /_/ / / / / / /
/____/\___/_/\__,_/\____/_/ /_/ /_/ v{x}.{x}.{x}
-----------------------------------------
@itest.info
save them to D:\github\seldom\demo\case.json
```
* 说明:
- `-p/--path`: 指定收集用例的目录:`test_dir`。
- `-c, --collect`: 指定收集用例, 默认`False`。
- `-l/--level`: 指定收集用例级别: `data/method`。
- `-j/--case-json`: 收集用例保存文件: `case.json`。
### 运行收集测试用例
```shell
> seldom -p test_dir -j case.json -r result.html
```
* 说明:
- `-p/--path`: 指定运行用例的根目录:`test_dir`。
- `-j/--case-json`: 运行收集用例文件: `case.json`。
- `-r/--report`: 运行收集用例生成报告: `result.html`。
### 清除所有缓存
```shell
> seldom --clear-cache
```
* 说明:默认清空seldom所有缓存,即`cache.clear()`
### har转接口测试用例
* `-h2c/--har2case`
```shell
> seldom -h2c demo.har
2022-09-03 11:29:29 core.py | INFO | demo.py
2022-09-03 11:29:29 core.py | INFO | Start to generate testcase.
2022-09-03 11:29:29 core.py | INFO | created file: D:\github\seldom\seldom\har2case\demo.py
```
### swagger转接口测试用例
* `-s2c/--swagger2case`
```shell
> seldom -s2c swagger.json
2025-07-08 23:24:04 | INFO | core.py | MainThread | Start to generate testcase.
2025-07-08 23:24:04 | INFO | core.py | MainThread | created file: D:\github\seldomQA\seldom\seldom\swagger2case\swagger.py
```
### 执行 API(excel文件)测试用例
```shell
> seldom --api-excel api_case.xlsx
```
* 说明:简单的HTTP接口测试可以使用excel编写,seldom支持运行excel文件。excel的具体定义可以参考`HTTP接口测试`章节。
================================================
FILE: docs/vpdocs/introduce.md
================================================
# 介绍
## 新书推荐
<p>
<a href="https://item.jd.com/10124939676219.html">
<img alt="京东链接" src="/image/book.jpg" style="width: 360px; margin-right: 140px;" />
</a>
</p>
京东 [购买链接](https://item.jd.com/14859108.html)
天猫 [购买链接](https://detail.tmall.com/item.htm?id=852715481274&skuId=5817727406269)
当当 [购买链接](https://product.dangdang.com/29809610.html)
依托于 SeldomQA 相关项目的开发和维护,在 `自动化测试框架设计`、 `定制化测试报告设计`、 `设计模式`,以及`测试平台开发`
方面有着深厚技术积累和独特的设计理念。
一本真正介绍 __自动化测试框架设计__ 的书终于出版了,书中浅显易懂的介绍了 SeldomQA
相关项目中的诸多设计和封装技术。并且,介绍了`一个开源自动化测试框架从设计到发布的整个流程`。
如果你正在使用SeldomQA相关项目之余,想了解他们背后的设计,那么这本书非常值得购买。
## seldom框架
### 特点
> seldom 是基于 unittest 的全功能自动化测试框架;针对自动化测试达到开箱即用。
__seldom特点__
* 支持测试类型(web/app/api)
* 丰富的断言
* 生成随机测试数据
* 用例依赖
* 用例分类标签
* 支持发送(邮件、钉钉、飞书、企微)消息等
* 日志打印
* 缓存cache
* 命令行工具
* 强大的数据驱动(JSON/YAML/CSV/EXCEL)
* HTML/XML报告
* 失败重跑&截图
* 数据库操作(MySQL/sqlite3/Mongodb)
* 支持平台化
### 设计理念
简单一句话就是回到最初写代码的样子。
自动化测试框架很多,只有在测试领域有一个比较奇怪的现象,如何用不写代码的方式解决自动化问题。为此,我们发明了用特定领域语言写用例,发明了用
excel 写用例,发明了用 YAML/JSON 写用例。这些方案看似简化了用例的编写,但是,会让解决复杂的问题变得更复杂。比如实现个分支判断/循环,传递参数,调用封装的步骤,编程语言中用
if/for 、变量、函数就实现了,但是用非编程语言的方式写用例处理起来就很麻烦。最终,并不能完全脱离编程,那么为什么不一开始就选择一个编程框架呢?
然而,seldom的定位是尽量用简单的设计去解决复杂问题,例如
Flask、requests、yagmail...等,这些框架/库都有一个共同的特点,用简单的方式去解决复杂的问题,在编程语言这个层面,并不会给你太多限制,你可以完全使用它,也可以只用一部分,也可以平滑的实现它不支持的功能。
seldom的目标以就让你用最少的代码编写自动化测试用例,当遇到seldom没有的功能,你可以方便的进行扩展。-- 这就是seldom的设计理念。
### 发展历史
2015年7月15号我在github上提交一个自动化项目,命名为:`pyse`, 即各取了`python` 和 `selenium`前两个字符。项目非常简单核心就三个文件。
* `pyse.py`:针对 selenium API做了简单封装。
* `HTMLTestRunner.py`: 修改的HTMLTestRunner报告。
* `TestRunner.py`: 一个简单的 unittest运行器。
之后项目断断续续的在维护,直到2019年,也许是太闲了,加上对UI自动化有了更深入的理解,重新投入主要精力维护pyse项目。
后来就需要将提交到pypi,这样更方便通过pip安装,发现 `pyse` 早已经被占用了,后来更名为`seldom`
,其实命名没有太多寓意,就是看他长得和`selenium`比较接近。
2020年1月发布1.0版本,之所以发布1.0
是因为自认为框架的功能比较成熟了,并且花费时间补充了文档。大家都不重视文档,其实文档非常重要,也需要花大量的时间更新和维护。有时候你加个功能很简单,编写说明文档和使用示例就要花费等同的时间。
1.0 版本之后,项目核心围绕着 selenium API的封装 和 unittest框架扩展(seldom基于unittest)等。
2021年4月正式发布 2.0,集成requests, 正式支持http接口测试。起因是发现cypress支持http调用,哦,原来UI测试工具也可以去做接口,格局一下子打开了!如何在不影响现有selenium
API的情况下集成requests是2.0考虑的重点。
2022年1月seldom项目正式在公司内部推广使用,当时我们做了几版的接口测试平台,平台的开发维护成本比较高,对于复杂的场景用例,编写成本比框架还要复杂简单;功能也依赖于平台所提供的,相比较而言,框架却有最大的灵活性,可以很好的基于业务做各种设计和封装。
因为在公司得到推广使用,seldom明显进入了更加快速的迭代开发阶段,并且稳定性、可用性会得到了很大的提升。
seldom 3.0 背景
seldom集成App测试是顺理成章的事情,早在几个月前我已经在公司项目中尝试 seldom + appium
进行App自动化测试。App自动化的维护成本确实比接口要高许多,这是由App本身的特点决定的,框架很难做到实质上的改变。
2022年10月seldom 3.0 beta发布,之所以选择appium有几个原因:
* appium 是由商业工具在维护,历史比较长,不会随意停止维护。
* appium 应用更加广泛,使用得人更多,支持得平台多(android/ios/flutter)
* appium 继承selenium,对于seldom来说对原有API改动最小。
目前,seldom 3.0 正式版已经发布,欢迎使用。
### seldom vs pytest
seldom 是建立在 unittest 的基础上的自动化测试框架。与 pytest进行对比,无疑相当于像拿一台`电脑`与一颗 intel `CPU` 进行比较,虽然
intel `CPU` 很强大,但我们无法直接拿一个`CPU`打游戏,对吧? pytest 就像一个 `CPU`
,虽然很强大,但无法直接拿来做自动化测试,比如配合各种测试库。而seldom不需要额外安装测试库,即可开始编写自动化测试用例。
* seldom vs pytest 对比差异
| 功能 | seldom | pytest |
|---------------|--------------------------------------------|---------------------------------------|
| web UI测试 | 支持 ✅ | 支持(需安装 selenium) ⚠️ |
| web UI断言 | 支持(assertText、assertTitle、assertElement) ✅ | 不支持 ❌ |
| playwright | 支持(需安装playwright) ⚠️ | 支持(playwright提供playwright-pytest插件) ✅ |
| 失败截图 | 支持(自动实现) ✅ | 支持(需要设置) ✅ |
| http接口测试 | 支持 ✅ | 支持(需安装 requests) ⚠️ |
| http接口断言 | 支持(assertJSON、assertPath、assertSchema) ✅ | 不支持 ❌ |
| app UI测试 | 支持 ✅ | 支持(需安装 appium) ⚠️ |
| Page Object模式 | 支持(推荐poium) ✅ | 支持(推荐poium) ✅ |
| 脚手架 | 支持(快速创建项目) ✅ | 不支持 ❌ |
| 生成随机测试数据 | 支持`testdata` ✅ | 不支持 ❌ |
| 发送消息 | 支持(email、钉钉、飞书、微信)✅ | 不支持 ❌ |
| log日志 | 支持 ✅ | 不支持 ❌ |
| 数据库操作 | 支持(sqlite3、MySQL、SQL Server) ✅ | 不支持 ❌ |
| 用例依赖 | 支持`@depend()` ✅ | `@pytest.mark.dependency()`支持 ✅ |
| 失败重跑 | 支持`rerun` ✅ | pytest-rerunfailures 支持 ✅ |
| 用例分类标签 | 支持`@label()` ✅ | `@pytest.mark.xxx`支持 ✅ |
| HTML测试报告 | 支持 ✅ | pytest-html、allure ✅ |
| XML测试报告 | 支持 ✅ | 自带 `--junit-xml` ✅ |
| 数据驱动方法 | `@data()` ✅ | `@pytest.mark.parametrize()` ✅ |
| 数据驱动文件 | `@file_data()`(JSON\YAML\CSV\Excel) ✅ | 不支持 ❌ |
| 钩子函数 | `confrun.py`用例运行钩子 ⚠️ | `conftest.py` 功能更强大 ✅ |
| 命令行工具CLI | 支持`seldom` ✅ | 支持`pytest` ✅ |
| 并发执行 | 不支持 ❌ | pytest-xdist、pytest-parallel ✅ |
| 平台化 | 支持(seldom-platform)✅ | 不支持 ❌
gitextract_p6uqsu94/
├── .gitignore
├── CHANGES.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── api_case/
│ ├── api_case.xlsx
│ └── confrun.py
├── demo/
│ ├── README.md
│ ├── __init__.py
│ ├── confrun.py
│ ├── reports/
│ │ └── .keep
│ ├── run.py
│ ├── test_data/
│ │ ├── csv_data.csv
│ │ ├── excel_data.xlsx
│ │ ├── json_data.json
│ │ └── yaml_data.yaml
│ └── test_dir/
│ ├── __init__.py
│ ├── api_case/
│ │ ├── __init__.py
│ │ └── test_http_demo.py
│ ├── app_case/
│ │ ├── __init__.py
│ │ ├── test_first_demo.py
│ │ ├── test_po_demo.py
│ │ └── test_u2_demo.py
│ └── web_case/
│ ├── __init__.py
│ ├── test_data_demo.py
│ ├── test_ddt_demo.py
│ ├── test_first_demo.py
│ ├── test_fixture_demo.py
│ ├── test_playwright_demo.py
│ └── test_po_demo.py
├── description.rst
├── docs/
│ ├── .gitignore
│ ├── README.md
│ ├── compare.md
│ ├── deploy.sh
│ ├── package.json
│ └── vpdocs/
│ ├── .vuepress/
│ │ └── config.js
│ ├── README.md
│ ├── api-testing/
│ │ ├── api_case.md
│ │ ├── api_object.md
│ │ ├── assert.md
│ │ ├── more.md
│ │ ├── start.md
│ │ └── webscocket.md
│ ├── app-testing/
│ │ ├── adb_lib.md
│ │ ├── appium_lab.md
│ │ ├── extensions.md
│ │ ├── page_object.md
│ │ └── start.md
│ ├── develop.md
│ ├── getting-started/
│ │ ├── advanced.md
│ │ ├── create_project.md
│ │ ├── data_driver.md
│ │ ├── dependent_func.md
│ │ ├── installation.md
│ │ ├── quick_start.md
│ │ └── seldom_cli.md
│ ├── introduce.md
│ ├── more-ability/
│ │ ├── benchmark.md
│ │ ├── db_operation.md
│ │ └── test_library.md
│ ├── platform/
│ │ └── platform.md
│ ├── version/
│ │ └── CHANGES.md
│ └── web-testing/
│ ├── browser_driver.md
│ ├── chaining.md
│ ├── other.md
│ ├── page_object.md
│ └── seldom_api.md
├── pyproject.toml
├── requirements.txt
├── seldom/
│ ├── __init__.py
│ ├── appdriver.py
│ ├── appium_lab/
│ │ ├── __init__.py
│ │ ├── action.py
│ │ ├── android.py
│ │ ├── appium_service.py
│ │ ├── find.py
│ │ ├── keyboard.py
│ │ ├── ocr_plugin.py
│ │ └── switch.py
│ ├── case.py
│ ├── cli.py
│ ├── db_operation/
│ │ ├── __init__.py
│ │ ├── base_db.py
│ │ ├── mongo_db.py
│ │ ├── mssql_db.py
│ │ ├── mysql_db.py
│ │ ├── postgres_db.py
│ │ └── sqlite_db.py
│ ├── driver.py
│ ├── extend_lib/
│ │ ├── __init__.py
│ │ ├── base_assert.py
│ │ ├── curlify.py
│ │ ├── jsonpath.py
│ │ ├── parameterized.py
│ │ └── tomorrow.py
│ ├── file_runner/
│ │ ├── __init__.py
│ │ └── api_excel.py
│ ├── har2case/
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── demo.har
│ │ └── utils.py
│ ├── logging/
│ │ ├── __init__.py
│ │ ├── exceptions.py
│ │ └── log.py
│ ├── project_temp/
│ │ ├── __init__.py
│ │ ├── api/
│ │ │ ├── confrun.py
│ │ │ ├── run.py
│ │ │ └── test_sample.py
│ │ ├── app/
│ │ │ ├── confrun.py
│ │ │ ├── run.py
│ │ │ └── test_sample.py
│ │ ├── data.json
│ │ └── web/
│ │ ├── confrun.py
│ │ ├── run.py
│ │ └── test_sample.py
│ ├── request.py
│ ├── running/
│ │ ├── DebugTestRunner.py
│ │ ├── __init__.py
│ │ ├── config.py
│ │ ├── loader_extend.py
│ │ ├── loader_hook.py
│ │ └── runner.py
│ ├── skip.py
│ ├── swagger2case/
│ │ ├── __init__.py
│ │ ├── core.py
│ │ └── swagger.json
│ ├── testdata/
│ │ ├── __init__.py
│ │ ├── conversion.py
│ │ ├── parameterization.py
│ │ ├── random_data.py
│ │ └── random_func.py
│ ├── utils/
│ │ ├── __init__.py
│ │ ├── adbutils.py
│ │ ├── benchmark.py
│ │ ├── cache.py
│ │ ├── cache_data.json
│ │ ├── dependence.py
│ │ ├── diff.py
│ │ ├── encrypt.py
│ │ ├── file_extend.py
│ │ ├── genson.py
│ │ ├── jmespath.py
│ │ ├── match_image.py
│ │ ├── resource_loader.py
│ │ ├── send_extend.py
│ │ ├── thread_lab.py
│ │ └── timer.py
│ ├── webcommon/
│ │ ├── __init__.py
│ │ ├── find_elems.py
│ │ ├── keyboard.py
│ │ ├── locators.py
│ │ └── selector.py
│ ├── webdriver.py
│ ├── webdriver_chaining.py
│ └── websocket_client.py
└── tests/
├── data/
│ ├── country.graphql
│ ├── db.sqlite3
│ └── hello.txt
├── test_adb.py
├── test_api_object.py
├── test_autowing.py
├── test_base_assert.py
├── test_benchmark.py
├── test_browser.py
├── test_browser_new.py
├── test_cache/
│ ├── __init__.py
│ ├── test_cache.py
│ ├── test_cache_thread.py
│ └── test_memory_cache.py
├── test_db/
│ ├── __init__.py
│ ├── test_db_mssql.py
│ ├── test_db_mysql.py
│ ├── test_db_postgresdb.py
│ └── test_db_sqlite3.py
├── test_dependent_func.py
├── test_encrypt.py
├── test_fixture.py
├── test_graphql.py
├── test_http_assert.py
├── test_jsonpath.py
├── test_locators.py
├── test_log.py
├── test_other_lib/
│ ├── __init__.py
│ ├── test_playwright.py
│ ├── test_pyautogui.py
│ └── test_uiautomator.py
├── test_playwright_sample.py
├── test_random/
│ ├── __init__.py
│ └── test_testdata.py
├── test_request_extend.py
├── test_skip.py
├── test_steps_chaining.py
├── test_steps_chaining_browser.py
├── test_thread/
│ ├── __init__.py
│ ├── test_thread.py
│ ├── test_thread_browser.py
│ ├── test_thread_case.py
│ └── test_thread_path.py
├── test_utils/
│ ├── __init__.py
│ └── test_file.py
└── test_websocket/
├── __init__.py
├── test_websocket.py
└── webscoket_server.py
SYMBOL INDEX (954 symbols across 114 files)
FILE: api_case/confrun.py
function base_url (line 6) | def base_url():
function debug (line 14) | def debug():
function rerun (line 21) | def rerun():
function report (line 28) | def report():
function timeout (line 37) | def timeout():
function title (line 44) | def title():
function tester (line 51) | def tester():
function description (line 58) | def description():
function language (line 65) | def language():
function failfast (line 73) | def failfast():
FILE: demo/confrun.py
function start_run (line 6) | def start_run():
function end_run (line 13) | def end_run():
function debug (line 20) | def debug():
function rerun (line 27) | def rerun():
function report (line 34) | def report():
function timeout (line 44) | def timeout():
function title (line 51) | def title():
function tester (line 58) | def tester():
function description (line 65) | def description():
function language (line 72) | def language():
function whitelist (line 81) | def whitelist():
function blacklist (line 86) | def blacklist():
FILE: demo/test_dir/api_case/test_http_demo.py
class TestRequest (line 5) | class TestRequest(seldom.TestCase):
method test_put_method (line 11) | def test_put_method(self):
method test_post_method (line 18) | def test_post_method(self):
method test_get_method (line 25) | def test_get_method(self):
method test_delete_method (line 33) | def test_delete_method(self):
class TestAssert (line 41) | class TestAssert(seldom.TestCase):
method test_data_assert (line 46) | def test_data_assert(self):
method test_format_assert (line 56) | def test_format_assert(self):
method test_path_assert (line 80) | def test_path_assert(self):
class TestRespData (line 90) | class TestRespData(seldom.TestCase):
method test_resp_data (line 95) | def test_resp_data(self):
method test_data_dependency (line 105) | def test_data_dependency(self):
class TestDDT (line 118) | class TestDDT(seldom.TestCase):
method test_data (line 128) | def test_data(self, key, value):
FILE: demo/test_dir/app_case/test_first_demo.py
class TestBingApp (line 7) | class TestBingApp(seldom.TestCase):
method start (line 12) | def start(self):
method test_bing_search (line 15) | def test_bing_search(self):
FILE: demo/test_dir/app_case/test_po_demo.py
class BingPage (line 8) | class BingPage(Page):
class TestBingApp (line 15) | class TestBingApp(seldom.TestCase):
method start (line 20) | def start(self):
method test_bbs (line 24) | def test_bbs(self):
FILE: demo/test_dir/app_case/test_u2_demo.py
class MyAppTest (line 9) | class MyAppTest(seldom.TestCase):
method start (line 11) | def start(self):
method end (line 17) | def end(self):
method test_app (line 21) | def test_app(self, user):
FILE: demo/test_dir/web_case/test_data_demo.py
class RandomDataTest (line 5) | class RandomDataTest(seldom.TestCase):
method test_case (line 10) | def test_case(self):
FILE: demo/test_dir/web_case/test_ddt_demo.py
class BingTest (line 5) | class BingTest(seldom.TestCase):
method test_bing_tuple (line 13) | def test_bing_tuple(self, desc, search_key):
method test_bing_list (line 28) | def test_bing_list(self, desc, search_key):
method test_bing_dict (line 43) | def test_bing_dict(self, scene, search_key):
class FileDataTest (line 54) | class FileDataTest(seldom.TestCase):
method start (line 57) | def start(self):
method test_json_list (line 61) | def test_json_list(self, firstname, lastname):
method test_json_dict (line 72) | def test_json_dict(self, _, username, password):
method test_yaml_list (line 83) | def test_yaml_list(self, firstname, lastname):
method test_yaml_dict (line 94) | def test_yaml_dict(self, username, password):
method test_csv (line 105) | def test_csv(self, firstname, lastname):
method test_excel (line 116) | def test_excel(self, firstname, lastname):
FILE: demo/test_dir/web_case/test_first_demo.py
class BingTest (line 5) | class BingTest(seldom.TestCase):
method test_case (line 8) | def test_case(self):
method test_case_two (line 15) | def test_case_two(self):
FILE: demo/test_dir/web_case/test_fixture_demo.py
class BaiduTest (line 4) | class BaiduTest(seldom.TestCase):
method start_class (line 11) | def start_class(cls):
method end_class (line 17) | def end_class(cls):
method start (line 21) | def start(self):
method end (line 27) | def end(self):
method test_open_index (line 31) | def test_open_index(self):
method test_open_news (line 35) | def test_open_news(self):
FILE: demo/test_dir/web_case/test_playwright_demo.py
class Playwright (line 10) | class Playwright(seldom.TestCase):
method start (line 12) | def start(self):
method end (line 17) | def end(self):
method test_start (line 20) | def test_start(self):
FILE: demo/test_dir/web_case/test_po_demo.py
class BaiduPage (line 13) | class BaiduPage(Page):
class BaiduTest (line 20) | class BaiduTest(seldom.TestCase):
method test_baidu_page (line 23) | def test_baidu_page(self):
FILE: seldom/appdriver.py
class AppDriver (line 14) | class AppDriver(WebDriver):
method __init__ (line 19) | def __init__(self):
method background_app (line 24) | def background_app(self, seconds: int):
method is_app_installed (line 35) | def is_app_installed(self, bundle_id: str) -> bool:
method install_app (line 47) | def install_app(self, app_path: str, **options: Any):
method remove_app (line 71) | def remove_app(self, app_id: str, **options: Any):
method terminate_app (line 90) | def terminate_app(self, app_id: str, **options: Any) -> bool:
method activate_app (line 106) | def activate_app(self, app_id: str):
method query_app_state (line 119) | def query_app_state(self, app_id: str) -> int:
method app_strings (line 132) | def app_strings(self, language: str = None, string_file: str = None) -...
method base64_image (line 147) | def base64_image(image_path: str):
method click_image (line 162) | def click_image(self, image_path: str) -> None:
method keyboard_search (line 174) | def keyboard_search(self) -> None:
FILE: seldom/appium_lab/__init__.py
class AppiumLab (line 10) | class AppiumLab(Action, FindByText, KeyEvent):
method check_state (line 20) | def check_state(self, app_id: str) -> int:
method launch_app (line 41) | def launch_app(self, app_id: str) -> None:
method close_app (line 49) | def close_app(self, app_id: str) -> None:
FILE: seldom/appium_lab/action.py
class Action (line 14) | class Action(Switch):
method __init__ (line 19) | def __init__(self, driver=None):
method size (line 26) | def size(self) -> dict:
method _perform_action (line 33) | def _perform_action(self, x_start: int, y_start: int, x_end: int, y_en...
method tap (line 45) | def tap(self, x: int, y: int, pause: float = 0.1, sleep: float = 2) ->...
method swipe_up (line 65) | def swipe_up(self, times: int = 1, upper: bool = False, interval_time:...
method swipe_down (line 88) | def swipe_down(self, times: int = 1, upper: bool = False, interval_tim...
method swipe_left (line 111) | def swipe_left(self, times: int = 1, width_percentage: float = 0.8, in...
method swipe_right (line 131) | def swipe_right(self, times: int = 1, width_percentage: float = 0.8, i...
method drag_from_to (line 151) | def drag_from_to(self, x_start: int, x_end: int, y_start: int, y_end: ...
FILE: seldom/appium_lab/android.py
class UiAutomator2Options (line 9) | class UiAutomator2Options(UiAutomator2Options):
method load_capabilities (line 14) | def load_capabilities(self: T, caps: Dict[str, Any]) -> T:
class EspressoOptions (line 24) | class EspressoOptions(EspressoOptions):
method load_capabilities (line 29) | def load_capabilities(self: T, caps: Dict[str, Any]) -> T:
FILE: seldom/appium_lab/appium_service.py
class AppiumService (line 7) | class AppiumService(OriginalServer):
method __init__ (line 12) | def __init__(self,
method start_service (line 25) | def start_service(self, **kwargs) -> None:
FILE: seldom/appium_lab/find.py
class FindByText (line 11) | class FindByText(Switch):
method __remove_unprintable_chars (line 17) | def __remove_unprintable_chars(string: str) -> str:
method __find (line 24) | def __find(self, class_name: str, attribute: str, text: str):
method find_view (line 47) | def find_view(self, text: str = None, content_desc: str = None):
method find_edit_text (line 74) | def find_edit_text(self, text: str):
method find_button (line 91) | def find_button(self, text: str = None, content_desc: str = None):
method find_text_view (line 118) | def find_text_view(self, text: str):
method find_image_view (line 135) | def find_image_view(self, text: str):
method find_check_box (line 152) | def find_check_box(self, text: str):
method find_static_text (line 169) | def find_static_text(self, text: str):
method find_other (line 186) | def find_other(self, text: str):
method find_text_field (line 203) | def find_text_field(self, text: str):
method find_image (line 220) | def find_image(self, text: str):
method find_ios_button (line 237) | def find_ios_button(self, text: str):
FILE: seldom/appium_lab/keyboard.py
class KeyEvent (line 66) | class KeyEvent:
method __init__ (line 72) | def __init__(self, driver):
method key_text (line 75) | def key_text(self, text: str = ""):
method press_key (line 96) | def press_key(self, key: str):
method long_press_key (line 108) | def long_press_key(self, key: str):
method back (line 120) | def back(self):
method home (line 125) | def home(self):
method hide_keyboard (line 130) | def hide_keyboard(self, key_name=None, key=None, strategy=None):
method is_keyboard_shown (line 146) | def is_keyboard_shown(self) -> bool:
FILE: seldom/appium_lab/ocr_plugin.py
class OCRCommand (line 8) | class OCRCommand(ExtensionBase):
method method_name (line 9) | def method_name(self):
method ocr_command (line 12) | def ocr_command(self, argument):
method add_command (line 15) | def add_command(self):
FILE: seldom/appium_lab/switch.py
class Switch (line 10) | class Switch:
method __init__ (line 15) | def __init__(self, driver=None):
method context (line 20) | def context(self):
method switch_to_app (line 30) | def switch_to_app(self) -> None:
method switch_to_web (line 39) | def switch_to_web(self, context_name: str = None) -> None:
method switch_to_flutter (line 55) | def switch_to_flutter(self) -> None:
method switch_to_ocr (line 64) | def switch_to_ocr(self) -> None:
method sleep (line 73) | def sleep(sec):
FILE: seldom/case.py
class TestCase (line 27) | class TestCase(unittest.TestCase, AppDriver, HttpRequest):
method start_class (line 30) | def start_class(self):
method end_class (line 36) | def end_class(self):
method setUpClass (line 43) | def setUpClass(cls):
method tearDownClass (line 56) | def tearDownClass(cls):
method start (line 70) | def start(self):
method end (line 76) | def end(self):
method setUp (line 82) | def setUp(self):
method tearDown (line 86) | def tearDown(self):
method driver (line 90) | def driver(self):
method device (line 97) | def device(self):
method browser (line 103) | def browser(self, name: str) -> None:
method quit (line 118) | def quit(self) -> None:
method new_browser (line 131) | def new_browser(self) -> WebDriver:
method assertTitle (line 139) | def assertTitle(self, title: str = None, msg: str = None) -> None:
method assertInTitle (line 159) | def assertInTitle(self, title: str = None, msg: str = None) -> None:
method assertUrl (line 179) | def assertUrl(self, url: str = None, msg: str = None) -> None:
method assertInUrl (line 200) | def assertInUrl(self, url: str = None, msg: str = None) -> None:
method assertText (line 221) | def assertText(self, text: str = None, msg: str = None) -> None:
method assertNotText (line 243) | def assertNotText(self, text: str = None, msg: str = None) -> None:
method assertAlertText (line 266) | def assertAlertText(self, text: str = None, msg: str = None) -> None:
method assertElement (line 287) | def assertElement(self, index: int = 0, msg: str = None, **kwargs) -> ...
method assertNotElement (line 305) | def assertNotElement(self, index: int = 0, msg: str = None, **kwargs) ...
method assertScreenshot (line 328) | def assertScreenshot(self, tolerance: int = 0, msg: str = None):
method assertStatusCode (line 343) | def assertStatusCode(self, status_code: int, msg: str = None) -> None:
method assertStatusOk (line 350) | def assertStatusOk(self, msg: str = None) -> None:
method assertSchema (line 357) | def assertSchema(self, schema, response=None) -> None:
method assertJSON (line 372) | def assertJSON(self, assert_json, response=None, exclude=None) -> None:
method assertPath (line 388) | def assertPath(self, path, value) -> None:
method assertInPath (line 397) | def assertInPath(self, path, value) -> None:
method xSkip (line 406) | def xSkip(self, reason):
method xFail (line 416) | def xFail(self, msg):
method sleep (line 427) | def sleep(sec: int | tuple = 1) -> None:
method pause (line 438) | def pause() -> None:
method assertEqual (line 448) | def assertEqual(self, first, second, msg=None):
method assertNotEqual (line 452) | def assertNotEqual(self, first, second, msg=None):
method assertTrue (line 456) | def assertTrue(self, expr, msg=None):
method assertFalse (line 460) | def assertFalse(self, expr, msg=None):
method assertIn (line 464) | def assertIn(self, member, container, msg=None):
method assertNotIn (line 468) | def assertNotIn(self, member, container, msg=None):
method assertIsInstance (line 472) | def assertIsInstance(self, obj, cls, msg=None):
method assertNotIsInstance (line 476) | def assertNotIsInstance(self, obj, cls, msg=None):
method assertRegex (line 480) | def assertRegex(self, text, regex, msg=None):
method assertNotRegex (line 484) | def assertNotRegex(self, text, regex, msg=None):
method assertAlmostEqual (line 488) | def assertAlmostEqual(self, first, second, places=None, msg=None, delt...
method assertNotAlmostEqual (line 492) | def assertNotAlmostEqual(self, first, second, places=None, msg=None, d...
method assertGreater (line 496) | def assertGreater(self, a, b, msg=None):
method assertGreaterEqual (line 500) | def assertGreaterEqual(self, a, b, msg=None):
method assertLess (line 504) | def assertLess(self, a, b, msg=None):
method assertLessEqual (line 508) | def assertLessEqual(self, a, b, msg=None):
method assertCountEqual (line 512) | def assertCountEqual(self, a, b, msg=None):
FILE: seldom/cli.py
function _import_seldom_core (line 20) | def _import_seldom_core():
function _import_har_parser (line 46) | def _import_har_parser():
function _import_swagger_parser (line 52) | def _import_swagger_parser():
function _import_file_running_config (line 58) | def _import_file_running_config():
function main (line 65) | def main(
function create_scaffold (line 263) | def create_scaffold(project_name: str, project_type: str, log) -> None:
function reset_case (line 308) | def reset_case(path: str, cases: list) -> tuple[str, list]:
FILE: seldom/db_operation/base_db.py
class SQLBase (line 6) | class SQLBase:
method dict_to_str (line 10) | def dict_to_str(data: dict) -> str:
method dict_to_str_and (line 26) | def dict_to_str_and(conditions: dict) -> str:
method delete (line 41) | def delete(self, table: str, where: dict = None) -> None:
method insert (line 47) | def insert(self, table: str, data: dict) -> None:
method select (line 53) | def select(self, table: str, where: dict = None, one: bool = False) ->...
method update (line 59) | def update(self, table: str, data: dict, where: dict) -> None:
FILE: seldom/db_operation/mongo_db.py
class MongoDB (line 10) | class MongoDB:
method __new__ (line 13) | def __new__(cls, host: str, port: int, db: str):
FILE: seldom/db_operation/mssql_db.py
class MSSQLDB (line 12) | class MSSQLDB(SQLBase):
method __init__ (line 15) | def __init__(self, server: str, user: str, password: str, database: st...
method close (line 31) | def close(self) -> None:
method execute_sql (line 37) | def execute_sql(self, sql: str) -> None:
method query_sql (line 46) | def query_sql(self, sql: str) -> list:
method query_one (line 60) | def query_one(self, sql: str) -> Any:
method insert_get_last_id (line 71) | def insert_get_last_id(self, sql: str) -> int:
method insert_data (line 83) | def insert_data(self, table: str, data: dict) -> None:
method select_data (line 94) | def select_data(self, table: str, where: dict = None, one: bool = Fals...
method update_data (line 106) | def update_data(self, table: str, data: dict, where: dict) -> None:
method delete_data (line 116) | def delete_data(self, table: str, where: dict = None) -> None:
method init_table (line 125) | def init_table(self, table_data: dict, clear: bool = True) -> None:
FILE: seldom/db_operation/mysql_db.py
class MySQLDB (line 9) | class MySQLDB(SQLBase):
method __init__ (line 12) | def __init__(self, host: str, port: int, user: str, password: str, dat...
method close (line 29) | def close(self) -> None:
method execute_sql (line 35) | def execute_sql(self, sql: str) -> None:
method query_sql (line 46) | def query_sql(self, sql: str) -> list:
method query_one (line 61) | def query_one(self, sql: str) -> Any:
method insert_get_last_id (line 73) | def insert_get_last_id(self, sql: str) -> int:
method insert_data (line 86) | def insert_data(self, table: str, data: dict) -> None:
method select_data (line 97) | def select_data(self, table: str, where: dict = None, one: bool = Fals...
method update_data (line 109) | def update_data(self, table: str, data: dict, where: dict) -> None:
method delete_data (line 119) | def delete_data(self, table: str, where: dict = None) -> None:
method init_table (line 128) | def init_table(self, table_data: dict, clear: bool = True) -> None:
FILE: seldom/db_operation/postgres_db.py
class PostgresDB (line 10) | class PostgresDB(SQLBase):
method __init__ (line 12) | def __init__(self, host: str, port: int, database: str, user: str, pas...
method close (line 26) | def close(self):
method execute_sql (line 32) | def execute_sql(self, sql: str) -> None:
method query_sql (line 40) | def query_sql(self, sql: str) -> list:
method query_one (line 54) | def query_one(self, sql: str) -> Any:
method insert_get_last_id (line 63) | def insert_get_last_id(self, sql: str) -> int:
method insert_data (line 74) | def insert_data(self, table: str, data: dict) -> None:
method select_data (line 85) | def select_data(self, table: str, where: dict = None, one: bool = Fals...
method update_data (line 96) | def update_data(self, table: str, data: dict, where: dict) -> None:
method delete_data (line 105) | def delete_data(self, table: str, where: dict = None) -> None:
method init_table (line 115) | def init_table(self, table_data: dict, clear: bool = True) -> None:
FILE: seldom/db_operation/sqlite_db.py
class SQLiteDB (line 9) | class SQLiteDB(SQLBase):
method __init__ (line 12) | def __init__(self, db_path: str):
method close (line 19) | def close(self) -> None:
method execute_sql (line 25) | def execute_sql(self, sql: str) -> None:
method insert_data (line 32) | def insert_data(self, table: str, data: dict) -> None:
method query_sql (line 43) | def query_sql(self, sql: str) -> list:
method query_one (line 55) | def query_one(self, sql: str) -> Any:
method insert_get_last_id (line 64) | def insert_get_last_id(self, sql: str) -> int:
method select_data (line 75) | def select_data(self, table: str, where: dict = None, one: bool = Fals...
method update_data (line 87) | def update_data(self, table: str, data: dict, where: dict) -> None:
method delete_data (line 97) | def delete_data(self, table: str, where: dict = None) -> None:
method init_table (line 106) | def init_table(self, table_data: dict, clear: bool = True) -> None:
FILE: seldom/driver.py
class Browser (line 17) | class Browser:
method __new__ (line 25) | def __new__(cls, name: str = None, executable_path=None, options=None,...
method chrome (line 51) | def chrome(executable_path, options, command_executor):
method firefox (line 73) | def firefox(executable_path, options, command_executor):
method ie (line 85) | def ie(executable_path, options, command_executor):
method edge (line 97) | def edge(executable_path, options, command_executor):
method safari (line 109) | def safari(executable_path, options, command_executor):
FILE: seldom/extend_lib/base_assert.py
function log_assertions (line 5) | def log_assertions(func):
FILE: seldom/extend_lib/curlify.py
function to_curl (line 8) | def to_curl(request, compressed: bool = False, verify: bool = True) -> str:
FILE: seldom/extend_lib/jsonpath.py
function normalize (line 16) | def normalize(x: str) -> str:
function jsonpath (line 38) | def jsonpath(
FILE: seldom/extend_lib/parameterized.py
class SkipTest (line 32) | class SkipTest(Exception):
class InstanceType (line 43) | class InstanceType:
function make_method (line 51) | def make_method(func, instance, type):
function to_text (line 59) | def to_text(x):
function getargspec (line 71) | def getargspec(func):
function skip_on_empty_helper (line 85) | def skip_on_empty_helper(*a, **kw):
function reapply_patches_if_need (line 89) | def reapply_patches_if_need(func):
class DummyPatchTarget (line 141) | class DummyPatchTarget(object):
method create_dummy_patch (line 145) | def create_dummy_patch():
function delete_patches_if_need (line 152) | def delete_patches_if_need(func):
class param (line 163) | class param(_param):
method __new__ (line 185) | def __new__(cls, *args, **kwargs):
method explicit (line 189) | def explicit(cls, args=None, kwargs=None):
method from_decorator (line 203) | def from_decorator(cls, args):
method __repr__ (line 226) | def __repr__(self):
class QuietOrderedDict (line 230) | class QuietOrderedDict(MaybeOrderedDict):
function parameterized_argument_value_pairs (line 237) | def parameterized_argument_value_pairs(func, p):
function short_repr (line 297) | def short_repr(x, n=64):
function default_doc_func (line 312) | def default_doc_func(func, num, p):
function default_name_func (line 336) | def default_name_func(func, num, p):
function set_test_runner (line 353) | def set_test_runner(name):
function detect_runner (line 363) | def detect_runner():
class parameterized (line 389) | class parameterized(object):
method __init__ (line 410) | def __init__(self, input, doc_func=None, skip_on_empty=False):
method __call__ (line 415) | def __call__(self, test_func):
method param_as_nose_tuple (line 466) | def param_as_nose_tuple(self, test_self, func, num, p):
method assert_not_in_testcase_subclass (line 485) | def assert_not_in_testcase_subclass(self):
method _terrible_magic_get_defining_classes (line 492) | def _terrible_magic_get_defining_classes(self):
method input_as_callable (line 510) | def input_as_callable(cls, input):
method check_input_values (line 517) | def check_input_values(cls, input_values):
method expand (line 528) | def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=Fa...
method param_as_standalone_func (line 611) | def param_as_standalone_func(cls, p, func, name):
method to_safe_name (line 637) | def to_safe_name(cls, s):
function parameterized_class (line 643) | def parameterized_class(attrs, input_values=None, class_name_func=None):
function get_class_name_suffix (line 702) | def get_class_name_suffix(params_dict):
function default_class_name_func (line 716) | def default_class_name_func(cls, num, params_dict):
FILE: seldom/extend_lib/tomorrow.py
function threads (line 10) | def threads(n, queue_max=None):
FILE: seldom/file_runner/api_excel.py
class APITest (line 8) | class APITest(seldom.TestCase):
method test_api_excel (line 11) | def test_api_excel(self, name, url, method, headers, param_type, param...
FILE: seldom/har2case/core.py
class HarParser (line 9) | class HarParser:
method __init__ (line 12) | def __init__(self, har_file_path: str):
method _make_testcase (line 33) | def _make_testcase(self) -> str:
method create_file (line 92) | def create_file(save_path: str, file_content: str = "") -> None:
method gen_testcase (line 100) | def gen_testcase(self) -> None:
FILE: seldom/har2case/utils.py
function load_har_log_entries (line 10) | def load_har_log_entries(file_path):
function list_to_dict_str (line 39) | def list_to_dict_str(data: list) -> str:
FILE: seldom/logging/exceptions.py
class SeldomException (line 6) | class SeldomException(Exception):
method __init__ (line 11) | def __init__(self, msg: str = None, screen: str = None, stacktrace: st...
method __str__ (line 16) | def __str__(self):
class BrowserTypeError (line 26) | class BrowserTypeError(SeldomException):
class NotFindElementError (line 33) | class NotFindElementError(SeldomException):
class TestFixtureRunError (line 40) | class TestFixtureRunError(SeldomException):
class FileTypeError (line 47) | class FileTypeError(SeldomException):
class RunParamError (line 54) | class RunParamError(SeldomException):
class RunningError (line 61) | class RunningError(SeldomException):
FILE: seldom/logging/log.py
class LogConfig (line 29) | class LogConfig:
method __init__ (line 32) | def __init__(self, level: str = "DEBUG", colorlog: bool = True):
method set_level (line 41) | def set_level(self, colorlog: bool = True, format: str = None, level: ...
FILE: seldom/project_temp/api/confrun.py
function base_url (line 8) | def base_url():
function debug (line 16) | def debug():
function rerun (line 23) | def rerun():
function report (line 30) | def report():
function timeout (line 40) | def timeout():
function title (line 47) | def title():
function tester (line 54) | def tester():
function description (line 61) | def description():
function language (line 68) | def language():
function whitelist (line 77) | def whitelist():
function blacklist (line 82) | def blacklist():
FILE: seldom/project_temp/api/test_sample.py
class TestRequest (line 5) | class TestRequest(seldom.TestCase):
method test_put_method (line 7) | def test_put_method(self):
method test_post_method (line 12) | def test_post_method(self):
method test_get_method (line 17) | def test_get_method(self):
method test_delete_method (line 23) | def test_delete_method(self):
class TestDDT (line 29) | class TestDDT(seldom.TestCase):
method test_get_method (line 32) | def test_get_method(self, _, id_, name):
FILE: seldom/project_temp/app/confrun.py
function app_info (line 9) | def app_info():
function app_server (line 26) | def app_server():
function debug (line 34) | def debug():
function rerun (line 41) | def rerun():
function report (line 48) | def report():
function timeout (line 58) | def timeout():
function title (line 65) | def title():
function tester (line 72) | def tester():
function description (line 79) | def description():
function language (line 86) | def language():
function whitelist (line 95) | def whitelist():
function blacklist (line 100) | def blacklist():
FILE: seldom/project_temp/app/test_sample.py
class TestBingApp (line 7) | class TestBingApp(seldom.TestCase):
method start (line 12) | def start(self):
method test_bing_search (line 15) | def test_bing_search(self):
FILE: seldom/project_temp/web/confrun.py
function browser (line 8) | def browser():
function debug (line 16) | def debug():
function rerun (line 23) | def rerun():
function report (line 30) | def report():
function timeout (line 40) | def timeout():
function title (line 47) | def title():
function tester (line 54) | def tester():
function description (line 61) | def description():
function language (line 68) | def language():
function whitelist (line 77) | def whitelist():
function blacklist (line 82) | def blacklist():
FILE: seldom/project_temp/web/test_sample.py
class SampleTest (line 5) | class SampleTest(seldom.TestCase):
method test_case (line 7) | def test_case(self):
class DDTTest (line 14) | class DDTTest(seldom.TestCase):
method test_data_driver (line 17) | def test_data_driver(self, _, keyword):
FILE: seldom/request.py
class ResponseResult (line 24) | class ResponseResult:
function formatting (line 30) | def formatting(msg):
function request (line 37) | def request(func):
function mock_url (line 109) | def mock_url(url: str) -> str:
function check_proxies (line 126) | def check_proxies() -> Any | None:
class HttpRequest (line 134) | class HttpRequest:
method __init__ (line 137) | def __init__(self, base_url=None, *args, **kwargs):
method get (line 145) | def get(self, url, params=None, **kwargs):
method post (line 154) | def post(self, url, data=None, json=None, **kwargs):
method put (line 163) | def put(self, url, data=None, **kwargs):
method delete (line 172) | def delete(self, url, **kwargs):
method patch (line 181) | def patch(self, url, data=None, **kwargs):
method response (line 190) | def response(self) -> Any:
method jsonpath (line 198) | def jsonpath(expr, index: int = None, response=None) -> Any:
method jmespath (line 214) | def jmespath(expr, response=None) -> Any:
method status_code (line 226) | def status_code(self) -> int:
method curl (line 234) | def curl(request=None, compressed: bool = False, verify: bool = True) ...
class Session (line 247) | class Session(requests.Session):
method get (line 250) | def get(self, url, **kwargs):
method post (line 265) | def post(self, url, data=None, json=None, **kwargs):
method put (line 283) | def put(self, url, data=None, **kwargs):
method delete (line 300) | def delete(self, url, **kwargs):
method json_to_dict (line 315) | def json_to_dict(data: str, replace_quotes: bool = True) -> dict:
method base_url (line 343) | def base_url(self):
method save_response (line 350) | def save_response(response: requests.Response, filename: str = None):
method ip_address (line 382) | def ip_address(self, url: str = None) -> str:
method base_url (line 397) | def base_url(self, value):
function check_response (line 401) | def check_response(describe: str = "", status_code: int = 200, ret: str ...
function retry (line 464) | def retry(times: int = 3, wait: int = 1):
FILE: seldom/running/DebugTestRunner.py
class DebugTestRunner (line 10) | class DebugTestRunner(unittest.TextTestRunner):
method __init__ (line 13) | def __init__(self, *args, **kwargs):
method test_iter (line 23) | def test_iter(cls, suite):
method run (line 34) | def run(self, testlist):
FILE: seldom/running/config.py
class Seldom (line 9) | class Seldom:
method driver (line 16) | def driver(self):
method driver (line 23) | def driver(self, value):
method base_url (line 27) | def base_url(self):
method base_url (line 34) | def base_url(self, value):
method device (line 38) | def device(self):
method device (line 45) | def device(self, value):
class BrowserConfig (line 63) | class BrowserConfig:
function base_url (line 78) | def base_url():
function driver (line 83) | def driver():
function env (line 88) | def env():
class FileRunningConfig (line 93) | class FileRunningConfig:
function report_local_style (line 100) | def report_local_style() -> bool:
FILE: seldom/running/loader_extend.py
class SeldomTestLoader (line 9) | class SeldomTestLoader(TestLoader):
method getTestCaseNames (line 18) | def getTestCaseNames(self, testCaseClass):
method rediscover (line 65) | def rediscover(self, start_dir, pattern='test*.py', top_level_dir=None):
FILE: seldom/running/loader_hook.py
function loader (line 6) | def loader(func_name: str, file_name: str = "confrun.py", *args, **kwargs):
FILE: seldom/running/runner.py
class TestMain (line 49) | class TestMain:
method __init__ (line 55) | def __init__(
method run (line 210) | def run(self, suits) -> None:
method open_browser (line 255) | def open_browser(self) -> None:
method close_browser (line 273) | def close_browser() -> None:
class TestMainExtend (line 289) | class TestMainExtend(TestMain):
method __init__ (line 296) | def __init__(
method collect_cases (line 324) | def collect_cases(self, json: bool = False, level: str = "data", warni...
method _load_testsuite (line 375) | def _load_testsuite(self, warning: bool = False) -> Dict[str, List[Any]]:
method run_cases (line 407) | def run_cases(self, data: list) -> None:
FILE: seldom/skip.py
function skip (line 12) | def skip(reason=None):
function skip_if (line 23) | def skip_if(condition, reason):
function skip_unless (line 33) | def skip_unless(condition, reason):
function expected_failure (line 43) | def expected_failure(test_item):
function depend (line 52) | def depend(case=None):
function if_depend (line 77) | def if_depend(value):
function label (line 93) | def label(*labels):
function rerun (line 112) | def rerun(times: int = 2):
FILE: seldom/swagger2case/core.py
class SwaggerParser (line 8) | class SwaggerParser:
method __init__ (line 10) | def __init__(self, swagger: str, online=False):
method local_swagger_file (line 22) | def local_swagger_file(file_path: str) -> dict:
method online_swagger_doc (line 33) | def online_swagger_doc(url: str) -> dict:
method create_file (line 43) | def create_file(save_path: str, code: str = "") -> None:
method swagger_to_seldom_code (line 51) | def swagger_to_seldom_code(self, swagger_doc: dict) -> str:
method gen_testcase (line 128) | def gen_testcase(self) -> None:
FILE: seldom/testdata/conversion.py
function check_data (line 13) | def check_data(list_data: list) -> list:
function csv_to_list (line 35) | def csv_to_list(file: str = None, line: int = 1, end_line: int = None) -...
function excel_to_list (line 58) | def excel_to_list(file: str = None, sheet: str = "Sheet1", line: int = 1...
function json_to_list (line 88) | def json_to_list(file: str = None, key: str = None) -> list:
function yaml_to_list (line 116) | def yaml_to_list(file: str = None, key: str = None) -> list:
FILE: seldom/testdata/parameterization.py
function _search_file_path (line 33) | def _search_file_path(file_name: str, file_dir: Path) -> str:
function _search_env_file_path (line 59) | def _search_env_file_path(file_dir: Path, file_part_path: str) -> str:
function find_file (line 82) | def find_file(file: str, file_dir: Path) -> str:
function file_data (line 111) | def file_data(file: str, line: int = 1, sheet: str = "Sheet1", key: str ...
function api_data (line 169) | def api_data(url: str = None, params: dict = None, headers: dict = None,...
function data (line 198) | def data(input, name_func=None, doc_func=None, skip_on_empty=False, cart...
function data_class (line 267) | def data_class(attrs, input_values=None, class_name_func=None):
function default_name_func (line 274) | def default_name_func(func, num, p):
function default_doc_func (line 287) | def default_doc_func(func, num, p):
function cartesian_product (line 315) | def cartesian_product(arr) -> list:
FILE: seldom/testdata/random_func.py
function first_name (line 31) | def first_name(gender: str = "", language: str = "en") -> str:
function last_name (line 65) | def last_name(language: str = "en") -> str:
function username (line 81) | def username(name: str = "", language: str = "en") -> str:
function password (line 98) | def password(length: int = 12) -> str:
function get_email (line 119) | def get_email(name: str = "") -> str:
function get_md5 (line 140) | def get_md5(val: str = "") -> str:
function get_uuid (line 157) | def get_uuid() -> str:
function get_int (line 164) | def get_int(min_size: int = 1, max_size=sys.maxsize) -> int:
function get_int32 (line 173) | def get_int32(min_size: int = 1) -> int:
function get_int64 (line 180) | def get_int64(min_size=1):
function get_float (line 185) | def get_float(min_size: float = None, max_size: float = None) -> float:
function get_digits (line 202) | def get_digits(count: int) -> str:
function get_string (line 214) | def get_string(length: int = 8) -> str:
function get_number (line 224) | def get_number(length: int = 8) -> int:
function yes (line 237) | def yes(specifier=0) -> int:
function get_words (line 287) | def get_words(count: int = 0, as_str: bool = True, words=None) -> str:
function get_word (line 305) | def get_word(words=None) -> str:
function get_birthday (line 310) | def get_birthday(as_str: bool = False, start_age: int = 18, stop_age: in...
function get_past_datetime (line 335) | def get_past_datetime(now=None, strftime=False) -> datetime:
function get_future_datetime (line 355) | def get_future_datetime(now=None, strftime=False) -> datetime:
function get_now_datetime (line 375) | def get_now_datetime(strftime=False) -> [str, datetime]:
function get_past_time (line 386) | def get_past_time() -> str:
function get_future_time (line 396) | def get_future_time() -> datetime:
function get_date (line 406) | def get_date(day=None) -> str:
function get_month (line 419) | def get_month(month: int = None) -> str:
function get_year (line 433) | def get_year(year: int = None) -> str:
function get_phone (line 447) | def get_phone(operator: str = None) -> str:
function get_timestamp (line 469) | def get_timestamp(level="second") -> str:
function online_timestamp (line 483) | def online_timestamp() -> str:
function online_now_datetime (line 494) | def online_now_datetime() -> [str, datetime]:
FILE: seldom/utils/adbutils.py
class ADBUtils (line 11) | class ADBUtils:
method __init__ (line 17) | def __init__(self, default_device: str = None):
method _safe_popen (line 28) | def _safe_popen(self, command: str):
method refresh_devices (line 48) | def refresh_devices(self, force: bool = False) -> List[Tuple[str, str]]:
method _get_device_name (line 78) | def _get_device_name(self, device_serial: str) -> str:
method set_default_device (line 92) | def set_default_device(self, device_serial: str) -> bool:
method launch_app (line 105) | def launch_app(self, package_name: str, device_id: Optional[str] = Non...
method close_app (line 121) | def close_app(self, package_name: str, device_id: Optional[str] = None...
method get_app_info (line 137) | def get_app_info(self, device_id: Optional[str] = None) -> dict:
method _parse_window_dump (line 176) | def _parse_window_dump(self, dump_content: str) -> List[dict]:
FILE: seldom/utils/benchmark.py
class Benchmark (line 5) | class Benchmark:
method __init__ (line 10) | def __init__(self):
method measure (line 14) | def measure(func: Callable, rounds: int, iterations: int, *args: Any, ...
method add_result (line 35) | def add_result(self, test_name: str, durations: List[float], iteration...
method get_stats (line 48) | def get_stats(self, test_name: str) -> Dict[str, Any]:
method report (line 75) | def report(self) -> None:
method get_outliers (line 99) | def get_outliers(self, durations: List[float], mean: float, stddev: fl...
method mean (line 125) | def mean(self, data: List[float]) -> float:
method stddev (line 129) | def stddev(self, data: List[float], mean: float) -> float:
method median (line 134) | def median(self, data: List[float]) -> float:
method percentile (line 143) | def percentile(self, data: List[float], p: float) -> float:
method iqr (line 154) | def iqr(self, data: List[float]) -> float:
function benchmark_test (line 162) | def benchmark_test(rounds: int = 5, iterations: int = 1):
FILE: seldom/utils/cache.py
class Cache (line 18) | class Cache:
method __init__ (line 24) | def __init__(self):
method clear (line 31) | def clear(name: str = None) -> None:
method set (line 53) | def set(data: dict) -> None:
method get (line 74) | def get(name=None):
function memory_cache (line 96) | def memory_cache(maxsize=None, typed=False):
class DiskCache (line 102) | class DiskCache:
method __init__ (line 109) | def __init__(self, cache_path=None):
method __call__ (line 115) | def __call__(self, func):
method clear (line 146) | def clear(self, func_name: str = None) -> None:
FILE: seldom/utils/dependence.py
function dependent_func (line 7) | def dependent_func(func_obj: Callable, key_name: Text = None, *out_args,...
function _call_dependence (line 45) | def _call_dependence(dependent_api: Callable or Text, func_name: Text, *...
FILE: seldom/utils/diff.py
class AssertInfo (line 8) | class AssertInfo:
function _all_values_are_same (line 16) | def _all_values_are_same(input_list) -> bool:
function _list_sorted (line 23) | def _list_sorted(data):
function diff_json (line 49) | def diff_json(response_data: Any, assert_data: Any, exclude: list = None...
FILE: seldom/utils/encrypt.py
class CipherMode (line 44) | class CipherMode(Enum):
function encrypt_handler (line 53) | def encrypt_handler(func):
class HashUtil (line 69) | class HashUtil:
method md5 (line 74) | def md5(text: str, encoding: str = 'utf-8') -> str:
method sha1 (line 80) | def sha1(text: str, encoding: str = 'utf-8') -> str:
method sha256 (line 86) | def sha256(text: str, encoding: str = 'utf-8') -> str:
method sha512 (line 92) | def sha512(text: str, encoding: str = 'utf-8') -> str:
method hmac_sha256 (line 98) | def hmac_sha256(key: str, text: str, encoding: str = 'utf-8') -> str:
class AESUtil (line 107) | class AESUtil:
method _pad_key (line 111) | def _pad_key(key: bytes) -> bytes:
method encrypt (line 123) | def encrypt(key: str, text: str, mode: CipherMode = CipherMode.CBC,
method decrypt (line 160) | def decrypt(key: str, encrypted_text: str, mode: CipherMode = CipherMo...
class DESUtil (line 201) | class DESUtil:
method _pad_key (line 205) | def _pad_key(key: bytes) -> bytes:
method encrypt (line 211) | def encrypt(key: str, text: str, mode: CipherMode = CipherMode.CBC,
method decrypt (line 248) | def decrypt(key: str, encrypted_text: str, mode: CipherMode = CipherMo...
class TripleDESUtil (line 289) | class TripleDESUtil:
method _pad_key (line 293) | def _pad_key(key: bytes) -> bytes:
method encrypt (line 299) | def encrypt(key: str, text: str, mode: CipherMode = CipherMode.CBC,
method decrypt (line 336) | def decrypt(key: str, encrypted_text: str, mode: CipherMode = CipherMo...
class RSAUtil (line 377) | class RSAUtil:
method __init__ (line 380) | def __init__(self, public_key: Optional[str] = None,
method generate_key_pair (line 394) | def generate_key_pair(bits: int = 2048) -> tuple[str, str]:
method encrypt (line 410) | def encrypt(self, text: str, encoding: str = 'utf-8') -> str:
method decrypt (line 429) | def decrypt(self, encrypted_text: str, encoding: str = 'utf-8') -> str:
class EncodeUtil (line 449) | class EncodeUtil:
method base64_encode (line 454) | def base64_encode(text: str, encoding: str = 'utf-8') -> str:
method base64_decode (line 460) | def base64_decode(text: str, encoding: str = 'utf-8') -> str:
method url_encode (line 466) | def url_encode(text: str) -> str:
method url_decode (line 472) | def url_decode(text: str) -> str:
method html_encode (line 478) | def html_encode(text: str) -> str:
method html_decode (line 484) | def html_decode(text: str) -> str:
method base16_encode (line 490) | def base16_encode(text: str, encoding: str = 'utf-8') -> str:
method base16_decode (line 496) | def base16_decode(text: str, encoding: str = 'utf-8') -> str:
method base32_encode (line 502) | def base32_encode(text: str, encoding: str = 'utf-8') -> str:
method base32_decode (line 508) | def base32_decode(text: str, encoding: str = 'utf-8') -> str:
method base85_encode (line 514) | def base85_encode(text: str, encoding: str = 'utf-8') -> str:
method base85_decode (line 520) | def base85_decode(text: str, encoding: str = 'utf-8') -> str:
class EncryptUtil (line 525) | class EncryptUtil:
method aes (line 533) | def aes() -> Type[AESUtil]:
method des (line 538) | def des() -> Type[DESUtil]:
method des3 (line 543) | def des3() -> Type[TripleDESUtil]:
method rsa (line 548) | def rsa(public_key: Optional[str] = None,
FILE: seldom/utils/file_extend.py
class FindFilePath (line 10) | class FindFilePath:
method __new__ (line 13) | def __new__(cls, name: str = None) -> str:
class File (line 36) | class File:
method _get_caller_path (line 40) | def _get_caller_path(min_stack_level: int = 2) -> str:
method path (line 53) | def path(self) -> str:
method dir (line 61) | def dir(self) -> str:
method parent_dir (line 70) | def parent_dir(self, level: int = 1) -> str:
method dir_dir (line 80) | def dir_dir(self) -> str:
method dir_dir_dir (line 90) | def dir_dir_dir(self) -> str:
method add_to_path (line 100) | def add_to_path(path: str = None) -> None:
method join (line 110) | def join(a, *paths):
method remove (line 117) | def remove(path) -> None:
FILE: seldom/utils/genson.py
function genson (line 9) | def genson(data: dict = None):
FILE: seldom/utils/jmespath.py
function jmespath (line 8) | def jmespath(data, expression, options=None):
FILE: seldom/utils/match_image.py
function save_screenshot (line 12) | def save_screenshot(page, file_path: str) -> None:
function compare_images (line 25) | def compare_images(img1_path: str, img2_path: str, tolerance: int = 0) -...
function assert_screenshot (line 53) | def assert_screenshot(page, tolerance: int = 0, stack_t=None) -> bool | ...
FILE: seldom/utils/resource_loader.py
function resource_file (line 10) | def resource_file(
function _load_cached_file (line 63) | def _load_cached_file(file_path: Path, is_json: bool) -> str | Dict[str,...
function _get_nested_value (line 76) | def _get_nested_value(data: Dict[str, Any], key_path: str) -> Any:
FILE: seldom/utils/send_extend.py
class SMTP (line 16) | class SMTP(XSMTP):
method sendmail (line 19) | def sendmail(self, to: str | list[str], subject: str = None, attachmen...
class DingTalk (line 41) | class DingTalk(XDingTalk):
class FeiShu (line 47) | class FeiShu(XFeiShu):
class Weinxin (line 53) | class Weinxin(XWeinxin):
class RunResult (line 59) | class RunResult(XRunResult):
FILE: seldom/utils/thread_lab.py
class ThreadWait (line 8) | class ThreadWait:
class SeldomThread (line 17) | class SeldomThread(Thread):
method __init__ (line 20) | def __init__(self, func, name='', *args, **kwargs):
method run (line 28) | def run(self):
method get_result (line 37) | def get_result(self):
method __init__ (line 42) | def __init__(self, func):
method __call__ (line 45) | def __call__(self, *args, **kwargs):
method get_all_result (line 52) | def get_all_result(cls):
FILE: seldom/utils/timer.py
function timer (line 5) | def timer(func):
FILE: seldom/webcommon/find_elems.py
class WebElement (line 13) | class WebElement:
method __init__ (line 16) | def __init__(self, browser, selector: str = None, **kwargs) -> None:
method find (line 34) | def find(self, index: int = None, empty: bool = False, highlight: bool...
method _highlight_element (line 67) | def _highlight_element(self, elem=None) -> None:
method info (line 89) | def info(self):
method warn (line 94) | def warn(self):
FILE: seldom/webcommon/keyboard.py
class KeysClass (line 9) | class KeysClass:
method __init__ (line 17) | def __init__(self, browser, selector: str = None, index: int = 0, **kw...
method input (line 22) | def input(self, text=""):
method enter (line 32) | def enter(self):
method select_all (line 41) | def select_all(self):
method cut (line 53) | def cut(self):
method copy (line 65) | def copy(self):
method paste (line 77) | def paste(self):
method backspace (line 89) | def backspace(self):
method delete (line 98) | def delete(self):
method tab (line 107) | def tab(self):
method space (line 114) | def space(self):
FILE: seldom/webcommon/selector.py
function selection_checker (line 6) | def selection_checker(selector: str) -> (str, str):
FILE: seldom/webdriver.py
class WebDriver (line 32) | class WebDriver:
method __init__ (line 39) | def __init__(self, browser_name: str = None, is_new: bool = False, ima...
method Keys (line 51) | def Keys(self, selector: str = None, index: int = 0, **kwargs) -> Keys...
class Alert (line 56) | class Alert:
method __init__ (line 61) | def __init__(self, browser):
method text (line 65) | def text(self) -> str:
method dismiss (line 72) | def dismiss(self) -> None:
method accept (line 79) | def accept(self):
method send_keys (line 89) | def send_keys(self, text: str) -> None:
method prompt_value (line 99) | def prompt_value(self, text: str):
method alert (line 109) | def alert(self) -> Alert:
method visit (line 114) | def visit(self, url: str) -> None:
method open_electron (line 128) | def open_electron(self, app_path: str, disable_gpu: bool = False, chro...
method open (line 145) | def open(self, url: str) -> None:
method page_source (line 155) | def page_source(self) -> str:
method execute_cdp_cmd (line 163) | def execute_cdp_cmd(self, cmd: str, cmd_args: dict):
method get_log (line 172) | def get_log(self, log_type: str):
method max_window (line 184) | def max_window(self) -> None:
method set_window (line 193) | def set_window(self, wide: int = 0, high: int = 0) -> None:
method get_windows (line 202) | def get_windows(self) -> dict:
method type (line 211) | def type(self, selector: str = None, text: str = "", clear: bool = Fal...
method type_enter (line 232) | def type_enter(self, selector: str = None, text: str = "", clear: bool...
method clear (line 248) | def clear(self, selector: str = None, index: int = 0, **kwargs) -> None:
method click (line 260) | def click(self, selector: str = None, index: int = 0, **kwargs) -> None:
method slow_click (line 273) | def slow_click(self, selector: str = None, index: int = 0, **kwargs) -...
method right_click (line 285) | def right_click(self, selector: str = None, index: int = 0, **kwargs) ...
method move_to_element (line 297) | def move_to_element(self, selector: str = None, index: int = 0, **kwar...
method click_and_hold (line 309) | def click_and_hold(self, selector: str = None, index: int = 0, **kwarg...
method drag_and_drop_by_offset (line 321) | def drag_and_drop_by_offset(self, selector: str = None, index: int = 0...
method double_click (line 337) | def double_click(self, selector: str = None, index: int = 0, **kwargs)...
method action_chains (line 349) | def action_chains(self) -> ActionChains:
method click_text (line 356) | def click_text(self, text: str, index: int = 0) -> None:
method close (line 368) | def close(self) -> None:
method submit (line 378) | def submit(self, selector: str = None, index: int = 0, **kwargs) -> None:
method refresh (line 390) | def refresh(self) -> None:
method execute_script (line 400) | def execute_script(self, script: str, *args):
method window_scroll (line 409) | def window_scroll(self, width: int = 0, height: int = 0) -> None:
method element_scroll (line 419) | def element_scroll(self, css: str, width: int = 0, height: int = 0) ->...
method get_attribute (line 431) | def get_attribute(self, selector: str = None, attribute=None, index: i...
method get_text (line 445) | def get_text(self, selector: str = None, index: int = 0, **kwargs) -> ...
method get_display (line 457) | def get_display(self, selector: str = None, index: int = 0, **kwargs) ...
method get_title (line 471) | def get_title(self) -> str:
method get_url (line 482) | def get_url(self) -> str:
method get_alert_text (line 493) | def get_alert_text(self) -> str:
method wait (line 504) | def wait(self, secs: int = 10) -> None:
method is_visible (line 514) | def is_visible(self, timeout: float = 5, **kwargs) -> bool:
method accept_alert (line 532) | def accept_alert(self) -> None:
method dismiss_alert (line 543) | def dismiss_alert(self) -> None:
method switch_to_frame (line 554) | def switch_to_frame(self, selector: str = None, index: int = 0, **kwar...
method switch_to_frame_parent (line 566) | def switch_to_frame_parent(self) -> None:
method switch_to_frame_out (line 577) | def switch_to_frame_out(self) -> None:
method switch_to_window (line 588) | def switch_to_window(self, window: int) -> None:
method switch_to_new_window (line 602) | def switch_to_new_window(self, type_hint=None) -> None:
method save_screenshot (line 615) | def save_screenshot(self, file_path: str = None, selector: str = None,...
method screenshots (line 640) | def screenshots(self, image=None) -> None:
method element_screenshot (line 663) | def element_screenshot(self, selector: str = None, index: int = 0, **k...
method select (line 685) | def select(self, selector: str = None, value: str = None, text: str = ...
method get_cookies (line 718) | def get_cookies(self) -> list:
method get_cookie (line 726) | def get_cookie(self, name: str) -> dict:
method add_cookie (line 734) | def add_cookie(self, cookie_dict: dict) -> None:
method add_cookies (line 745) | def add_cookies(self, cookie_list: list) -> None:
method delete_cookie (line 764) | def delete_cookie(self, name: str) -> None:
method delete_all_cookies (line 772) | def delete_all_cookies(self) -> None:
method check_element (line 780) | def check_element(self, css: str = None) -> None:
method get_elements (line 801) | def get_elements(self, selector: str = None, **kwargs):
method get_element (line 817) | def get_element(self, selector: str = None, index: int = 0, **kwargs):
method switch_to_app (line 830) | def switch_to_app(self) -> None:
method switch_to_web (line 840) | def switch_to_web(self, context=None) -> None:
method switch_to_flutter (line 860) | def switch_to_flutter(self) -> None:
FILE: seldom/webdriver_chaining.py
class Steps (line 22) | class Steps:
method __init__ (line 28) | def __init__(self, browser=None, url: str = None, desc: str = None, im...
method open (line 42) | def open(self, url: str = None):
method max_window (line 61) | def max_window(self):
method set_window (line 71) | def set_window(self, wide: int = 0, high: int = 0):
method find (line 81) | def find(self, selector: str, index: int = 0):
method find_text (line 90) | def find_text(self, text: str, index: int = 0):
method type (line 102) | def type(self, text):
method click (line 110) | def click(self):
method clear (line 118) | def clear(self):
method submit (line 128) | def submit(self):
method enter (line 138) | def enter(self):
method move_to_click (line 148) | def move_to_click(self):
method right_click (line 158) | def right_click(self):
method move_to_element (line 169) | def move_to_element(self):
method click_and_hold (line 180) | def click_and_hold(self):
method double_click (line 192) | def double_click(self):
method close (line 203) | def close(self):
method quit (line 213) | def quit(self):
method refresh (line 223) | def refresh(self):
method alert (line 234) | def alert(self):
method accept (line 244) | def accept(self):
method dismiss (line 255) | def dismiss(self):
method switch_to_frame (line 266) | def switch_to_frame(self):
method switch_to_frame_out (line 277) | def switch_to_frame_out(self):
method switch_to_window (line 289) | def switch_to_window(self, window: int):
method screenshots (line 304) | def screenshots(self, file_path: str = None):
method element_screenshot (line 321) | def element_screenshot(self, file_path: str = None):
method select (line 338) | def select(self, value: str = None, text: str = None, index: int = None):
method sleep (line 370) | def sleep(self, sec: [int, tuple] = 1):
FILE: seldom/websocket_client.py
class WebSocketClient (line 8) | class WebSocketClient(Thread):
method __init__ (line 13) | def __init__(self, url):
method run (line 20) | def run(self):
method send_message (line 49) | def send_message(self, message):
method stop (line 61) | def stop(self):
method on_open (line 70) | def on_open():
method on_error (line 78) | def on_error(error):
method on_close (line 86) | def on_close(self):
FILE: tests/test_adb.py
class ADBUtilsTest (line 7) | class ADBUtilsTest(unittest.TestCase):
method setUp (line 9) | def setUp(self):
method test_devices (line 12) | def test_devices(self):
method test_app_lunch_and_close (line 20) | def test_app_lunch_and_close(self):
method test_app_info (line 29) | def test_app_info(self):
FILE: tests/test_api_object.py
class LoginApiObject (line 6) | class LoginApiObject(HttpRequest):
method get_login_user (line 18) | def get_login_user(self):
class LoginTest (line 27) | class LoginTest(seldom.TestCase):
method test_user_login (line 29) | def test_user_login(self):
FILE: tests/test_autowing.py
class TestBingSearch (line 10) | class TestBingSearch(seldom.TestCase):
method start_class (line 13) | def start_class(cls):
method test_bing_search (line 20) | def test_bing_search(self):
FILE: tests/test_base_assert.py
class TestAssertions (line 4) | class TestAssertions(seldom.TestCase):
method test_assertEqual (line 5) | def test_assertEqual(self):
method test_assertTrue (line 14) | def test_assertTrue(self):
method test_assertIn (line 23) | def test_assertIn(self):
method test_assertIsInstance (line 32) | def test_assertIsInstance(self):
method test_assertRegex (line 41) | def test_assertRegex(self):
method test_assertAlmostEqual (line 50) | def test_assertAlmostEqual(self):
method test_assertGreater (line 59) | def test_assertGreater(self):
method test_assertLess (line 68) | def test_assertLess(self):
method test_assertCountEqual (line 77) | def test_assertCountEqual(self):
FILE: tests/test_benchmark.py
class MyTests (line 7) | class MyTests(seldom.TestCase):
method test_something_performance_1 (line 10) | def test_something_performance_1(self):
method test_something_performance_2 (line 18) | def test_something_performance_2(self):
method test_http_performance (line 26) | def test_http_performance(self):
FILE: tests/test_browser.py
class WebTestOne (line 4) | class WebTestOne(seldom.TestCase):
method start (line 7) | def start(self):
method end (line 10) | def end(self):
method test_baidu (line 13) | def test_baidu(self):
method test_bing (line 20) | def test_bing(self):
class WebTestTwo (line 28) | class WebTestTwo(seldom.TestCase):
method start_class (line 32) | def start_class(cls):
method end_class (line 36) | def end_class(cls):
method test_baidu (line 39) | def test_baidu(self):
method test_bing (line 46) | def test_bing(self):
FILE: tests/test_browser_new.py
class WebTestNew (line 4) | class WebTestNew(seldom.TestCase):
method test_new_browser (line 7) | def test_new_browser(self):
FILE: tests/test_cache/test_cache_thread.py
function operating_token (line 12) | def operating_token(tk: str):
FILE: tests/test_cache/test_memory_cache.py
function add (line 8) | def add(x, y):
class MyTest (line 14) | class MyTest(seldom.TestCase):
method test_case (line 16) | def test_case(self):
method test_case2 (line 21) | def test_case2(self):
method test_case3 (line 26) | def test_case3(self):
method test_case4 (line 31) | def test_case4(self):
FILE: tests/test_db/test_db_mssql.py
class MSSQLTest (line 12) | class MSSQLTest(unittest.TestCase):
method setUp (line 15) | def setUp(self) -> None:
method tearDown (line 20) | def tearDown(self) -> None:
method test_query_sql (line 23) | def test_query_sql(self):
method test_query_one (line 28) | def test_query_one(self):
method test_execute_sql (line 33) | def test_execute_sql(self):
method test_select_sql (line 42) | def test_select_sql(self):
method test_delete_sql (line 49) | def test_delete_sql(self):
method test_update_sql (line 55) | def test_update_sql(self):
method test_insert_sql (line 61) | def test_insert_sql(self):
method test_init_table (line 68) | def test_init_table(self):
FILE: tests/test_db/test_db_mysql.py
class MySQLTest (line 11) | class MySQLTest(unittest.TestCase):
method setUp (line 14) | def setUp(self) -> None:
method tearDown (line 19) | def tearDown(self) -> None:
method test_query_sql (line 22) | def test_query_sql(self):
method test_query_one (line 27) | def test_query_one(self):
method test_execute_sql (line 32) | def test_execute_sql(self):
method test_select_sql (line 41) | def test_select_sql(self):
method test_delete_sql (line 48) | def test_delete_sql(self):
method test_update_sql (line 55) | def test_update_sql(self):
method test_insert_sql (line 61) | def test_insert_sql(self):
method test_init_table (line 68) | def test_init_table(self):
FILE: tests/test_db/test_db_postgresdb.py
class PostgresDBTest (line 12) | class PostgresDBTest(unittest.TestCase):
method setUp (line 13) | def setUp(self) -> None:
method tearDown (line 21) | def tearDown(self) -> None:
method test_query_sql (line 24) | def test_query_sql(self):
method test_query_one (line 29) | def test_query_one(self):
method test_execute_sql (line 34) | def test_execute_sql(self):
method test_select_sql (line 43) | def test_select_sql(self):
method test_delete_sql (line 50) | def test_delete_sql(self):
method test_update_sql (line 57) | def test_update_sql(self):
method test_insert_sql (line 63) | def test_insert_sql(self):
FILE: tests/test_db/test_db_sqlite3.py
class SQLite3Test (line 12) | class SQLite3Test(unittest.TestCase):
method setUp (line 15) | def setUp(self) -> None:
method tearDown (line 21) | def tearDown(self) -> None:
method test_query_sql (line 24) | def test_query_sql(self):
method test_query_one (line 29) | def test_query_one(self):
method test_execute_sql (line 34) | def test_execute_sql(self):
method test_select_sql (line 43) | def test_select_sql(self):
method test_delete_sql (line 50) | def test_delete_sql(self):
method test_update_sql (line 57) | def test_update_sql(self):
method test_insert_sql (line 63) | def test_insert_sql(self):
method test_init_table (line 70) | def test_init_table(self):
FILE: tests/test_dependent_func.py
class DependentTest (line 6) | class DependentTest(seldom.TestCase):
method user_login (line 9) | def user_login(username, password):
method test_case (line 16) | def test_case(self):
FILE: tests/test_encrypt.py
class TestHashUtil (line 16) | class TestHashUtil(unittest.TestCase):
method test_md5 (line 19) | def test_md5(self):
method test_sha1 (line 24) | def test_sha1(self):
method test_sha256 (line 29) | def test_sha256(self):
method test_sha512 (line 34) | def test_sha512(self):
method test_hmac_sha256 (line 40) | def test_hmac_sha256(self):
class TestAESUtil (line 47) | class TestAESUtil(unittest.TestCase):
method test_encrypt_decrypt_cbc (line 50) | def test_encrypt_decrypt_cbc(self):
method test_encrypt_decrypt_ecb (line 57) | def test_encrypt_decrypt_ecb(self):
class TestDESUtil (line 65) | class TestDESUtil(unittest.TestCase):
method test_encrypt_decrypt_cbc (line 68) | def test_encrypt_decrypt_cbc(self):
method test_encrypt_decrypt_ecb (line 75) | def test_encrypt_decrypt_ecb(self):
class TestTripleDESUtil (line 83) | class TestTripleDESUtil(unittest.TestCase):
method test_encrypt_decrypt_cbc (line 86) | def test_encrypt_decrypt_cbc(self):
method test_encrypt_decrypt_ecb (line 93) | def test_encrypt_decrypt_ecb(self):
class TestRSAUtil (line 101) | class TestRSAUtil(unittest.TestCase):
method setUp (line 104) | def setUp(self):
method test_encrypt_decrypt (line 107) | def test_encrypt_decrypt(self):
class TestEncodeUtil (line 115) | class TestEncodeUtil(unittest.TestCase):
method test_base64_encode_decode (line 118) | def test_base64_encode_decode(self):
method test_url_encode_decode (line 124) | def test_url_encode_decode(self):
method test_html_encode_decode (line 130) | def test_html_encode_decode(self):
class TestEncryptUtil (line 137) | class TestEncryptUtil(unittest.TestCase):
method test_hash_util (line 140) | def test_hash_util(self):
method test_encode_util (line 143) | def test_encode_util(self):
method test_aes_util (line 146) | def test_aes_util(self):
method test_des_util (line 149) | def test_des_util(self):
method test_des3_util (line 152) | def test_des3_util(self):
method test_rsa_util (line 155) | def test_rsa_util(self):
FILE: tests/test_fixture.py
class TestCase (line 4) | class TestCase(seldom.TestCase):
method start_class (line 7) | def start_class(cls):
method end_class (line 11) | def end_class(cls):
method start (line 14) | def start(self):
method end (line 17) | def end(self):
method test_case_one (line 20) | def test_case_one(self):
method test_case_two (line 23) | def test_case_two(self):
FILE: tests/test_graphql.py
class TestGraphQL (line 6) | class TestGraphQL(TestCase):
method test_graphql_query (line 8) | def test_graphql_query(self):
FILE: tests/test_http_assert.py
class TestHttpAssert (line 5) | class TestHttpAssert(seldom.TestCase):
method test_assert_json (line 7) | def test_assert_json(self):
method test_assert_path (line 19) | def test_assert_path(self):
method test_assert_schema (line 29) | def test_assert_schema(self):
FILE: tests/test_jsonpath.py
class JSONPathTest (line 26) | class JSONPathTest(unittest.TestCase):
method test_case (line 28) | def test_case(self):
method test_case1 (line 32) | def test_case1(self):
method test_case2 (line 36) | def test_case2(self):
method test_case3 (line 40) | def test_case3(self):
method test_case4 (line 45) | def test_case4(self):
FILE: tests/test_locators.py
class TestForm (line 8) | class TestForm(seldom.TestCase):
method start (line 10) | def start(self):
method test_locator (line 14) | def test_locator(self):
method test_selector (line 26) | def test_selector(self):
method test_step (line 38) | def test_step(self):
FILE: tests/test_log.py
class TestCase (line 7) | class TestCase(seldom.TestCase):
method test_case (line 8) | def test_case(self):
method test_case2 (line 14) | def test_case2(self):
method test_ddt (line 23) | def test_ddt(self, _, keyword):
method test_failed (line 28) | def test_failed(self):
method test_error (line 32) | def test_error(self):
FILE: tests/test_other_lib/test_playwright.py
class Playwright (line 10) | class Playwright(seldom.TestCase):
method start (line 12) | def start(self):
method end (line 17) | def end(self):
method test_playwright_start (line 21) | def test_playwright_start(self):
method test_playwright_todo (line 37) | def test_playwright_todo(self):
FILE: tests/test_other_lib/test_pyautogui.py
class TestPyAutoGUINote (line 7) | class TestPyAutoGUINote(seldom.TestCase):
method start (line 9) | def start(self):
method end (line 14) | def end(self):
method test_write_and_save (line 18) | def test_write_and_save(self):
FILE: tests/test_other_lib/test_uiautomator.py
class MyAppTest (line 5) | class MyAppTest(seldom.TestCase):
method start (line 7) | def start(self):
method end (line 13) | def end(self):
method test_app (line 17) | def test_app(self, user):
FILE: tests/test_playwright_sample.py
class Playwright (line 11) | class Playwright(seldom.TestCase):
method start (line 13) | def start(self):
method end (line 18) | def end(self):
method test_start (line 21) | def test_start(self):
FILE: tests/test_random/test_testdata.py
class TestRandomData (line 10) | class TestRandomData(seldom.TestCase):
method test_print_data (line 12) | def test_print_data(self):
FILE: tests/test_request_extend.py
class TestSaveResp (line 4) | class TestSaveResp(seldom.TestCase):
method test_save_response (line 6) | def test_save_response(self):
class TestReqIP (line 12) | class TestReqIP(seldom.TestCase):
method test_get_ip_address (line 14) | def test_get_ip_address(self):
FILE: tests/test_skip.py
class SkipTest (line 5) | class SkipTest(seldom.TestCase):
method test_case (line 7) | def test_case(self):
class YouTest (line 11) | class YouTest(seldom.TestCase):
method test_skip_case (line 14) | def test_skip_case(self):
method test_if_skip (line 17) | def test_if_skip(self):
FILE: tests/test_steps_chaining.py
class WebTestChaining (line 5) | class WebTestChaining(seldom.TestCase):
method test_baidu (line 8) | def test_baidu(self):
method test_bing (line 13) | def test_bing(self):
FILE: tests/test_steps_chaining_browser.py
class WebTestChaining (line 5) | class WebTestChaining(seldom.TestCase):
method start (line 8) | def start(self):
method end (line 11) | def end(self):
method test_baidu (line 14) | def test_baidu(self):
method test_bing (line 19) | def test_bing(self):
FILE: tests/test_thread/test_thread.py
function slow_event (line 13) | def slow_event(case_name, s):
class MyTest (line 25) | class MyTest(seldom.TestCase):
method start_class (line 28) | def start_class(cls):
method end_class (line 32) | def end_class(self):
method test_case_success (line 40) | def test_case_success(self):
method test_case_fail (line 46) | def test_case_fail(self):
method test_ddt (line 57) | def test_ddt(self, _, name, sec, ret):
FILE: tests/test_thread/test_thread_browser.py
class BingTest (line 6) | class BingTest(seldom.TestCase):
method test_case (line 9) | def test_case(self):
function run_case (line 19) | def run_case(browser):
FILE: tests/test_thread/test_thread_case.py
class MyTest (line 6) | class MyTest(seldom.TestCase):
method test_baidu (line 8) | def test_baidu(self):
method test_bing (line 12) | def test_bing(self):
function run_case (line 20) | def run_case(case: str):
FILE: tests/test_thread/test_thread_path.py
function run_case (line 6) | def run_case(path: str):
FILE: tests/test_utils/test_file.py
class TestFilePathUtils (line 6) | class TestFilePathUtils(unittest.TestCase):
method test_path_is_string_and_correct (line 8) | def test_path_is_string_and_correct(self):
method test_dir_is_string_and_correct (line 14) | def test_dir_is_string_and_correct(self):
method test_dir_dir_is_string_and_correct (line 20) | def test_dir_dir_is_string_and_correct(self):
method test_dir_dir_dir_is_string_and_correct (line 26) | def test_dir_dir_dir_is_string_and_correct(self):
method test_parent_dir_is_string_and_correct (line 32) | def test_parent_dir_is_string_and_correct(self):
FILE: tests/test_websocket/test_websocket.py
class WebSocketTest (line 6) | class WebSocketTest(seldom.TestCase):
method start (line 8) | def start(self):
method end (line 15) | def end(self):
method test_send_and_receive_message (line 22) | def test_send_and_receive_message(self):
FILE: tests/test_websocket/webscoket_server.py
function websocket_handler (line 5) | async def websocket_handler(request):
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (811K chars).
[
{
"path": ".gitignore",
"chars": 65,
"preview": ".idea\n*.pyc\n*.log\nreport\nbuild\ndist\nseldom.egg-info\nlog/\n.history"
},
{
"path": "CHANGES.md",
"chars": 18898,
"preview": "### 3.14.2\n\n* 合并提交:\n * `send_extend.py`: 规范参数多类型,避免警告提示。\n * 优化`loadert('start run')`和`loader(\"end run\")` 的调用。\n* 升级"
},
{
"path": "LICENSE",
"chars": 11364,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "MANIFEST.in",
"chars": 44,
"preview": "recursive-include seldom/running/html *.html"
},
{
"path": "README.md",
"chars": 11289,
"preview": "[GitHub](https://github.com/SeldomQA/seldom) | [Gitee](https://gitee.com/fnngj/seldom) |\n\n\n"
},
{
"path": "api_case/confrun.py",
"chars": 940,
"preview": "\"\"\"\nseldom confrun.py hooks function\n\"\"\"\n\n\ndef base_url():\n \"\"\"\n http test\n api base url\n \"\"\"\n return \"ht"
},
{
"path": "demo/README.md",
"chars": 4139,
"preview": "## seldom demo\n\n通过 demo 帮助你快速了解seldom的使用。\n\n### 准备工作\n\n* 目录树:\n\n```shell\n./demo\n├── README.md\n├── __init__.py\n├── confrun.p"
},
{
"path": "demo/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo/confrun.py",
"chars": 1076,
"preview": "\"\"\"\nseldom confrun.py hooks function\n\"\"\"\n\n\ndef start_run():\n \"\"\"\n Test the hook function before running\n \"\"\"\n "
},
{
"path": "demo/reports/.keep",
"chars": 0,
"preview": ""
},
{
"path": "demo/run.py",
"chars": 1479,
"preview": "import seldom\n\n\"\"\"\n说明:\npath: 指定测试目录。\nbrowser: Web测试,指定浏览器,默认chrome - web专用\nbase_url: Http测试,指定接口地址。 - api专用\napp_in"
},
{
"path": "demo/test_data/csv_data.csv",
"chars": 49,
"preview": "firstname,lastname\nForest,Hobbs\nFerdinand,Lozano\n"
},
{
"path": "demo/test_data/json_data.json",
"chars": 275,
"preview": "{\n \"name\": [\n [\"Wayne\", \"Burch\"],\n [\"Jamie-louise\", \"Wong\"]\n ],\n \"login\":[\n {\n \"scene\": \"tom用户登录\",\n "
},
{
"path": "demo/test_data/yaml_data.yaml",
"chars": 131,
"preview": "---\nname:\n- - Elnora\n - West\n- - Leon\n - Richard\nlogin:\n- username: Tom\n password: tom123\n- username: Jerry\n passwor"
},
{
"path": "demo/test_dir/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo/test_dir/api_case/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo/test_dir/api_case/test_http_demo.py",
"chars": 3546,
"preview": "import seldom\nfrom seldom import data\n\n\nclass TestRequest(seldom.TestCase):\n \"\"\"\n http api test demo\n doc: http"
},
{
"path": "demo/test_dir/app_case/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo/test_dir/app_case/test_first_demo.py",
"chars": 1169,
"preview": "from appium.options.android import UiAutomator2Options\n\nimport seldom\nfrom seldom.appium_lab.keyboard import KeyEvent\n\n\n"
},
{
"path": "demo/test_dir/app_case/test_po_demo.py",
"chars": 1454,
"preview": "from poium import Page, Element\n\nimport seldom\nfrom seldom.appium_lab.keyboard import KeyEvent\nfrom seldom.appium_lab.an"
},
{
"path": "demo/test_dir/app_case/test_u2_demo.py",
"chars": 792,
"preview": "\"\"\"\n需要安装 uiautomator2: https://github.com/openatx/uiautomator2\n> pip install uiautomator2\n\"\"\"\nimport seldom\nimport uiaut"
},
{
"path": "demo/test_dir/web_case/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo/test_dir/web_case/test_data_demo.py",
"chars": 555,
"preview": "import seldom\nfrom seldom import testdata\n\n\nclass RandomDataTest(seldom.TestCase):\n \"\"\"\n Randomly generate test da"
},
{
"path": "demo/test_dir/web_case/test_ddt_demo.py",
"chars": 4159,
"preview": "import seldom\nfrom seldom import data, file_data\n\n\nclass BingTest(seldom.TestCase):\n \"\"\"Bing search test case\"\"\"\n\n "
},
{
"path": "demo/test_dir/web_case/test_first_demo.py",
"chars": 585,
"preview": "import seldom\nfrom seldom import Steps\n\n\nclass BingTest(seldom.TestCase):\n \"\"\"Bing search test case\"\"\"\n\n def test_"
},
{
"path": "demo/test_dir/web_case/test_fixture_demo.py",
"chars": 807,
"preview": "import seldom\n\n\nclass BaiduTest(seldom.TestCase):\n \"\"\"\n * start_class/end_class\n * start/end\n \"\"\"\n\n @clas"
},
{
"path": "demo/test_dir/web_case/test_playwright_demo.py",
"chars": 911,
"preview": "\"\"\"\n需要安装 playwright: https://playwright.dev/\n> pip install playwright\n\"\"\"\nimport seldom\nfrom playwright.sync_api import "
},
{
"path": "demo/test_dir/web_case/test_po_demo.py",
"chars": 1140,
"preview": "\"\"\"\npage object model\nUsing the poium Library\nhttps://github.com/SeldomQA/poium\n```\n> pip install poium>=1.6.1\n```\n\"\"\"\ni"
},
{
"path": "description.rst",
"chars": 201,
"preview": "seldom\n---------------\n\nSeldom is an automation testing framework based on unittest.\n\nInstallation\n------------\n\n $ p"
},
{
"path": "docs/.gitignore",
"chars": 57,
"preview": "node_modules\n.temp\n.cache\n.DS_Store\nvpdocs/.vuepress/dist"
},
{
"path": "docs/README.md",
"chars": 822,
"preview": "## ☘️Introduction\n\n此目录用于**存放 & 编辑** seldom 相关文档\n\n## 📖 Document\n\n[中文文档](https://seldomqa.github.io/)\n\n[English document(r"
},
{
"path": "docs/compare.md",
"chars": 2881,
"preview": "## seldom vs pytest\n\n| 功能 | seldom | pytest "
},
{
"path": "docs/deploy.sh",
"chars": 237,
"preview": "# 确保脚本抛出遇到的错误\nset -e\n\n# 生成静态文件\nnpm run build\n\n# 进入生成的文件夹\ncd vpdocs/.vuepress/dist\n\ngit init\ngit add -A\ngit commit -m 'de"
},
{
"path": "docs/package.json",
"chars": 497,
"preview": "{\n \"name\": \"vuepress-docs\",\n \"version\": \"1.0.0\",\n \"description\": \"docs by vuepress\",\n \"main\": \"index.js\",\n \"author\""
},
{
"path": "docs/vpdocs/.vuepress/config.js",
"chars": 2271,
"preview": "import { defineUserConfig } from 'vuepress'\nimport { viteBundler } from '@vuepress/bundler-vite'\nimport { defaultTheme }"
},
{
"path": "docs/vpdocs/README.md",
"chars": 563,
"preview": "---\nhome: true\nheroText: Seldom\nheroImage: /image/book.jpg\nactions:\n - text: 快速上手→\n link: /getting-started/quick_sta"
},
{
"path": "docs/vpdocs/api-testing/api_case.md",
"chars": 5166,
"preview": "# 支持Excel测试用例\n\n> seldom > 3.8.0\n\n在编写接口测试用例的时候,有时候测试用例非常简单,比如单接口的测试,不需要登录token,不存在用例数据依赖,也不需要参数加密,此时,使用`Excel`\n文件编写用例更为高效"
},
{
"path": "docs/vpdocs/api-testing/api_object.md",
"chars": 1784,
"preview": "# API Object\n\nAPI Object Models,简称AOM,AOM是一种设计模式,它围绕着将API、路由或功能交互及其相关行为封装在结构良好的对象中。AOM旨在增强API测试和集成的直观性和弹性。在实践中,AOM需要精心设计"
},
{
"path": "docs/vpdocs/api-testing/assert.md",
"chars": 3113,
"preview": "# 接口断言\n\n断言接口返回的数据是HTTP接口自动化测试非常重要的工作,提供强大的断言方法可以提高用例的编写效率。\n\n## assertJSON\n\n`assertJSON()` 断言接口返回的某部分数据。\n\n* 请求参数\n\n```json"
},
{
"path": "docs/vpdocs/api-testing/more.md",
"chars": 22024,
"preview": "# 更多功能\n\n### har to case\n\n对于不熟悉 Requests 库的人来说,通过Seldom来写接口测试用例还是会有一点难度。于是,seldom 提供了`har` 文件转 `case` 的命令。\n\n首先,打开fiddler "
},
{
"path": "docs/vpdocs/api-testing/start.md",
"chars": 3013,
"preview": "# HTTP测试\n\n## 优势\n\nseldom 非常适合个人接口自动化项目,它有以下优势。\n\n* 可以写更少的代码\n* 提供详细的运行日志\n* 提供专门为接口设计的断言\n* 强大的数据驱动\n* 自动生成HTML/XML测试报告\n* 支持生成"
},
{
"path": "docs/vpdocs/api-testing/webscocket.md",
"chars": 3093,
"preview": "# WebSocket\n\n> seldom > 3.6.0 支持该功能\n\n有些时间我们需要通过`WebSocket`实现长连接,很高兴的告诉告诉你seldom支持`WebSocket`测试了。\n\n### WebSocket 生命周期\n\nWe"
},
{
"path": "docs/vpdocs/app-testing/adb_lib.md",
"chars": 1549,
"preview": "# ADB 操作\n\nApp(Android)测试必然需要用到adb命令, seldom根据需要封装了几个常用的操作。\n\n* 获取设备信息\n\n```python\nfrom seldom.utils.adbutils import ADBUti"
},
{
"path": "docs/vpdocs/app-testing/appium_lab.md",
"chars": 6701,
"preview": "# appium API\n\nappium API继承 selenium API,所以,操作方法是通用的。在seldom 中,请参考web UI 中的seldom API。\n\n## appium 定位\n\n* 支持定位类型\n\nseldom 支持"
},
{
"path": "docs/vpdocs/app-testing/extensions.md",
"chars": 4630,
"preview": "# appium 扩展\n\nappium支持扩展,通过扩展来增强appium定位元素的能力。\n\n## appium images-plugin\n\n使用此插件支持的`-image`定位器策略,可以通过Appium指定想要定位的元素的图片文件。\n"
},
{
"path": "docs/vpdocs/app-testing/page_object.md",
"chars": 2025,
"preview": "# Page Object\n\n在编写App自动化测试时,推荐使用`page object models`(简称 PO设计模式)。你可以看到seldom并没有完全封装appium的API,我们可以借助\npoium 来实现基于元素的定位。\n\ng"
},
{
"path": "docs/vpdocs/app-testing/start.md",
"chars": 3112,
"preview": "# app 测试\n\n`seldom 3.0` 基于appium支持APP测试。\n\nappium 官方网站:https://appium.io/\n\n## 环境安装\n\napp 的自动化测试环境相比较 web 要复杂一些,请参考appium官方。"
},
{
"path": "docs/vpdocs/develop.md",
"chars": 949,
"preview": "## ☘️Introduction\n\n基于 vuepress2.0+ 的 **seldom [操作文档](https://seldomqa.github.io/)**\n\n你可以使用 Markdown 书写文档,并通过 VuePress 部署"
},
{
"path": "docs/vpdocs/getting-started/advanced.md",
"chars": 15347,
"preview": "# 高级用法\n\n### fixture\n\n有时自动化测试用例的运行需要一些前置&后置步骤,seldom提供了相应的方法。\n\nseldom重写了unittest的`fixture`,所以,请使用seldom的`fixture`,对应表格。\n\n"
},
{
"path": "docs/vpdocs/getting-started/create_project.md",
"chars": 3067,
"preview": "# 创建项目\n\nseldom已经安装完成,那么现在已经迫不及待的想体验seldom的使用。\n\n### 自动生成项目\n\nseldom 通过`seldom`命令提供了脚手架,可以快速的帮我们创建自动化测试项目。\n\n1. 查看帮助:\n\n```sh"
},
{
"path": "docs/vpdocs/getting-started/data_driver.md",
"chars": 10029,
"preview": "# 数据驱动\n\n数据驱动是测试框架非常重要的功能之一,它可以有效的节约大量重复的测试代码。seldom针对该功能做强大的支持。\n\n### @class_data() 方法\n\n`class_data()` 装饰测试类,测试类下面的任何方法可以"
},
{
"path": "docs/vpdocs/getting-started/dependent_func.md",
"chars": 6374,
"preview": "# 方法的依赖\n\n> 在 seldom 3.4.0 版本实现了该功能。\n\n在复杂的测试场景中,常常会存在用例依赖,以一个接口自动化平台为例,依赖关系:\n\n`创建用例` --> `创建模块` --> `创建项目` --> `登录`。\n\n__用"
},
{
"path": "docs/vpdocs/getting-started/installation.md",
"chars": 1075,
"preview": "# Installation\n\nseldom的安装非常简单。\n\n* 快速安装\n\n目前已经上传 pypi.org ,可以使用pip命令安装。\n\n```shell\n> pip install seldom\n```\n\n* 体验最新代码\n\n如果你想"
},
{
"path": "docs/vpdocs/getting-started/quick_start.md",
"chars": 13275,
"preview": "# 快速开始\n\n### 基本规范\n\n`seldom`继承`unittest`单元测试框架,所以他的编写规范与[unittest](https://docs.python.org/3/library/unittest.html)基本保持一致。"
},
{
"path": "docs/vpdocs/getting-started/seldom_cli.md",
"chars": 8158,
"preview": "# seldom CLI\n\n`seldom 2.10.7` 对命令行工具做了增强,可以使用命令行的方式运行用例。\n\n## seldom 帮助\n\n* `seldom --help` 查看帮助使用\n\n```shell\n> seldom --he"
},
{
"path": "docs/vpdocs/introduce.md",
"chars": 5762,
"preview": "# 介绍\n\n## 新书推荐\n\n<p>\n <a href=\"https://item.jd.com/10124939676219.html\">\n <img alt=\"京东链接\" src=\"/image/book.jpg\" style="
},
{
"path": "docs/vpdocs/more-ability/benchmark.md",
"chars": 3081,
"preview": "# 基准测试\n\n基准测试属于性能测试的一种,用于评估和衡量软件的性能指标。我们可以在软件开发的某个阶段通过基准测试建立一个已知的性能水平,称为\"\n基准线\"。当系统的软硬件环境发生变化之后再进行一次基准测试以确定那些变化对性能的影响。__这是"
},
{
"path": "docs/vpdocs/more-ability/db_operation.md",
"chars": 4794,
"preview": "# 数据库操作\n\nseldom 支持sqlite3、MySQL、SQL Server、MongoDB、PostgreSQL等数据库操作。\n\n| sqlite3 | MySQL | SQ"
},
{
"path": "docs/vpdocs/more-ability/test_library.md",
"chars": 5870,
"preview": "# 支持更多测试库\n\nseldom 集成了`selenium`、`appium`、`requests`,他们都是非常优秀且成熟的库,这并不是说,你不能在seldom使用其他的测试库。\n\nseldom 作为一个测试框架,理论上可以与任何测试库"
},
{
"path": "docs/vpdocs/platform/platform.md",
"chars": 5261,
"preview": "# 平台化支持\n\n为了更好的支持测试用例平台化,Seldom 提供了API用于获取用例列表,以及根据传入的用例信息运行测试用例。\n\n## 接入平台方式\n\nseldom-platform项目: https://github.com/Seldo"
},
{
"path": "docs/vpdocs/version/CHANGES.md",
"chars": 19431,
"preview": "# 版本更新\n\n### seldom 3.x <Badge type=\"tip\" text=\"v3\" vertical=\"top\" />\n\n__3.13.0(2025-03-16)__\n\n* 功能:unittest所有基础断言增加日志, 例"
},
{
"path": "docs/vpdocs/web-testing/browser_driver.md",
"chars": 3907,
"preview": "# 浏览器与驱动\n\n### 管理浏览器驱动\n\n> seldom 2.3.0 版本集成webdriver_manager管理浏览器驱动。\n>\n> seldom 3.3.0 版本移除了webdriver_manager,selenium 4.6"
},
{
"path": "docs/vpdocs/web-testing/chaining.md",
"chars": 3808,
"preview": "# 链式调用\n\n方法链接是一种技术,用于对同一个对象进行多个方法调用,只使用一次对象引用。\n\n### 基本例子\n\n先来看一下如何通过seldom使用链式调用编写Web测试用例。\n\n```python\nimport seldom\nfrom s"
},
{
"path": "docs/vpdocs/web-testing/other.md",
"chars": 5195,
"preview": "# 浏览器启动配置\n\nselenium 在启动浏览器的时候可以做很多配置,seldom 试图简化这些配置,但是总有很多情况兼顾不到。\n\n> `seldom 3.2` 版本开放了这些配置,你只需要将配置传给 seldom 即可。\n\n### 使"
},
{
"path": "docs/vpdocs/web-testing/page_object.md",
"chars": 814,
"preview": "# Page Object\n\nseldom API 的设计理念是将元素操作和元素定位放到起,本身不太适合实现`Page object`设计模式。\n\n[poium](https://github.com/SeldomQA/poium) 是`P"
},
{
"path": "docs/vpdocs/web-testing/seldom_api.md",
"chars": 11131,
"preview": "# Seldom API\n\n### 查找元素\n\nseldom 提供了8中定位方式,与Selenium保持一致。\n\n* id_\n* name\n* class_name\n* tag\n* link_text\n* partial_link_text"
},
{
"path": "pyproject.toml",
"chars": 1633,
"preview": "[project]\nname = \"seldom\"\nversion = \"3.14.2\"\ndescription = \"Seldom automation testing framework based on unittest.\"\nauth"
},
{
"path": "requirements.txt",
"chars": 211,
"preview": "Appium-Python-Client>=5.2.0\nXTestRunner>=1.8.6\nloguru~=0.7.0\nopenpyxl>=3.0.3\npyyaml>=6.0\njsonschema>=4.10.0\njmespath>=0."
},
{
"path": "seldom/__init__.py",
"chars": 1271,
"preview": "#!/usr/bin/python\n#\n# Licensed to the Software Freedom Conservancy (SFC) under one\n# or more contributor license agreeme"
},
{
"path": "seldom/appdriver.py",
"chars": 6307,
"preview": "\"\"\"\nappium API\n\"\"\"\nimport base64\nfrom pathlib import Path\nfrom typing import Any, Dict\nfrom appium.webdriver.common.appi"
},
{
"path": "seldom/appium_lab/__init__.py",
"chars": 1595,
"preview": "\"\"\"\nappium laboratory\n\"\"\"\nfrom seldom.logging import log\nfrom seldom.appium_lab.action import Action\nfrom seldom.appium_"
},
{
"path": "seldom/appium_lab/action.py",
"chars": 5746,
"preview": "\"\"\"\nappium action\n\"\"\"\n\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom selenium.webdriver.common.a"
},
{
"path": "seldom/appium_lab/android.py",
"chars": 1083,
"preview": "from appium.options.android import UiAutomator2Options\nfrom appium.options.android import EspressoOptions\nfrom appium.op"
},
{
"path": "seldom/appium_lab/appium_service.py",
"chars": 1817,
"preview": "import time\nfrom seldom.logging import log\nfrom seldom.utils import file\nfrom appium.webdriver.appium_service import App"
},
{
"path": "seldom/appium_lab/find.py",
"chars": 7036,
"preview": "\"\"\"\nfind element by text\n\"\"\"\n\nfrom appium.webdriver.common.appiumby import AppiumBy\n\nfrom seldom.appium_lab.switch impor"
},
{
"path": "seldom/appium_lab/keyboard.py",
"chars": 3383,
"preview": "\"\"\"\nApp keyboard operation\n\"\"\"\nfrom seldom.logging import log\n\nkeycodes = {\n '0': 7,\n '1': 8,\n '2': 9,\n '3':"
},
{
"path": "seldom/appium_lab/ocr_plugin.py",
"chars": 409,
"preview": "\"\"\"\nAppium OCR plugin\nhelp: https://github.com/jlipps/appium-ocr-plugin\n\"\"\"\nfrom appium.webdriver.webdriver import Exten"
},
{
"path": "seldom/appium_lab/switch.py",
"chars": 2149,
"preview": "\"\"\"\nswitch app context\n\"\"\"\nimport time\n\nfrom seldom.logging import log\nfrom seldom.running.config import Seldom\n\n\nclass "
},
{
"path": "seldom/case.py",
"chars": 15287,
"preview": "\"\"\"\nseldom test case\n\"\"\"\nimport pdb\nimport random\nimport inspect\nimport unittest\nfrom time import sleep\nfrom urllib.pars"
},
{
"path": "seldom/cli.py",
"chars": 14122,
"preview": "\"\"\"\nseldom CLI\n\"\"\"\nimport json\nimport os\nimport sys\nfrom pathlib import Path\n\nimport typer\n\n# Import only the absolute m"
},
{
"path": "seldom/db_operation/__init__.py",
"chars": 62,
"preview": "from .sqlite_db import SQLiteDB\nfrom .mysql_db import MySQLDB\n"
},
{
"path": "seldom/db_operation/base_db.py",
"chars": 1635,
"preview": "\"\"\"\nSQL API\n\"\"\"\n\n\nclass SQLBase:\n \"\"\"SQL base API\"\"\"\n\n @staticmethod\n def dict_to_str(data: dict) -> str:\n "
},
{
"path": "seldom/db_operation/mongo_db.py",
"chars": 688,
"preview": "\"\"\"\nMongo DB API\n\"\"\"\ntry:\n from pymongo import MongoClient\nexcept ModuleNotFoundError as e:\n raise ModuleNotFoundE"
},
{
"path": "seldom/db_operation/mssql_db.py",
"chars": 3951,
"preview": "\"\"\"\nMS SQL Server DB API\n\"\"\"\nfrom typing import Any\ntry:\n import pymssql\nexcept ModuleNotFoundError as e:\n raise M"
},
{
"path": "seldom/db_operation/mysql_db.py",
"chars": 4192,
"preview": "\"\"\"\nMySQL DB API\n\"\"\"\nfrom typing import Any\nimport pymysql.cursors\nfrom seldom.db_operation.base_db import SQLBase\n\n\ncla"
},
{
"path": "seldom/db_operation/postgres_db.py",
"chars": 3769,
"preview": "from typing import Any\ntry:\n import psycopg2\n import psycopg2.extras\nexcept ModuleNotFoundError as e:\n raise Mo"
},
{
"path": "seldom/db_operation/sqlite_db.py",
"chars": 3092,
"preview": "\"\"\"\nSQLite3 DB API\n\"\"\"\nfrom typing import Any\nimport sqlite3\nfrom seldom.db_operation.base_db import SQLBase\n\n\nclass SQL"
},
{
"path": "seldom/driver.py",
"chars": 4232,
"preview": "\"\"\"\nbrowser driver\n\"\"\"\n\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.service import Service as ChromeSe"
},
{
"path": "seldom/extend_lib/__init__.py",
"chars": 263,
"preview": "\"\"\"\nIn order to reduce dependencies,\n some simple third-party libraries are directly migrated over.\n\"\"\"\nfrom .jsonpath i"
},
{
"path": "seldom/extend_lib/base_assert.py",
"chars": 564,
"preview": "from functools import wraps\nfrom seldom.logging import log\n\n\ndef log_assertions(func):\n \"\"\"\n Decorator: Adds loggi"
},
{
"path": "seldom/extend_lib/curlify.py",
"chars": 1134,
"preview": "\"\"\"\nA library to convert python requests object to curl command.\nGitHub: https://github.com/ofw/curlify\n\"\"\"\nfrom shlex i"
},
{
"path": "seldom/extend_lib/jsonpath.py",
"chars": 10081,
"preview": "\"\"\"\nAn XPath for JSON\nhelp: https://goessner.net/articles/JsonPath/\nGitHub: https://gist.github.com/drewr/783585\n\"\"\"\nfro"
},
{
"path": "seldom/extend_lib/parameterized.py",
"chars": 26167,
"preview": "\"\"\"\nParameterized testing with any Python test framework.\nGitHub:https://github.com/wolever/parameterized\n\"\"\"\nimport ins"
},
{
"path": "seldom/extend_lib/tomorrow.py",
"chars": 1548,
"preview": "\"\"\"\nthis is tomorrow3 library,Easier way to use thread pool executor\nGitHub: https://github.com/dflupu/tomorrow3\n\"\"\"\nfro"
},
{
"path": "seldom/file_runner/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "seldom/file_runner/api_excel.py",
"chars": 1806,
"preview": "import seldom\nfrom seldom import file_data\nimport json\nfrom seldom.logging import log\nfrom seldom.running.config import "
},
{
"path": "seldom/har2case/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "seldom/har2case/core.py",
"chars": 3896,
"preview": "\"\"\"\nhar to case core\n\"\"\"\nimport os\nfrom seldom.logging import log\nfrom seldom.har2case import utils\n\n\nclass HarParser:\n "
},
{
"path": "seldom/har2case/demo.har",
"chars": 3820,
"preview": "{\n \"log\": {\n \"pages\": [],\n \"entries\": [\n {\n \"time\": 537,\n \"request\": {\n \"headersSize"
},
{
"path": "seldom/har2case/utils.py",
"chars": 1120,
"preview": "\"\"\"\nhar to case utils\n\"\"\"\nimport io\nimport sys\nimport json\nfrom seldom.logging import log\n\n\ndef load_har_log_entries(fil"
},
{
"path": "seldom/logging/__init__.py",
"chars": 46,
"preview": "from .log import log\nfrom .log import log_cfg\n"
},
{
"path": "seldom/logging/exceptions.py",
"chars": 1232,
"preview": "\"\"\"\nExceptions that may happen in all the seldom code.\n\"\"\"\n\n\nclass SeldomException(Exception):\n \"\"\"\n Base seldom e"
},
{
"path": "seldom/logging/log.py",
"chars": 1989,
"preview": "\"\"\"\nSeldom log\n\"\"\"\nimport os\nimport sys\nimport time\nimport inspect\nfrom loguru import logger\nfrom seldom.running.config "
},
{
"path": "seldom/project_temp/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "seldom/project_temp/api/confrun.py",
"chars": 1056,
"preview": "\"\"\"\nseldom confrun.py hooks function - api auto test project\nRun:\n> seldom -p test_dir\n\"\"\"\n\n\ndef base_url():\n \"\"\"\n "
},
{
"path": "seldom/project_temp/api/run.py",
"chars": 509,
"preview": "\"\"\"\nseldom.main() - Run seldom main method.\nRun:\n> python run.py\n\"\"\"\nimport seldom\n\n\"\"\"\n参数说明:\npath: 指定测试目录。\nbase_url: Ht"
},
{
"path": "seldom/project_temp/api/test_sample.py",
"chars": 1044,
"preview": "import seldom\nfrom seldom import file_data\n\n\nclass TestRequest(seldom.TestCase):\n\n def test_put_method(self):\n "
},
{
"path": "seldom/project_temp/app/confrun.py",
"chars": 1576,
"preview": "\"\"\"\nseldom confrun.py hooks function - app auto test project\nRun:\n> seldom -p test_dir\n\"\"\"\nfrom seldom.appium_lab.andro"
},
{
"path": "seldom/project_temp/app/run.py",
"chars": 985,
"preview": "\"\"\"\nseldom.main() - Run seldom main method.\nRun:\n> python run.py\n\"\"\"\nimport seldom\nfrom seldom.appium_lab.android import"
},
{
"path": "seldom/project_temp/app/test_sample.py",
"chars": 1169,
"preview": "from appium.options.android import UiAutomator2Options\n\nimport seldom\nfrom seldom.appium_lab.keyboard import KeyEvent\n\n\n"
},
{
"path": "seldom/project_temp/data.json",
"chars": 310,
"preview": "{\n \"bing\": [\n [\n \"case1\",\n \"seldom\"\n ],\n [\n \"case2\",\n \"poium\"\n ],\n [\n \"case3\",\n"
},
{
"path": "seldom/project_temp/web/confrun.py",
"chars": 1083,
"preview": "\"\"\"\nseldom confrun.py hooks function - web auto test project\nRun:\n> seldom -p test_dir\n\"\"\"\n\n\ndef browser():\n \"\"\"\n "
},
{
"path": "seldom/project_temp/web/run.py",
"chars": 552,
"preview": "\"\"\"\nseldom.main() - Run seldom main method.\nRun:\n> python run.py\n\"\"\"\nimport seldom\n\n\"\"\"\n参数说明:\npath: 指定测试目录。\nbrowser: Web"
},
{
"path": "seldom/project_temp/web/test_sample.py",
"chars": 616,
"preview": "import seldom\nfrom seldom import file_data\n\n\nclass SampleTest(seldom.TestCase):\n\n def test_case(self):\n \"\"\"a s"
},
{
"path": "seldom/request.py",
"chars": 16343,
"preview": "\"\"\"\nseldom requests\n\"\"\"\nimport os\nimport ast\nimport time\nimport json\nimport socket\nfrom typing import Any\nfrom functools"
},
{
"path": "seldom/running/DebugTestRunner.py",
"chars": 2663,
"preview": "\"\"\"\nRun tests in debug mode\n\"\"\"\nimport unittest\nimport functools\nfrom seldom.utils.benchmark import benchmark\nfrom seldo"
},
{
"path": "seldom/running/__init__.py",
"chars": 787,
"preview": "# Licensed to the Software Freedom Conservancy (SFC) under one\n# or more contributor license agreements. See the NOTICE"
},
{
"path": "seldom/running/config.py",
"chars": 2041,
"preview": "\"\"\"\nSeldom configuration file\n\"\"\"\nimport logging\nimport threading\nimport requests\n\n\nclass Seldom:\n \"\"\"\n Seldom bro"
},
{
"path": "seldom/running/loader_extend.py",
"chars": 7523,
"preview": "\"\"\"seldom Loading unittests.\"\"\"\nimport os\nimport sys\nimport functools\nfrom fnmatch import fnmatchcase\nfrom unittest.load"
},
{
"path": "seldom/running/loader_hook.py",
"chars": 1004,
"preview": "import os\nimport sys\nimport importlib\n\n\ndef loader(func_name: str, file_name: str = \"confrun.py\", *args, **kwargs):\n "
},
{
"path": "seldom/running/runner.py",
"chars": 17038,
"preview": "\"\"\"\nseldom main\n\"\"\"\nimport ast\nimport builtins\nimport inspect\nimport json as sys_json\nimport os\nimport re\nimport unittes"
},
{
"path": "seldom/skip.py",
"chars": 2990,
"preview": "\"\"\"\nunittest decorator\n\"\"\"\nimport unittest\nimport functools\n\n__all__ = [\n \"skip\", \"skip_if\", \"skip_unless\", \"expected"
},
{
"path": "seldom/swagger2case/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "seldom/swagger2case/core.py",
"chars": 5115,
"preview": "import os\nimport json\nimport requests\nfrom seldom.logging import log\nfrom seldom.utils.file_extend import file\n\n\nclass S"
},
{
"path": "seldom/swagger2case/swagger.json",
"chars": 35035,
"preview": "{\n \"swagger\": \"2.0\",\n \"info\": {\n \"description\": \"This is a sample server Petstore server. You can find out"
},
{
"path": "seldom/testdata/__init__.py",
"chars": 27,
"preview": "from .random_func import *\n"
},
{
"path": "seldom/testdata/conversion.py",
"chars": 4023,
"preview": "\"\"\"\nData type conversion of different files\n\"\"\"\nimport csv\nimport json\nfrom itertools import islice\nimport codecs\n\nimpor"
},
{
"path": "seldom/testdata/parameterization.py",
"chars": 10432,
"preview": "\"\"\"\ntest data driver function\n\"\"\"\nimport inspect as sys_inspect\nimport itertools\nimport os\nimport warnings\nfrom functool"
},
{
"path": "seldom/testdata/random_data.py",
"chars": 16373,
"preview": "\"\"\"\nrandom data file\n\"\"\"\nimport re\nimport sys\n\n\n# https://www.ssa.gov/oact/babynames/decades/names2010s.html\nen_first_na"
},
{
"path": "seldom/testdata/random_func.py",
"chars": 13524,
"preview": "\"\"\"\nA function that generates random data\n\"\"\"\nimport re\nimport sys\nimport time\nimport uuid\nimport random\nimport hashlib\n"
},
{
"path": "seldom/utils/__init__.py",
"chars": 259,
"preview": "from .file_extend import file, find_file_path\nfrom .diff import diff_json, AssertInfo\nfrom .jmespath import jmespath\nfro"
},
{
"path": "seldom/utils/adbutils.py",
"chars": 9803,
"preview": "import os\nimport re\nimport subprocess\nimport time\nfrom typing import List, Tuple, Optional\nfrom contextlib import contex"
},
{
"path": "seldom/utils/benchmark.py",
"chars": 6624,
"preview": "import time\nfrom typing import Callable, List, Dict, Any\n\n\nclass Benchmark:\n \"\"\"\n Benchmark Class\n \"\"\"\n\n def"
},
{
"path": "seldom/utils/cache.py",
"chars": 4912,
"preview": "\"\"\"\nseldom cache\n\"\"\"\nimport os\nimport json\nimport uuid\nimport pickle\nimport shutil\nimport tempfile\nimport threading\nfrom"
},
{
"path": "seldom/utils/cache_data.json",
"chars": 2,
"preview": "{}"
},
{
"path": "seldom/utils/dependence.py",
"chars": 1727,
"preview": "from typing import Callable, Text, Tuple\nfrom functools import wraps\nfrom seldom.utils import cache\nfrom seldom.logging "
},
{
"path": "seldom/utils/diff.py",
"chars": 2773,
"preview": "\"\"\"\ndiff file\n\"\"\"\nfrom typing import Any\nfrom seldom.logging import log\n\n\nclass AssertInfo:\n \"\"\"\n Save assert warn"
},
{
"path": "seldom/utils/encrypt.py",
"chars": 15780,
"preview": "\"\"\"\nEncryption Utility Module\n\nProvides comprehensive encryption and decryption functionalities, supporting the followin"
},
{
"path": "seldom/utils/file_extend.py",
"chars": 3470,
"preview": "\"\"\"\nfile extend\n\"\"\"\nimport os\nimport sys\nimport inspect\nfrom pathlib import Path\n\n\nclass FindFilePath:\n \"\"\"find file "
},
{
"path": "seldom/utils/genson.py",
"chars": 419,
"preview": "\"\"\"\ngenson:\nhttps://github.com/wolverdude/GenSON\n\"\"\"\nfrom genson import SchemaBuilder\nfrom seldom.request import Respons"
},
{
"path": "seldom/utils/jmespath.py",
"chars": 231,
"preview": "\"\"\"\njmespath search data\nhttps://github.com/jmespath/jmespath.py\n\"\"\"\nfrom jmespath import search\n\n\ndef jmespath(data, ex"
},
{
"path": "seldom/utils/match_image.py",
"chars": 3225,
"preview": "import inspect\nimport os\nfrom pathlib import Path\n\ntry:\n from PIL import Image, ImageChops\nexcept ModuleNotFoundError"
},
{
"path": "seldom/utils/resource_loader.py",
"chars": 3017,
"preview": "import json\nfrom functools import lru_cache\nfrom pathlib import Path\nfrom typing import Any, Dict, Literal\n\nfrom seldom."
},
{
"path": "seldom/utils/send_extend.py",
"chars": 1588,
"preview": "\"\"\"\nsend message file\n\"\"\"\nimport os\n\nfrom XTestRunner import DingTalk as XDingTalk\nfrom XTestRunner import FeiShu as XFe"
},
{
"path": "seldom/utils/thread_lab.py",
"chars": 1521,
"preview": "\"\"\"\ncase more threading\n\"\"\"\nfrom threading import Thread\nfrom seldom.logging import log\n\n\nclass ThreadWait:\n \"\"\"\n "
},
{
"path": "seldom/utils/timer.py",
"chars": 386,
"preview": "import time\nfrom seldom.logging import log\n\n\ndef timer(func):\n \"\"\"\n timer decorator\n :param func:\n :return:\n"
},
{
"path": "seldom/webcommon/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "seldom/webcommon/find_elems.py",
"chars": 3271,
"preview": "import time\n\nfrom selenium.common.exceptions import TimeoutException\nfrom selenium.webdriver.support import expected_con"
},
{
"path": "seldom/webcommon/keyboard.py",
"chars": 2963,
"preview": "import platform\n\nfrom selenium.webdriver.common.keys import Keys\n\nfrom seldom.logging import log\nfrom seldom.webcommon.f"
},
{
"path": "seldom/webcommon/locators.py",
"chars": 1429,
"preview": "\"\"\"\nappium & selenium locator\n\"\"\"\nfrom appium.webdriver.common.appiumby import AppiumBy as By\n\nLOCATOR_LIST = {\n 'css"
},
{
"path": "seldom/webcommon/selector.py",
"chars": 884,
"preview": "from appium.webdriver.common.appiumby import AppiumBy as By\n\nfrom seldom.webcommon.locators import SELECTOR_LIST\n\n\ndef s"
},
{
"path": "seldom/webdriver.py",
"chars": 28892,
"preview": "\"\"\"\nselenium WebDriver API\n\"\"\"\nimport base64\nimport os\nimport time\nimport warnings\n\nfrom selenium.common.exceptions impo"
},
{
"path": "seldom/webdriver_chaining.py",
"chars": 9878,
"preview": "\"\"\"\nWebDriver chaining API\n\"\"\"\nimport os\nimport random\nimport time\n\nfrom selenium.webdriver.common.action_chains import "
},
{
"path": "seldom/websocket_client.py",
"chars": 2076,
"preview": "from threading import Thread\n\nimport websocket\n\nfrom seldom.logging import log\n\n\nclass WebSocketClient(Thread):\n \"\"\"\n"
},
{
"path": "tests/data/country.graphql",
"chars": 158,
"preview": "query GetCountry($code: ID!) {\n country(code: $code) {\n name\n capital\n currency\n language"
},
{
"path": "tests/data/hello.txt",
"chars": 13,
"preview": "hello world!\n"
},
{
"path": "tests/test_adb.py",
"chars": 891,
"preview": "import time\nfrom seldom.utils.adbutils import ADBUtils\n\nimport unittest\n\n\nclass ADBUtilsTest(unittest.TestCase):\n\n de"
},
{
"path": "tests/test_api_object.py",
"chars": 831,
"preview": "import seldom\nfrom seldom.request import api\nfrom seldom.request import HttpRequest\n\n\nclass LoginApiObject(HttpRequest):"
},
{
"path": "tests/test_autowing.py",
"chars": 932,
"preview": "\"\"\"\n> pip install autowing\n\"\"\"\nimport seldom\nfrom seldom import Seldom\nfrom autowing.selenium.fixture import create_fixt"
},
{
"path": "tests/test_base_assert.py",
"chars": 2902,
"preview": "import seldom\n\n\nclass TestAssertions(seldom.TestCase):\n def test_assertEqual(self):\n self.assertEqual(10, 10) "
},
{
"path": "tests/test_benchmark.py",
"chars": 799,
"preview": "import time\nimport seldom\nfrom seldom.testdata import get_int\nfrom seldom.utils.benchmark import benchmark_test\n\n\nclass "
},
{
"path": "tests/test_browser.py",
"chars": 1292,
"preview": "import seldom\n\n\nclass WebTestOne(seldom.TestCase):\n \"\"\"case lunch browser\"\"\"\n\n def start(self):\n self.brows"
},
{
"path": "tests/test_browser_new.py",
"chars": 619,
"preview": "import seldom\n\n\nclass WebTestNew(seldom.TestCase):\n \"\"\"Web search test case\"\"\"\n\n def test_new_browser(self):\n "
},
{
"path": "tests/test_cache/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_cache/test_cache.py",
"chars": 443,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/5/22\ndesc: 缓存用法\n\"\"\"\nfrom seldom.utils import cache\n\n# 清除指定缓存\ncache.clear()\n\n# 获取指定缓存\nto"
},
{
"path": "tests/test_cache/test_cache_thread.py",
"chars": 526,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2024/8/27\ndesc: 多线程缓存用法\n\"\"\"\nfrom seldom.utils import cache\nfrom seldom.extend_lib import thr"
},
{
"path": "tests/test_cache/test_memory_cache.py",
"chars": 722,
"preview": "import seldom\nimport time\nfrom seldom.logging import log\nfrom seldom.utils import memory_cache\n\n\n@memory_cache()\ndef add"
},
{
"path": "tests/test_db/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_db/test_db_mssql.py",
"chars": 2970,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/10/01\ndesc: 数据库操作\nPlease install the library. https://github.com/pymssql/pymssql\n\"\"\"\nim"
},
{
"path": "tests/test_db/test_db_mysql.py",
"chars": 2850,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/10/01\ndesc: 数据库操作\n\"\"\"\nimport unittest\n\nfrom seldom.db_operation import MySQLDB\n\n\nclass "
},
{
"path": "tests/test_db/test_db_postgresdb.py",
"chars": 2788,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/10/01\ndesc: 数据库操作\nPlease install the library. https://github.com/psycopg/psycopg2\n\"\"\"\ni"
},
{
"path": "tests/test_db/test_db_sqlite3.py",
"chars": 2707,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/10/01\ndesc: 数据库操作\n\"\"\"\nimport unittest\n\nfrom seldom.db_operation import SQLiteDB\nfrom se"
},
{
"path": "tests/test_dependent_func.py",
"chars": 564,
"preview": "import seldom\nfrom seldom.testdata import get_md5\nfrom seldom.utils import cache, dependent_func\n\n\nclass DependentTest(s"
},
{
"path": "tests/test_encrypt.py",
"chars": 5030,
"preview": "import unittest\n\n# 导入待测试的模块\nfrom seldom.utils.encrypt import (\n CipherMode,\n HashUtil,\n AESUtil,\n DESUtil,\n "
},
{
"path": "tests/test_fixture.py",
"chars": 420,
"preview": "import seldom\n\n\nclass TestCase(seldom.TestCase):\n\n @staticmethod\n def start_class(cls):\n print(\"测试类开始执行\")\n\n"
},
{
"path": "tests/test_graphql.py",
"chars": 632,
"preview": "import seldom\nfrom seldom import TestCase\nfrom seldom.utils.resource_loader import resource_file\n\n\nclass TestGraphQL(Tes"
},
{
"path": "tests/test_http_assert.py",
"chars": 1746,
"preview": "import seldom\nfrom seldom.utils import genson\n\n\nclass TestHttpAssert(seldom.TestCase):\n\n def test_assert_json(self):\n"
},
{
"path": "tests/test_jsonpath.py",
"chars": 1200,
"preview": "import unittest\nfrom seldom.extend_lib.jsonpath2 import jsonpath\n\njson_data = {\n \"args\": {\n \"id\": \"1\",\n "
},
{
"path": "tests/test_locators.py",
"chars": 2004,
"preview": "\"\"\"\nselenium locators\n\"\"\"\nimport seldom\nfrom seldom import Steps\n\n\nclass TestForm(seldom.TestCase):\n\n def start(self)"
},
{
"path": "tests/test_log.py",
"chars": 941,
"preview": "import sys\nimport seldom\nfrom seldom.logging import log\nfrom seldom import data\n\n\nclass TestCase(seldom.TestCase):\n d"
},
{
"path": "tests/test_other_lib/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_other_lib/test_playwright.py",
"chars": 1880,
"preview": "\"\"\"\nplaywright demo\ndoc: https://playwright.dev\n\"\"\"\nimport seldom\nfrom playwright.sync_api import sync_playwright\nfrom p"
},
{
"path": "tests/test_other_lib/test_pyautogui.py",
"chars": 1040,
"preview": "import os\nimport pyautogui\nimport seldom\nfrom seldom.testdata import get_int\n\n\nclass TestPyAutoGUINote(seldom.TestCase):"
},
{
"path": "tests/test_other_lib/test_uiautomator.py",
"chars": 688,
"preview": "import seldom\nimport uiautomator2 as u2\n\n\nclass MyAppTest(seldom.TestCase):\n\n def start(self):\n # 链接设备\n "
},
{
"path": "tests/test_playwright_sample.py",
"chars": 1082,
"preview": "\"\"\"\n需要安装 playwright: https://playwright.dev/\n> pip install playwright\n\"\"\"\nimport seldom\nimport base64\nfrom playwright.sy"
},
{
"path": "tests/test_random/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_random/test_testdata.py",
"chars": 2255,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/9/17\ndesc: 生成随机数用法\n\"\"\"\nimport seldom\nfrom seldom.testdata import *\n\n\nclass TestRandomDa"
},
{
"path": "tests/test_request_extend.py",
"chars": 409,
"preview": "import seldom\n\n\nclass TestSaveResp(seldom.TestCase):\n\n def test_save_response(self):\n \"\"\"将response保存到文件中\"\"\"\n "
},
{
"path": "tests/test_skip.py",
"chars": 406,
"preview": "import seldom\n\n\n@seldom.skip(reason=\"跳过类\")\nclass SkipTest(seldom.TestCase):\n\n def test_case(self):\n ...\n\n\nclas"
},
{
"path": "tests/test_steps_chaining.py",
"chars": 563,
"preview": "import seldom\nfrom seldom import Steps\n\n\nclass WebTestChaining(seldom.TestCase):\n \"\"\"test chaining API\"\"\"\n\n def te"
},
{
"path": "tests/test_steps_chaining_browser.py",
"chars": 662,
"preview": "import seldom\nfrom seldom import Steps\n\n\nclass WebTestChaining(seldom.TestCase):\n \"\"\"test chaining API\"\"\"\n\n def st"
},
{
"path": "tests/test_thread/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_thread/test_thread.py",
"chars": 1467,
"preview": "\"\"\"\nauthor: bugmaster\ndata: 2022/6/18\ndesc: 线程用法\n\"\"\"\nimport time\nimport seldom\nfrom seldom import data\nfrom seldom.utils"
},
{
"path": "tests/test_thread/test_thread_browser.py",
"chars": 542,
"preview": "import time\nimport seldom\nfrom seldom.extend_lib import threads\n\n\nclass BingTest(seldom.TestCase):\n \"\"\"Bing search te"
},
{
"path": "tests/test_thread/test_thread_case.py",
"chars": 677,
"preview": "import time\nimport seldom\nfrom seldom.extend_lib import threads\n\n\nclass MyTest(seldom.TestCase):\n\n def test_baidu(sel"
},
{
"path": "tests/test_thread/test_thread_path.py",
"chars": 436,
"preview": "import seldom\nfrom seldom.extend_lib import threads\n\n\n@threads(3) # !!!核心!!!! 设置线程数\ndef run_case(path: str):\n \"\"\"\n "
},
{
"path": "tests/test_utils/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_utils/test_file.py",
"chars": 1495,
"preview": "import unittest\nfrom pathlib import Path\nfrom seldom.utils import file\n\n\nclass TestFilePathUtils(unittest.TestCase):\n\n "
},
{
"path": "tests/test_websocket/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/test_websocket/test_websocket.py",
"chars": 1074,
"preview": "import seldom\nfrom seldom.logging import log\nfrom seldom.websocket_client import WebSocketClient\n\n\nclass WebSocketTest(s"
}
]
// ... and 4 more files (download for full content)
About this extraction
This page contains the full source code of the defnngj/pyse GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (675.4 KB), approximately 190.7k tokens, and a symbol index with 954 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.