...
 
Commits (83)
    https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/52bea414e7a5b9743e0990f2ec870431f378ba9b Merge pull request #11 from amazingTest/dev 2019-12-29T14:30:58+00:00 Taisite 523314409@qq.com [feat](project)项目内在页面顶部看到项目名称 https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/164eb7d2e1fcc09924242fa0b5b4419a5c104b3c [refact](dist)重新打包dist 2019-12-29T14:40:36+00:00 shaoyuyishiwo 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/4abffbadabfe46ec54549e0364372cdc32b6c186 Update README_CN.md 2020-01-31T10:03:20+00:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/dc17f5452ff1752e5743e945bf05438414a62c56 report-view-color-fix 2020-02-04T15:46:34+08:00 nogit websnaile@qq.com report-view-color-fix https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/80b384e8f4df0aa569c5f6e2515dc9bdb55f710c Merge pull request #1 from nogit/nogit-patch-1-report-view-color-fix 2020-02-04T15:52:48+08:00 nogit websnaile@qq.com report-view-color-fix https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/8a9de41533e80bc4e3488257c2fa708a08c77857 Merge pull request #12 from nogit/master 2020-02-04T09:00:51+00:00 Taisite 523314409@qq.com report-view-color-fix https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/815a535644486e9834072fd3d6976540193c6a02 [refact](dist)重新打包dist 2020-02-04T09:19:25+00:00 shaoyuyishiwo 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/58ed5dc538ceac76f5735213dca6203a08142a4e Update README_CN.md 2020-03-02T09:25:51+00:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/58c724721c771ba6041fe2f33243164a865148d3 Update README_CN.md 2020-04-22T22:07:23+01:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/548c0f7c73a95104e293cda5f4850e9de10c4509 Update README.md 2020-04-22T22:14:21+01:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f51992b8696f1bc31bb7110132806516ad718234 Update README_CN.md 2020-04-23T09:48:03+01:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/7f4704112b0b79c1101eaa8e9cb9fa589d777540 Create FUNDING.yml 2020-06-12T18:42:21+08:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/5e381ee2fb41f25824e791c79b1382dda0f9c0c7 Update FUNDING.yml 2020-06-12T18:43:32+08:00 Taisite 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/49c6f6f5d2125dbfcba56003a820f0652fb74e81 [refact]支持对非json格式返回的正则校验 2020-09-05T11:27:34+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/5936a88863493f79d3011ece134e8d2f0024b340 [fix]修复get请求下全局变量替换参数 2020-09-05T11:29:27+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/1a3b90bc188b3a445b243b07933b60860177aab5 [doc]add ignore 2020-09-12T12:35:26+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/18603ca2a592bbe506eab02e0ceda53fce1ca23e [feat]实际结果&测试结论限制字符长度 2020-09-12T12:38:21+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/c2f056b8e0b6a74554f89e2826e64e57ec25e246 [fix]修复邮箱判断 2020-09-12T12:39:22+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f499f52aed50b43ad91611067c3de8d6f0b909c9 [fix]关闭debug模式 2020-09-12T12:39:47+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/14a7c54770827ac0293561adfeae9c2035b8f848 [feat]正则查询语句支持全局替换 2020-09-12T12:43:39+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/611fb04b10e4bf130d25f989268a7b6c914d687a [tips]预先打包好dist 2020-09-12T13:25:17+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/337343a286751963433ca8edb25518bb28465a0c [extra]为使用者预先打包好dist 2020-09-12T13:26:42+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/942e066d50e37fffb783a75981d956912d6688b5 [fix]修复html正则验证 2020-09-12T16:07:02+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/1abda419e15a97a96011849a2fba89705a24b067 [fix]修复对数组直接进行正则断言 2020-09-20T18:33:23+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/1a67ccf86b6c4468850e116daa733d6d855b275f [fix]修复导出用例未按照用例组排序 2020-09-20T18:34:17+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/a63595533efc38af4eb4ccdcd011b65cfd874d60 [refact]提高性能 2020-09-26T10:27:40+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f11208dfcde9f1cc5043d536c3b3992c4d9daf51 [feat]新增导出报告接口 2020-09-26T10:30:26+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/213925d61544c488a7e7fdfac543e73f8b1f71fa [feat]新增导出报告接口api 2020-09-26T10:32:32+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f8ac3b83fcd38a4758cd4a5fd42c60e7eb28efbc [fix]修复报告请求参数过长问题 2020-09-26T10:34:10+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f3de76bb3607d060f6485d3b53a1ef58b57afb09 [feat]新增报告导出按钮 2020-09-26T10:35:08+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/c6ddca8065e13955ce260f1cffc7018855f4cd7c [feat]报告新增测试总耗时&测试环境&项目名称 2020-09-26T10:38:03+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/979fe67d0ee420c38e9911fabdc2593de19fec44 [feat]新增测试总耗时&测试环境&项目名称字段 2020-09-26T10:39:20+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/65149b56466601d3bf793115cfb7162c27038672 [refact]兼容单个用例下测试总耗时计算 2020-09-26T10:40:42+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/ab84fb72bb37b73d462a10fa1317bfb63db0765c [refact]兼容定时任务下测试总耗时计算 2020-09-26T10:42:01+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/92597b8ca18853e54526bf75faa1008e6cf77262 [extra]为使用者打包好dist 2020-09-26T10:46:10+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/cc5280732d58823d3a204679ba91104b4ac31599 [fix]fix prop 2020-09-26T10:55:42+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/83585cac9de9356c3af09de3a83d669f9d5c39b2 [extra]为使用者打包好最新dist 2020-09-26T10:59:10+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/904f78d636be727bdc0e057f09a90a9b3075caca [extra]为使用者打包好最新dist 2020-09-26T14:27:51+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/508b47a7bfc77ee8b105d53d23389bf04495c74b [fix] fix sender_id 2020-10-31T10:44:22+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/e4d11616200cee220aad01309594e6bd07f22972 [feat]新增定时任务名称记录 2020-10-31T11:07:45+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/ff64f9afc42f8c9dd36eb1a52e27ae36c5257912 [refact] 重构 export_report 接口 2020-10-31T11:08:33+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/9f7135a27265dfc3fa343d9b3b1dbfd3b81c9a80 [feat]新增get_test_report_excel_bytes_io方法 2020-10-31T11:10:15+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/f4aa2d1227292371232cbdd2e31e8919e03ccc1d [feat] 新增定时任务名称记录 2020-10-31T11:11:58+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/2fc4b242adc2a438256129e086e794ec5f05a57a [feat] 新增邮件 & 企业微信发送详细测试报告内容 2020-10-31T11:12:19+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/faae25efd1499f23d81beeef4e3554cb83bab025 [feat] 邮件发送支持传送附件 2020-10-31T11:14:03+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/0c33c357022c21eb470252ca186270cf477c2035 [feat] 邮件发送支持传送附件 2020-10-31T11:14:39+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/64f10c5719d0c84081506fbb494057512f54008c [refact]新增邮箱发件人配置placeholder 2020-10-31T12:19:57+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/37514035df1cf45fdb26ca7aa6a5c0a0cc2d6bfd [extra]打包好最新dist 2020-10-31T12:21:31+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/4619c155ef1bbb0bda40fb8626331dd83d337886 [feat]新增 generate_curl 2020-11-08T11:54:04+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/5359d5ee70b574a2debf8cc6f5cedd65c32800f0 [feat] 新增 helpers 2020-11-08T11:54:33+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/dd5adb384f98dc7bf01c8327548d182f8fa30afe [feat]记录下 curl 2020-11-08T11:55:25+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/6329b1ebcfdd45538c6a47c7dc120c537368885d [feat] 2020-11-08T11:56:18+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/b849580fac6f3729fa7c2beea5443a87b5b8ad97 [feat]定时任务智能告警&恢复提醒功能 2020-11-08T11:58:28+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/393717189d9e843815bf59b60ab4f05abf88f65c [doc]添加项目更新日志 2020-11-10T16:24:30+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/3c20572497a306007b8ee3a879defa6f8063c0f4 [feat]新增 resolve_fake_var 方法 2020-11-13T21:57:04+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/97c5ae35e58e3ba0b26990bb9199e3a40f88e333 [feat]支持在请求参数中调用 faker 开源库 2020-11-13T21:58:27+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/8f220cf52db110bb08f0c11455c7c4d44cf364dc [requirements]新增 Faker==4.14.2 2020-11-13T22:00:01+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/83956c7bf4eb5b10995a6ab97cd0d7d3253aa91c [readme] 2020-11-17T12:16:46+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/91c10ac771025182c98f492fb7bd2c361bbc4bf9 [feat] test_report_detail_map 新增 checkResponseTime 2020-11-21T14:17:38+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/c1b79792d3d24b579b6f2af5147ea35dbce41bab [feat] 新增接口响应耗时断言功能 2020-11-21T14:18:43+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/07c54e229af227b6c29616dbd463687e8c77331c [feat] test_case_map 新增 checkResponseTime 字段 2020-11-21T14:20:29+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/1eefb84066f770a008ab9928c7099974c3bd6153 [feat] 新增 checkResponseTime 字段 2020-11-21T14:22:03+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/8c51277dda77af3795086e83287f47d7f0d4f27d [feat] 前端新增测试耗时断言设置 2020-11-21T14:23:56+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/60cc48f9db52a1af151b427f226c4035fc420b12 [feat]测试报告新增测试耗时 2020-11-21T14:25:28+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/4ec53956a59c0c81be800ef9f131272451134719 [extra]打包好最新dist 2020-11-21T14:29:04+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/d9f0c9f8a5a8e760267b2646589c90e3b5e6d188 [refact]默认不开启nlp 2020-11-21T14:34:29+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/ae5cc0bfba08c4e50af3e688c2270c81088989bc [feat]新增 testDataStorage 2021-01-16T09:51:20+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/547d7913c4fa736cbf39f6b35cc585d6a8681014 testDataStorage.py 2021-01-16T09:54:21+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/5cd42b3847b5fed3b86634ce59607e94cb1630fe add datastorage model 2021-01-16T09:55:33+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/394e2afa211e57f576cf06c0c5a6c9e354f7c61a [faet] start test 新增数据字典参数 2021-01-16T10:00:15+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/0dae07a56f4d59ecb50f4c2e7fee5361e5caadf3 [fix] 修复测试结果乱码 2021-01-16T10:04:42+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/393c9228923aeb24d81e32b570d7054cc1952539 [fix]修复异常情况下 curl 中 headers 的生成 2021-01-16T10:05:34+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/4ea5515b8ed4f0e2cf92dcf4fcbd6dbd252e1f6c [feat]新增 global_vars 传参 2021-01-16T10:07:10+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/fb822ac22a3970202940673899b5cdd82bb56b6c [feat]add storage router 2021-01-16T10:21:16+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/4e2f41f0da7c4840e5d9524d91276c5ee449eec2 [feat]add storage api 2021-01-16T10:22:56+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/fa4181338e8ca6017c9d3fa35e3c9741abbd12f4 [feat]新增前端数据字典测试入口 2021-01-16T10:29:40+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/ef0e43d981bedb943263e465163e4eeeb60d5a39 [feat]新增数据仓库页面 2021-01-16T10:31:07+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/7eb40462236e6c47bb594f7b58c0326db70a5984 [extra]打包好dist 2021-01-16T10:37:06+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/88eaa183646fb1fc6381646caa106baa5bad8e27 [extra]打包好 dist 2021-01-16T10:41:00+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/e7f865184f4facae4fff1b76b6b35acff295b79e [fix]about autor 2021-01-16T10:42:45+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/6fd5611b3c861e6774b7ecd7f3b4d5b56e941f28 [doc] 2021-01-16T10:48:51+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/fea1dfdff774df8c37fb83652459d6b78f62000b add ignore 2021-01-16T10:59:29+08:00 amazingTest 523314409@qq.com https://gitcode.net/weixin_41908648/Taisite-Platform/-/commit/5f11c8c20686c03bee2c3b1d497afe313012d96b [idea] 2021-01-16T11:01:50+08:00 amazingTest 523314409@qq.com
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # https://github.com/amazingTest/Taisite-Platform/blob/master/images/wechatDonation.jpg
.idea/.xml
\ No newline at end of file
.idea/.xml
.DS_Store
venv
.idea
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/frontend/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.6 (Taisite-Platform)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="R User Library" level="project" />
<orderEntry type="library" name="R Skeletons" level="application" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="Werkzeug" />
<item index="1" class="java.lang.String" itemvalue="cryptography" />
<item index="2" class="java.lang.String" itemvalue="prompt-toolkit" />
<item index="3" class="java.lang.String" itemvalue="urllib3" />
<item index="4" class="java.lang.String" itemvalue="click" />
<item index="5" class="java.lang.String" itemvalue="Jinja2" />
<item index="6" class="java.lang.String" itemvalue="Flask" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (Taisite-Platform)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
......
此差异已折叠。
......@@ -72,218 +72,13 @@ The platform follows the idea of "separate development frontend and backend". Th
![泰斯特平台结构图_V1.0](https://github.com/amazingTest/Taisite-Platform/blob/master/images/泰斯特平台结构图_V1.0.png)
## IV . Deploy
## deploy
### Deploy under windows
[click me](https://mp.weixin.qq.com/s/bLyDWHCAPCshF8vmbSHtWw)
#### 0. Clone
## how to use
git clone https://github.com/amazingTest/Taisite-Platform.git
#### 1. Install python 3 env
#### 2. deploy NLP model
[Download model](https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip)
2.2 Extract the compression package
2.3 Install python dependent-packages
pip install tensorflow==1.14.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install bert-serving-server==1.9.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
2.4 Start the model
// Execute after the current directory is switched to the model folder directory
bert-serving-start -model_dir ./chinese_L-12_H-768_A-12/ -num_worker=1
After the startup is successful, the output is as follows:
![NLP模型启动成功输出](https://github.com/amazingTest/Taisite-Platform/blob/master/images/NLP模型启动成功输出.png)
#### 3. Deploy Mongodb database
#### 4. Set system environment variables
AUTOTEST_PLATFORM_ENV=production
AUTOTEST_PLATFORM_NLP_SERVER_HOST=127.0.0.1
AUTOTEST_PLATFORM_MONGO_HOST=${MONGO_HOST}
AUTOTEST_PLATFORM_MONGO_PORT=${MONGO_PORT}
AUTOTEST_PLATFORM_MONGO_USERNAME=${USERNAME}
AUTOTEST_PLATFORM_MONGO_PASSWORD=${PASSWORD}
AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME=taisite
Where AUTOTEST_PLATFORM_ENV defaults to production (required)
AUTOTEST_PLATFORM_MONGO_HOST and AUTOTEST_PLATFORM_MONGO_PORT indicate the address and port of the database (required)
AUTOTEST_PLATFORM_MONGO_USERNAME and AUTOTEST_PLATFORM_MONGO_PASSWORD represent the account password of the database (if not required)
AUTOTEST_PLATFORM_NLP_SERVER_HOST (Natural Language Model Service) defaults to native boot (not required)
AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME is the default data table name (required)
After the setting is completed, you can test it with the following commands (CMD switches to the project root directory)
python ./backend/config.py
If the configuration is successful, you can see the input configuration data.
#### 5. Package the front-end dist file (I have done this for you, skip it if you don't need secondary development)
5.1 Install the Vue environment, download node.js and configure the environment, download the npm package manager
5.2 Cmd into the frontend directory, configure cnpm:
npm install -g cnpm --registry=https://registry.npm.taobao.org
5.3 Execute the install dependency package command:
cnpm install
5.4 Execute the package command:
cnpm run build
If successfully packaged, the dist folder will be generated in the project root directory.
#### 6. Start backend
// Switch to the project root directory to execute
pip install -r ./backend/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
// Start backend (default 5050 port)
python ./backend/run.py
// Create a platform administrator account password
python ./backend/createAdminUser.py
#### 7. Access project
You can now log in using http://127.0.0.1:5050/#/login using the created administrator account password.
![平台登录界面2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/平台登录界面2.png)
### Docker containerized deployment in Linux environment
#### 0. Clone
git clone https://github.com/amazingTest/Taisite-Platform.git
#### 1. Natural language model deployment
sudo -i
docker pull shaoyuyishiwo/bertserver
docker run --name autotest-platform-bertserver -d shaoyuyishiwo/bertserver
#### 2. Mongo database deployment (skip this step if an existing database is available)
2.1 Start database & data mount to host
sudo -i
docker pull mongo
docker run --name autotest-platform-mongo -p 27017:27017 -v /data/db:/data/db -v /data/configdb:/data/configdb ``-d mongo
2.2 Create a database account
docker exec -it autotest-platform-mongo /bin/bash
mongo
> use admin
switched to db admin
> db.createUser({user:"${USERNAME}",pwd:"${PASSWORD}",roles:["root"]})
Successfully added user: { "user" : "admin", "roles" : [ "root" ] }
2.3 Database memory expansion (recommended)
> db.adminCommand({setParameter:1, internalQueryExecMaxBlockingSortBytes:335544320})
{ "was" : 33554432, "ok" : 1 }
#### 3. Environment variable configuration
// Edit /etc/profile file
sudo -i
vi /etc/profile
If there is a warning, select (E)dit anyway (enter E)
3.1 Insert the following data at the end of the text (enter i to get into insert status)
export AUTOTEST_PLATFORM_ENV=production
export AUTOTEST_PLATFORM_NLP_SERVER_HOST=${BERT_IPADRESS}
export AUTOTEST_PLATFORM_MONGO_HOST=${MONGO_HOST}
export AUTOTEST_PLATFORM_MONGO_PORT=${MONGO_PORT}
export AUTOTEST_PLATFORM_MONGO_USERNAME=${USERNAME}
export AUTOTEST_PLATFORM_MONGO_PASSWORD=${PASSWORD}
export AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME=${DBNAME}
The variable is a dynamic value. The deployer can input it according to the actual situation.
The DBNAME value can be arbitrarily customized (database table name). The BERT_IPADRESS and
MONGO_HOST values can be queried by the following commands:
docker inspect autotest-platform-bertserver
docker inspect autotest-platform-mongo // If you used the above steps to deploy the database
The output is shown below:
![控制台输出1.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/控制台输出1.png)
3.2 After inserting, click the ESC button, type :wq and click Enter to save.
3.3 Environment variables take effect immediately after executing the following command
source /etc/profile
#### 4. Start the project
Before you start the project, you need to change the timezone info by modifying the RUN script in **Dockerfile.backend** which stay
in first-level directory of the project. The default timezone is Asia/Shanghai.
// Execute the deployment file in the project root directory
sh deploy ${PORT}
The ${PORT} variable fills in the project access port, and the administrator account password is also created when the
project starts, as shown in the following figure:
![控制台输出2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/控制台输出2.png)
#### 5. Access project
The browser can access the ${PORT} port of the deployment server address.
![平台登录界面.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/平台登录界面.png)
#### EXTRA. FQA
The following output represents the NLP model startup failure
![NLP部署失败.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/NLP部署失败.png)
Solution steps:
1. Remove the code from ./backend/app/init.py:
![不使用NLP模型方法指南1.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/不使用NLP模型方法指南1.png)
2. Modify the following code in ./backend/testframe/interfaceTest/tester.py to pass:
![不使用NLP模型方法指南2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/不使用NLP模型方法指南2.png)
When you start the project after you finish, you will not depend on the natural language model~
[click me](https://shimo.im/docs/8TqxG3Ttjvj9yT8T)
## V . Contact me
......
......@@ -4,6 +4,10 @@
![泰斯特平台LOGO.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/泰斯特平台LOGO.png)
## 更新记录
[完整更新日记](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MzIwODY0NA==&action=getalbum&album_id=1515122445446397953&scene=173&subscene=&sessionid=undefined&enterid=1608004196&from_msgid=2247485048&from_itemidx=1&count=3#wechat_redirect)
## 开源申明
**这是一个受限制的自由软件!您不能在任何未经允许的前提下对程序代码进行修改和用于商业用途;也不允许对程序代码修改后以任何形式任何目的的再发布。**
......@@ -70,247 +74,14 @@
![泰斯特平台结构图_V1.0](https://github.com/amazingTest/Taisite-Platform/blob/master/images/泰斯特平台结构图_V1.0.png)
## IV . 泰斯特平台部署
### windows 环境下部署
#### 0. 克隆项目
git clone https://github.com/amazingTest/Taisite-Platform.git
#### 1. 安装 python 3 环境
[点击进入教程](https://www.runoob.com/python3/python3-install.html)
#### 2. 部署自然语言模型
[点击下载模型](https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip)
2.2 解压压缩包
2.3 安装 python 依赖包
pip install tensorflow==1.14.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install bert-serving-server==1.9.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
2.4 启动模型
// 当前目录切换至模型文件夹目录后执行
bert-serving-start -model_dir ./chinese_L-12_H-768_A-12/ -num_worker=1
启动成功后输出如下:
![NLP模型启动成功输出](https://github.com/amazingTest/Taisite-Platform/blob/master/images/NLP模型启动成功输出.png)
#### 3. 部署 Mongodb 数据库
[点击进入教程](https://www.runoob.com/mongodb/mongodb-window-install.html)
#### 4. 设置系统环境变量
AUTOTEST_PLATFORM_ENV=production
AUTOTEST_PLATFORM_NLP_SERVER_HOST=127.0.0.1
AUTOTEST_PLATFORM_MONGO_HOST=${MONGO_HOST}
AUTOTEST_PLATFORM_MONGO_PORT=${MONGO_PORT}
AUTOTEST_PLATFORM_MONGO_USERNAME=${USERNAME}
AUTOTEST_PLATFORM_MONGO_PASSWORD=${PASSWORD}
AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME=taisite
其中 AUTOTEST_PLATFORM_ENV 默认为 production (必填)
AUTOTEST_PLATFORM_MONGO_HOST和 AUTOTEST_PLATFORM_MONGO_PORT 分别表示数据库的地址和端口(必填)
AUTOTEST_PLATFORM_MONGO_USERNAME和 AUTOTEST_PLATFORM_MONGO_PASSWORD 分别表示数据库的帐号密码(若无可不填)
AUTOTEST_PLATFORM_NLP_SERVER_HOST(自然语言模型服务)默认为本机启动 (非必填)
AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME 为默认的数据表名(必填)
设置完成后可通过下列命令进行测试(CMD切换至项目根目录下)
python ./backend/config.py
若配置成功则可看见输入的配置数据
#### 5. 打包前端 dist 文件 (这一步我已为你们做好,若不需二次开发可跳过)
5.1 安装 Vue 环境,下载 node.js 并配置环境,下载 npm 包管理器
5.2 cmd 进入 frontend 目录下,配置 cnpm :
npm install -g cnpm --registry=https://registry.npm.taobao.org
5.3 执行安装依赖包命令:
cnpm install
5.4 执行打包命令:
cnpm run build
若成功打包则会在项目根目录下生成 dist 文件夹
#### 6. 启动后端
// 切换至项目根目录下执行
pip install -r ./backend/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
// 启动后端 ( 默认5050端口 )
python ./backend/run.py
// 创建平台管理员帐号密码
python ./backend/createAdminUser.py
#### 7. 访问项目
现在就可以访问 http://127.0.0.1:5050/#/login 使用创建的管理员帐号密码进行登录
![平台登录界面2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/平台登录界面2.png)
### Linux 环境下 Docker 容器化部署
[点击进入 Docker 教程地址](https://www.runoob.com/docker/ubuntu-docker-install.html)
#### 0. 克隆项目
git clone https://github.com/amazingTest/Taisite-Platform.git
#### 1. 自然语言模型部署
sudo -i
docker pull shaoyuyishiwo/bertserver
docker run --name autotest-platform-bertserver -d shaoyuyishiwo/bertserver
#### 2. Mongo 数据库部署 (若已有现成数据库可用则可跳过此步)
2.1 启动数据库 & 数据挂载至宿主机
sudo -i
docker pull mongo
docker run --name autotest-platform-mongo -p 27017:27017 -v /data/db:/data/db -v /data/configdb:/data/configdb ``-d mongo
2.2 创建数据库帐号
docker exec -it autotest-platform-mongo /bin/bash
mongo
> use admin
switched to db admin
> db.createUser({user:"${USERNAME}",pwd:"${PASSWORD}",roles:["root"]})
Successfully added user: { "user" : "admin", "roles" : [ "root" ] }
2.3 数据库内存扩容(建议)
> db.adminCommand({setParameter:1, internalQueryExecMaxBlockingSortBytes:335544320})
{ "was" : 33554432, "ok" : 1 }
#### 3. 环境变量配置
// 编辑 /etc/profile 文件
sudo -i
vi /etc/profile
若出现警告则选择 (E)dit anyway (输入 E)
3.1 文本末端插入下列数据 (输入 i 则变为 insert 状态)
export AUTOTEST_PLATFORM_ENV=production
export AUTOTEST_PLATFORM_NLP_SERVER_HOST=${BERT_IPADRESS}
export AUTOTEST_PLATFORM_MONGO_HOST=${MONGO_HOST}
export AUTOTEST_PLATFORM_MONGO_PORT=${MONGO_PORT}
export AUTOTEST_PLATFORM_MONGO_USERNAME=${USERNAME}
export AUTOTEST_PLATFORM_MONGO_PASSWORD=${PASSWORD}
export AUTOTEST_PLATFORM_MONGO_DEFAULT_DBNAME=${DBNAME}
变量为动态值,部署者自行根据实际情况输入,DBNAME 值可任意自定义(数据库表名),其中 BERT_IPADRESS 和 MONGO_HOST 值可通过下列命令查询:
docker inspect autotest-platform-bertserver
docker inspect autotest-platform-mongo // 若使用了上面的步骤部署数据库
输出如下图所示:
![控制台输出1.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/控制台输出1.png)
3.2 插入完毕后点击 ESC 按钮、输入 :wq 后单击回车保存
3.3 执行下列命令后环境变量立即生效
source /etc/profile
#### 4. 启动项目
//在项目根目录下执行部署文件
sh deploy ${PORT}
其中 ${PORT} 变量填写项目访问端口即可,项目启动的同时也创建了管理员帐号密码,如下图所示:
![控制台输出2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/控制台输出2.png)
#### 5. 访问项目
浏览器访问部署服务器地址的 ${PORT}端口即可
![平台登录界面.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/平台登录界面.png)
#### EXTRA. 常见问题
下列输出代表 NLP模型 启动失败
![NLP部署失败.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/NLP部署失败.png)
解决步骤:
1.删除 ./backend/app/init.py 中的这段代码:
![不使用NLP模型方法指南1.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/不使用NLP模型方法指南1.png)
2.将 ./backend/testframe/interfaceTest/tester.py 中的下列代码修改成 pass:
![不使用NLP模型方法指南2.png](https://github.com/amazingTest/Taisite-Platform/blob/master/images/不使用NLP模型方法指南2.png)
完成后再启动项目时,就不会依赖于自然语言模型了~
## V . 泰斯特平台使用教程
平台主流程使用可参考[本篇博文中的正文部分](https://juejin.im/post/5cd0117be51d456e537ef3bd)
若想 **完整教程** 可关注下方微信号,回复 **优质教程**
![2D-Code](https://github.com/amazingTest/Taisite-Platform/blob/master/images/微信公众号.jpg)
**QQ 交流群号:728314402**
## VI . 泰斯特平台零距离体验(建议先看教程哦~)
因体验服务器配置问题,体验需关注下方公众号后回复 「体验地址」 获得:
![2D-Code](https://github.com/amazingTest/Taisite-Platform/blob/master/images/微信公众号.jpg)
## Ⅶ . 泰斯特带你成长
## 平台部署
如果你想:
[点我进入平台部署](https://mp.weixin.qq.com/s/bLyDWHCAPCshF8vmbSHtWw)
+ 熟悉平台的每一个最新改动
+ 了解人工智能与测试结合的最新动态
+ 打破测试水平的瓶颈
+ 泰斯特带你一起成长
## 平台教程
那么我欢迎你来加入(扫描下方海报中二维码) **我的星球** 一起问道技术巅峰
[点我进入平台教程](https://shimo.im/docs/8TqxG3Ttjvj9yT8T)
![我的星球](https://github.com/amazingTest/Taisite-Platform/blob/master/images/知识星球二维码.jpg)
## Ⅷ . 捐赠
......
......@@ -32,14 +32,14 @@ from utils.cron.cronManager import CronManager
cron_manager = CronManager()
cron_manager.start()
from utils.nlp.Nlper import Nlper
bert_ip = _config.get_nlp_server_host() if _config.get_nlp_server_host() else '127.0.0.1'
bert_client = BertClient(ip=bert_ip, timeout=10000)
nlper = Nlper(bert_client)
# from utils.nlp.Nlper import Nlper
# bert_ip = _config.get_nlp_server_host() if _config.get_nlp_server_host() else '127.0.0.1'
# bert_client = BertClient(ip=bert_ip, timeout=10000)
# nlper = Nlper(bert_client)
from models import project, host, caseSuite, testingCase, testReport, cronTab, mail, mailSender
from models import project, host, caseSuite, testingCase, testReport, cronTab, mail, mailSender, testDataStorage
from controllers import user
from controllers import project, host, caseSuite, testingCase, testReport, cronTab, mail, mailSender, webhook
from controllers import project, host, caseSuite, testingCase, testReport, cronTab, mail, mailSender, webhook, testDataStorage
......@@ -55,7 +55,8 @@ def add_cron(project_id):
trigger_type=filtered_data.get('triggerType'),
test_case_id_list=filtered_data.get('testCaseIdList'),
is_execute_forbiddened_case=filtered_data.get('isExecuteForbiddenedCase'),
run_date=filtered_data.get('runDate'))
run_date=filtered_data.get('runDate'),
cron_name=filtered_data.get('name'))
else:
cron = Cron(test_case_suite_id_list=filtered_data.get('testCaseSuiteIdList'),
test_domain=filtered_data.get('testDomain'),
......@@ -69,7 +70,9 @@ def add_cron(project_id):
trigger_type=filtered_data.get('triggerType'),
test_case_id_list=filtered_data.get('testCaseIdList'),
is_execute_forbiddened_case=filtered_data.get('isExecuteForbiddenedCase'),
seconds=filtered_data.get('interval'))
seconds=filtered_data.get('interval'),
cron_name=filtered_data.get('name'))
cron_id = cron_manager.add_cron(cron)
for key, value in filtered_data.items():
......
......@@ -34,13 +34,13 @@ def add_mail_sender(project_id):
@app.route('/api/project/<project_id>/mailSenderList/<sender_id>/updateMailSender', methods=['POST'])
@login_required
def update_mail_sender(project_id, host_id):
def update_mail_sender(project_id, sender_id):
try:
filtered_data = MailSender.filter_field(request.get_json())
for key, value in filtered_data.items():
MailSender.update({"_id": ObjectId(host_id)},
MailSender.update({"_id": ObjectId(sender_id)},
{'$set': {key: value}})
update_response = MailSender.update({"_id": ObjectId(host_id)},
update_response = MailSender.update({"_id": ObjectId(sender_id)},
{'$set': {'lastUpdateTime': datetime.datetime.utcnow()}})
if update_response["n"] == 0:
return jsonify({'status': 'failed', 'data': '未找到相应更新数据!'})
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app import app
from flask import Flask, jsonify, request, abort, send_file
from models.testDataStorage import TestDataStorage
from bson import ObjectId
from utils import common
from flask_login import login_required
import datetime
import ast
@app.route('/api/project/<project_id>/testDataStorageList', methods=['GET', 'POST'])
@login_required
def test_data_storage_list(project_id):
# TODO 性能优化
total_num, storages = common.get_total_num_and_arranged_data(TestDataStorage, request.args, fuzzy_fields=['name'])
return jsonify({'status': 'ok', 'data': {'totalNum': total_num, 'rows': storages}})
@app.route('/api/project/<project_id>/testDataStorageList/<storage_id>', methods=['GET'])
@login_required
def storage_detail(project_id, storage_id):
storage = TestDataStorage.find_one({'_id': ObjectId(storage_id)})
storage = common.format_response_in_dic(storage)
return jsonify({'status': 'ok', 'data': storage}) if storage else \
jsonify({'status': 'failed', 'data': '未找到测试数据仓库详情'})
@app.route('/api/project/<project_id>/addTestDataStorage', methods=['POST'])
@login_required
def add_storage(project_id):
request_data = request.get_json()
request_data["status"] = True
request_data["projectId"] = ObjectId(project_id)
request_data["createAt"] = datetime.datetime.utcnow()
request_data["lastUpdateTime"] = datetime.datetime.utcnow()
if 'dataMap' in request_data:
request_data['dataMap'] = ast.literal_eval(request_data['dataMap'])
if not isinstance(request_data['dataMap'], dict):
return jsonify({'status': 'failed', 'data': '数据字典必须为字典格式!'})
filtered_data = TestDataStorage.filter_field(request_data, use_set_default=True)
try:
TestDataStorage.insert(filtered_data)
return jsonify({'status': 'ok', 'data': '添加成功'})
except BaseException as e:
return jsonify({'status': 'failed', 'data': '添加失败 %s' % e})
@app.route('/api/project/<project_id>/testDataStorageList/<storage_id>/updateStorage', methods=['POST'])
@login_required
def update_storage(project_id, storage_id):
json_data = request.get_json()
if 'dataMap' in json_data:
json_data['dataMap'] = ast.literal_eval(json_data['dataMap'])
if not isinstance(json_data['dataMap'], dict):
return jsonify({'status': 'failed', 'data': '数据字典必须为字典格式!'})
try:
filtered_data = TestDataStorage.filter_field(json_data)
for key, value in filtered_data.items():
TestDataStorage.update({"_id": ObjectId(storage_id)},
{'$set': {key: value}})
update_response = TestDataStorage.update({"_id": ObjectId(storage_id)},
{'$set': {'lastUpdateTime': datetime.datetime.utcnow()}})
if update_response["n"] == 0:
return jsonify({'status': 'failed', 'data': '未找到相应更新数据!'})
return jsonify({'status': 'ok', 'data': '更新成功'})
except BaseException as e:
return jsonify({'status': 'failed', 'data': '更新失败: %s' % e})
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app import app
from flask import Flask, jsonify, request, abort
from flask import Flask, jsonify, request, abort, send_file
from models.testReport import TestReport
from bson import ObjectId
from utils import common
......@@ -13,13 +13,22 @@ from flask_login import login_required
def reports_list(project_id):
# TODO 性能优化
total_num, test_reports = common.get_total_num_and_arranged_data(TestReport, request.args)
for test_report in test_reports:
del test_report['testDetail']
return jsonify({'status': 'ok', 'data': {'totalNum': total_num, 'rows': test_reports}})
@app.route('/api/project/<project_id>/reportsList/<report_id>', methods=['GET'])
@login_required
def report_detail(project_id, report_id):
test_report = list(TestReport.find({'_id': ObjectId(report_id)}))
test_report = common.format_response_in_dic(test_report[0])
test_report = TestReport.find_one({'_id': ObjectId(report_id)})
test_report = common.format_response_in_dic(test_report)
return jsonify({'status': 'ok', 'data': test_report}) if test_report else \
jsonify({'status': 'failed', 'data': '未找到报告详情'})
@app.route('/api/project/<project_id>/reportsList/<report_id>/export', methods=['POST'])
@login_required
def export_report(project_id, report_id):
bytes_io = TestReport.get_test_report_excel_bytes_io(report_id)
return send_file(bytes_io, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
\ No newline at end of file
......@@ -10,6 +10,8 @@ from io import BytesIO
from models.testingCase import TestingCase
from models.caseSuite import CaseSuite
from models.testReport import TestReport
from models.project import Project
from models.testDataStorage import TestDataStorage
from bson import ObjectId
from utils import common
import pymongo
......@@ -35,6 +37,12 @@ def add_case(project_id, case_suite_id):
request_data["testCaseType"] = 'interfaceTest'
request_data["createAt"] = datetime.datetime.utcnow()
request_data["lastUpdateTime"] = datetime.datetime.utcnow()
if "checkResponseTime" in request_data:
request_data["checkResponseTime"] = float(request_data["checkResponseTime"])\
if request_data["checkResponseTime"] else None
if "checkHttpCode" in request_data:
request_data["checkHttpCode"] = str(request_data["checkHttpCode"]) \
if request_data["checkHttpCode"] else ""
filtered_data = TestingCase.filter_field(request_data, use_set_default=True)
try:
TestingCase.insert(filtered_data)
......@@ -92,7 +100,14 @@ def update_case(project_id, case_suite_id, case_id):
return jsonify({'status': 'failed', 'data': '请求参数数据格式不正确!: %s' % e})
try:
filtered_data = TestingCase.filter_field(request.get_json())
json_data = request.get_json()
if "checkResponseTime" in json_data:
json_data["checkResponseTime"] = float(json_data["checkResponseTime"]) \
if json_data["checkResponseTime"] else None
if "checkHttpCode" in json_data:
json_data["checkHttpCode"] = str(json_data["checkHttpCode"]) \
if json_data["checkHttpCode"] else ""
filtered_data = TestingCase.filter_field(json_data)
for key, value in filtered_data.items():
TestingCase.update({"_id": ObjectId(case_id)},
{'$set': {key: value}})
......@@ -132,6 +147,13 @@ def start_test():
else:
domain = request_data["domain"]
# 获取 global_vars_id
global_vars_id = request_data["globalVarsId"] if request_data.get('globalVarsId') else None
# 查找数据字典
global_vars = TestDataStorage.find_one({'_id': ObjectId(global_vars_id)}).get('dataMap', {})\
if global_vars_id else {}
if 'caseSuiteIdList' in request_data:
case_suite_id_list = request_data["caseSuiteIdList"]
......@@ -171,6 +193,7 @@ def start_test():
if len(testing_case_list) > 0:
project_id = testing_case_list[0]["projectId"]
project_name = Project.find_one({'_id': ObjectId(project_id)})['name']
else:
return jsonify({'status': 'failed', 'data': '请先「启用」接口测试用例'})
......@@ -190,11 +213,11 @@ def start_test():
if 'caseSuiteIdList' not in request_data and len(testing_case_list) == 1:
is_single_test = True
tester = tester(test_case_list=testing_case_list, domain=domain)
tester = tester(test_case_list=testing_case_list, domain=domain, global_vars=global_vars)
if not is_single_test:
try:
tester.execute_all_test_and_send_report(TestingCase, TestReport, project_id, executor_nick_name, execution_mode)
tester.execute_all_test_and_send_report(TestingCase, TestReport, project_id, executor_nick_name, execution_mode, project_name)
return jsonify({'status': 'ok', 'data': '测试已启动,稍后请留意自动化测试报告'})
except BaseException as e:
return jsonify({'status': 'failed', 'data': '测试启动失败: %s' % e})
......@@ -221,12 +244,15 @@ def start_test():
raw_data = {
"projectId": ObjectId(project_id),
"projectName": project_name,
"testCount": test_count,
"passCount": passed_count,
"failedCount": failed_count,
"passRate": passed_rate,
"comeFrom": execution_mode,
"executorNickName": executor_nick_name,
"totalTestSpendingTimeInSec": test_result_list[0]['spendingTimeInSec'],
"testDomain": domain,
"testDetail": test_result_list,
"createAt": datetime.datetime.utcnow()
}
......@@ -264,6 +290,7 @@ test_case_map = {
'headers': '请求头部',
'presendParams': '请求参数',
'checkHttpCode': '状态码校验',
'checkResponseTime': '耗时校验/s',
'checkResponseData': '正则校验',
'checkResponseSimilarity': '文本相似度校验',
'checkResponseNumber': '数值校验',
......@@ -481,7 +508,9 @@ def export_test_cases():
print(e)
return _case_info
export_testing_cases = map(export_case_format, map(add_case_suite_name, TestingCase.find(query)))
export_testing_cases = map(export_case_format, map(add_case_suite_name,
TestingCase.find(query).sort([('caseSuiteId', pymongo.ASCENDING),
('createAt', pymongo.ASCENDING)])))
bytes_io = BytesIO()
workbook = xlsxwriter.Workbook(bytes_io, {'in_memory': True})
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from app import db
from utils.mango import *
# 类名定义 collection
class TestDataStorage(Model):
class Meta:
database = db
collection = 'testDataStorage'
# 字段
name = StringField()
description = StringField()
# dataMap = ArrayField(field_name='dataMap', default=[{'key': '', 'value': '', '__value_type': ''}],
# expected_structure={'expectedTypeRange': [list],
# 'expectedValueRange': [{
# 'expectedTypeRange': [dict],
# 'expectedDict': {
# 'key': {'expectedTypeRange': [str]},
# 'value': {'expectedTypeRange': [str, dict, list, int, float, bool]},
# '__value_type': {'expectedTypeRange': [str]},
# }
# }]
# })
dataMap = DictField()
_id = ObjectIdField()
isDeleted = BooleanField(field_name='isDeleted', default=False)
projectId = ObjectIdField()
createAt = DateField()
status = BooleanField(field_name='status', default=True)
creatorNickName = StringField()
lastUpdateTime = DateField()
lastUpdatorNickName = StringField()
if __name__ == '__main__':
pass
\ No newline at end of file
......@@ -3,6 +3,47 @@
from app import db
from utils.mango import *
from utils import common
from utils.helpers import ExcelHelper
import xlsxwriter
from io import BytesIO
import ast
import datetime
from bson import ObjectId
test_report_summary_map = {
'projectName': '测试项目',
'testDomain': '测试环境',
'testCount': '用例总数',
'passCount': '通过数',
'failedCount': '失败数',
'passRate': '通过率',
'comeFrom': '报告来源',
'executorNickName': '执行人',
'createAt': '生成时间',
'totalTestSpendingTimeInSec': '总耗时/s'
}
# 使用 dic_get 定位数据
test_report_detail_map = {
"['testBaseInfo', 'name']": '用例名称',
"['testBaseInfo', 'requestMethod']": '请求方法',
"['testBaseInfo', 'url']": '请求地址',
"['testBaseInfo', 'headers']": '请求头',
"['testBaseInfo', 'cookies']": '请求Cookie',
"['testBaseInfo', 'presendParams']": '请求参数',
"['testBaseInfo', 'curl']": '复现 curl',
"['testBaseInfo', 'checkHttpCode']": '状态码校验',
"['responseHttpStatusCode']": '实际状态码',
"['testBaseInfo', 'checkResponseData']": '数据校验',
"['testBaseInfo', 'checkResponseNumber']": '数值校验',
"['testBaseInfo', 'checkResponseSimilarity']": '相似度校验',
"['responseData']": '实际数据',
"['testConclusion']": '测试结论',
"['testStartTime']": '测试开始时间',
"['testBaseInfo', 'checkResponseTime']": '耗时校验/s',
"['spendingTimeInSec']": '测试耗时/s',
}
# 类名定义 collection
......@@ -17,8 +58,11 @@ class TestReport(Model):
_id = ObjectIdField()
isDeleted = BooleanField(field_name='isDeleted', default=False)
projectId = ObjectIdField()
projectName = StringField()
testDomain = StringField()
createAt = DateField()
lastUpdateTime = DateField()
totalTestSpendingTimeInSec = FloatField()
testCount = IntField()
passCount = IntField()
failedCount = IntField()
......@@ -28,6 +72,97 @@ class TestReport(Model):
executorNickName = StringField()
cronId = StringField()
@classmethod
def get_test_report_excel_bytes_io(cls, report_id):
test_report = cls.find_one({'_id': ObjectId(report_id)})
test_report = common.format_response_in_dic(test_report)
bytes_io = BytesIO()
workbook = xlsxwriter.Workbook(bytes_io, {'in_memory': True})
summary_sheet = workbook.add_worksheet(u'测试报告概览')
summary_sheet.freeze_panes(1, 0)
detail_sheet = workbook.add_worksheet(u'测试报告详情')
detail_sheet.freeze_panes(1, 0)
# 设置测试报告表头 format
header_style = workbook.add_format()
header_style.set_bg_color("#00CCFF")
header_style.set_color("#FFFFFF")
header_style.set_bold()
header_style.set_border()
# 测试报告概览表头
for index, value in enumerate(test_report_summary_map.values()):
summary_sheet.write(0, index, value, header_style)
# 设置测试报告概览每列宽度
[ExcelHelper.ExcelSheetHelperFunctions.set_column_auto_width(summary_sheet, i)
for i in range(len(test_report_summary_map.values()))]
# 测试报告概览数据
for index, value in enumerate(test_report_summary_map.keys()):
summary_sheet.write(1, index, str(test_report.get(value, '(暂无此数据)')))
test_details = test_report['testDetail']
# 测试报告详情表头
for index, value in enumerate(test_report_detail_map.values()):
detail_sheet.write(0, index, value, header_style)
# 设置测试报告详情每列宽度
[ExcelHelper.ExcelSheetHelperFunctions.set_column_auto_width(detail_sheet, i)
for i in range(len(test_report_detail_map.values()))]
test_result_pass_style = workbook.add_format()
test_result_pass_style.set_bg_color("#00ff44")
test_result_pass_style.set_color("#FFFFFF")
test_result_pass_style.set_bold()
# test_result_pass_style.set_border()
test_result_failed_style = workbook.add_format()
test_result_failed_style.set_bg_color("#ff0026")
test_result_failed_style.set_color("#FFFFFF")
test_result_failed_style.set_bold()
# test_result_failed_style.set_border()
failed_curl_style = workbook.add_format()
failed_curl_style.set_bg_color("#ffee00")
# failed_curl_style.set_color("#FFFFFF")
failed_curl_style.set_bold()
# failed_curl_style.set_border()
# 测试报告详情数据
for index, locator in enumerate(test_report_detail_map.keys()):
locator = ast.literal_eval(locator)
for col_index, detail in enumerate(test_details):
if 'testConclusion' in str(locator):
test_result = str(common.dict_get(detail, locator))
if '测试通过' in test_result:
detail_sheet.write(col_index + 1, index,
test_result, test_result_pass_style)
else:
detail_sheet.write(col_index + 1, index,
test_result, test_result_failed_style)
elif 'curl' in str(locator):
test_result_status = str(common.dict_get(detail, ['status']))
if test_result_status == 'failed':
detail_sheet.write(col_index + 1, index,
str(common.dict_get(detail, locator)), failed_curl_style)
else:
detail_sheet.write(col_index + 1, index, str(common.dict_get(detail, locator)))
else:
pre_write_value = str(common.dict_get(detail, locator))
pre_write_value = '(空)' if pre_write_value == 'None' else pre_write_value
detail_sheet.write(col_index + 1, index, pre_write_value)
workbook.close()
bytes_io.seek(0)
return bytes_io
def __str__(self):
return "createAt:{}"\
.format(self.createAt)
......
......@@ -70,6 +70,7 @@ class TestingCase(Model):
status = BooleanField(field_name='status', default=False)
isClearCookie = BooleanField(field_name='isClearCookie', default=False)
checkHttpCode = StringField()
checkResponseTime = FloatField()
checkResponseData = ArrayField(field_name='checkResponseData', default=[{'regex': '', 'query': []}],
expected_structure={'expectedTypeRange': [list, type(None)],
'expectedValueRange': [{
......
......@@ -10,3 +10,4 @@ python-dateutil==2.7.3
xlrd==1.1.0
xlsxwriter==1.1.8
github_webhook==1.0.2
Faker==4.14.2
\ No newline at end of file
......@@ -2,4 +2,4 @@
from app import app
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5050)
\ No newline at end of file
app.run(debug=False, host='0.0.0.0', port=5050)
\ No newline at end of file
......@@ -11,6 +11,33 @@ from utils.sendReportEmail import send_report_email
from tzlocal import get_localzone
import string
from faker import Faker
def generate_curl(url, method='POST', headers=None, data=None):
curl_method = f' -X {method.upper()}'
curl_headers = ''
if isinstance(headers, dict): # {'Accept': 'application/json', 'Content-Type': 'application/json'}
for k, v in headers.items():
curl_headers += f" -H '{k}: {v}'"
elif isinstance(headers, list): # [{'name': 'Accept', 'value': 'application/json'}, {'name': 'Content-Type', 'value': 'application/json'}]
for header in headers:
curl_headers += f" -H '{header.get('name', '')}: {header.get('value', '')}'"
data = str(data).replace("'", '"') if data else None
curl_data = f" --data-binary '{data}'" if data else ''
curl = f"curl '{url}'" \
f"{curl_method}" \
f"{curl_headers}" \
f"{curl_data} "
return curl
def get_offset_between_local_and_utc():
ts = time.time()
......@@ -149,6 +176,7 @@ def format_js_dic_to_python_dic(query_dic):
def get_total_num_and_arranged_data(raw_model, query_dic, fuzzy_fields=None):
query_dic = query_dic.to_dict() if query_dic.to_dict() else {}
if fuzzy_fields is not None:
if not isinstance(fuzzy_fields, list):
......@@ -161,13 +189,7 @@ def get_total_num_and_arranged_data(raw_model, query_dic, fuzzy_fields=None):
query_dic[fuzzy_field] = re.compile(pre_compiled_str)
query_dic = format_js_dic_to_python_dic(query_dic)
raw_model_copy = copy.deepcopy(raw_model)
raw_model_data_copy = []
if not isinstance(raw_model_copy.find(), list):
try:
raw_model_data_copy = list(raw_model_copy.find({'isDeleted': {"$ne": True}}))
except BaseException as e:
raise TypeError('raw_data cannot convert to list: %s' % e)
if not isinstance(query_dic, dict):
raise TypeError('query_dic must be dict')
......@@ -183,9 +205,10 @@ def get_total_num_and_arranged_data(raw_model, query_dic, fuzzy_fields=None):
if not query_dic == {}:
query_dic['isDeleted'] = {"$ne": True}
total_num = len(list(raw_model_copy.find(query_dic)))
# total_num = len(list(raw_model_copy.find(query_dic)))
total_num = raw_model_copy.find(query_dic).count()
else:
total_num = len(raw_model_data_copy)
total_num = raw_model_copy.find(query_dic).count()
if sort_by and order and format_order(order):
sort_query = [(sort_by, format_order(order))]
else:
......@@ -203,6 +226,7 @@ def get_total_num_and_arranged_data(raw_model, query_dic, fuzzy_fields=None):
else:
arranged_data = raw_model_copy.find(query_dic).skip(skip).limit(size)
# TODO 性能优化
return total_num, list(map(format_response_in_dic, map(raw_model_copy.filter_field, arranged_data)))
......@@ -219,12 +243,15 @@ def dict_get(dic, locators, default=None):
'''
if not isinstance(dic, dict):
if isinstance(dic, str) and len(locators) == 1 and is_slice_expression(locators[0]):
slice_indexes = locators[0].split(':')
start_index = int(slice_indexes[0]) if slice_indexes[0] else None
end_index = int(slice_indexes[-1]) if slice_indexes[-1] else None
value = dic[start_index:end_index]
return value
if can_convert_to_str(dic):
dic = str(dic)
if len(locators) == 1 and is_slice_expression(locators[0]):
slice_indexes = locators[0].split(':')
start_index = int(slice_indexes[0]) if slice_indexes[0] else None
end_index = int(slice_indexes[-1]) if slice_indexes[-1] else None
value = dic[start_index:end_index]
return value
return dic
return default
if dic == {} or len(locators) < 1:
......@@ -306,6 +333,34 @@ def is_slice_expression(expression):
return False
def resolve_fake_var(pre_resolve_var, fake_var_regex='\${faker\..+\(.*\)}', locale='zh-CN'):
# usage:
# print(resolve_fake_var('这是随机出来的地址: 【${faker.address()}】 我厉害吧!'))
re_global_var = re.compile(fake_var_regex)
faker = Faker(locale)
def fake_var_repl(match_obj):
attribute_start_index = match_obj.group().index('.') + 1
attribute_end_index = match_obj.group().index('(')
_attribute = match_obj.group()[attribute_start_index: attribute_end_index]
_str_params = match_obj.group()[attribute_end_index + 1: -2]
_dict_params = str_params_2_dict(_str_params) if '=' in _str_params else {}
nonlocal faker
match_value = getattr(faker, _attribute)(**_dict_params)
# 将一些数字类型转成str,否则re.sub会报错, match_value可能是0!
match_value = str(match_value) if match_value is not None else match_value
return match_value if match_value else match_obj.group()
resolved_var = re.sub(pattern=re_global_var, string=pre_resolve_var, repl=fake_var_repl)
return resolved_var
def resolve_global_var(pre_resolve_var, global_var_dic, global_var_regex='\${.*?}',
match2key_sub_string_start_index=2, match2key_sub_string_end_index=-1):
......@@ -399,8 +454,7 @@ def frontend_date_str2datetime(input_str, timedelta=None):
def is_valid_email(email):
re_email = re.compile(r'^[a-zA-Z0-9\.]+@[a-zA-Z0-9]+\.[a-zA-Z]{3}$')
if re_email.match(email):
if re.match('^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$', email):
return True
else:
return False
......@@ -503,7 +557,7 @@ def validate_and_pre_process_import_test_case(case_suite_model, testing_case_mod
if is_transfer_ele2dict:
# TODO 判断优化: (默认值可能不是都存在)
_case_info[key] = case_attribute.default if not _case_info[key] else\
list(map(lambda x: ast.literal_eval(x.replace('\'', '\"')),
list(map(lambda x: ast.literal_eval(x.replace('"', r'\"').replace('\'', '\"')),
str(_case_info[key]).strip().split(';')))
elif attribute_type is dict:
_case_info[key] = ast.literal_eval(str(_case_info[key]).strip()) \
......@@ -572,8 +626,11 @@ def send_email(model, project_id, send_data):
mail_list = send_data.get('mail_list')
mail_title = send_data.get('mail_title')
mail_content = send_data.get('mail_content')
attachment_name = send_data.get('attachment_name', 'attachment')
attachment_content = send_data.get('attachment_content')
if send_report_email(user_name, pass_word, mail_list, mail_title, mail_content):
if send_report_email(user_name, pass_word, mail_list, mail_title, mail_content,
attachment_name, attachment_content):
return {'status': 'ok', 'data': '邮件发送成功'}
else:
return {'status': 'failed', 'data': '邮件发送失败'}
......@@ -586,6 +643,18 @@ def get_random_key(digit_num=16):
return keys
# TODO 暂时没有处理非字符串类型的值
def str_params_2_dict(str_params: str) -> dict:
str_params = str_params.replace(' ', '')
params = str_params.split(',')
dic = {}
for param in params:
equal_sign_index = param.index('=')
key = param[:equal_sign_index]
value = param[equal_sign_index + 1:]
dic[key] = value
return dic
if __name__ == '__main__':
pass
......@@ -98,6 +98,7 @@ class CronManager:
is_enterprise_wechat_notify = cron_info.get('isEnterpriseWechatNotify')
enterprise_wechat_access_token = cron_info.get('enterpriseWechatAccessToken')
enterprise_wechat_notify_strategy = cron_info.get('enterpriseWechatNotifyStrategy')
cron_name = cron_info.get('name')
try:
if trigger_type == 'interval' and int(interval) > 0:
......@@ -120,7 +121,8 @@ class CronManager:
enterprise_wechat_notify_strategy=enterprise_wechat_notify_strategy,
trigger_type=trigger_type, # 更新定时器时,此参数并没有真正起到作用, 仅修改展示字段
test_case_id_list=test_case_id_list,
run_date=run_date) # 更新定时器时,此参数并没有起到作用, 仅修改展示字段
run_date=run_date,
cron_name=cron_name) # 更新定时器时,此参数并没有起到作用, 仅修改展示字段
else:
cron = Cron(test_case_suite_id_list=test_case_suite_id_list,
is_execute_forbiddened_case=is_execute_forbiddened_case,
......@@ -134,7 +136,8 @@ class CronManager:
enterprise_wechat_notify_strategy=enterprise_wechat_notify_strategy,
trigger_type=trigger_type, # 更新定时器时,此参数并没有起到作用, 仅修改展示字段
test_case_id_list=test_case_id_list,
seconds=interval) # 更新定时器时,此参数并没有起到作用, 仅修改展示字段
seconds=interval, # 更新定时器时,此参数并没有起到作用, 仅修改展示字段
cron_name=cron_name)
# 玄学,更改job的时候必须改args,不能改func
self.scheduler.modify_job(job_id=cron_id, coalesce=True, args=[cron])
......
from xlsxwriter.worksheet import (
Worksheet, cell_number_tuple, cell_string_tuple)
from typing import Optional
class ExcelSheetHelperFunctions:
def __init__(self):
pass
@staticmethod
def set_column_auto_width(worksheet: Worksheet, column: int):
"""
Set the width automatically on a column in the `Worksheet`.
!!! Make sure you run this function AFTER having all cells filled in
the worksheet!
"""
max_width = ExcelSheetHelperFunctions.get_column_width(worksheet=worksheet, column=column)
if max_width is None:
return
elif max_width > 45:
max_width = 45
worksheet.set_column(first_col=column, last_col=column, width=max_width)
@staticmethod
def get_column_width(worksheet: Worksheet, column: int) -> Optional[int]:
"""Get the max column width in a `Worksheet` column."""
strings = getattr(worksheet, '_ts_all_strings', None)
if strings is None:
strings = worksheet._ts_all_strings = sorted(
worksheet.str_table.string_table,
key=worksheet.str_table.string_table.__getitem__)
lengths = set()
for row_id, columns_dict in worksheet.table.items(): # type: int, dict
data = columns_dict.get(column)
if not data:
continue
if type(data) is cell_string_tuple:
iter_length = len(strings[data.string])
if not iter_length:
continue
lengths.add(iter_length)
continue
if type(data) is cell_number_tuple:
iter_length = len(str(data.number))
if not iter_length:
continue
lengths.add(iter_length)
if not lengths:
return None
return int(max(lengths) * 2.5) # 中文给他加长一波
......@@ -7,12 +7,8 @@ from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
#发送邮件
#title:标题
#conen:内容
def send_report_email(username, password, mail_namelist, title, content, attachment=None):
def send_report_email(username, password, mail_namelist, title, content, attachment_name='attachment',
attachment_content=None):
try:
msg = MIMEMultipart()
msg['from'] = username
......@@ -21,11 +17,12 @@ def send_report_email(username, password, mail_namelist, title, content, attachm
txt = MIMEText(content, 'html', 'utf-8')
msg.attach(txt)
if attachment:
if attachment_name and attachment_content:
# 添加附件
part = MIMEApplication(open(attachment, 'rb').read())
part = MIMEApplication(attachment_content)
# part = MIMEApplication(open(attachment, 'rb').read())
part.add_header('Content-Disposition', 'attachment', filename=
attachment)
attachment_name)
msg.attach(part)
# 设置服务器、端口
......@@ -49,3 +46,5 @@ if __name__ == '__main__':
pass
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>autotest-platform</title><link href=/static/css/app.b9f22872b933349a211edcec5f288a6a.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.5dbfeeda77126c757cd8.js></script><script type=text/javascript src=/static/js/app.fe9e4e63df40224d9918.js></script></body></html>
\ No newline at end of file
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>autotest-platform</title><link href=/static/css/app.884423d85178d863bbbe128e72a976e0.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.95947f6a8c21de282d4f.js></script><script type=text/javascript src=/static/js/app.a95e515db29f31eabd46.js></script></body></html>
\ No newline at end of file
因为 它太大了无法显示 source diff 。你可以改为 查看blob
因为 它太大了无法显示 source diff 。你可以改为 查看blob
此差异已折叠。
{"version":3,"sources":["webpack:///webpack/bootstrap 36d5576ff2b654e1f4bf"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 36d5576ff2b654e1f4bf"],"sourceRoot":""}
\ No newline at end of file
{"version":3,"sources":["webpack:///webpack/bootstrap 11c2ede9fd2d57da8016"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 11c2ede9fd2d57da8016"],"sourceRoot":""}
\ No newline at end of file
此差异已折叠。
此差异已折叠。
import request from '@/utils/request'
export function getTestDataStorageList (project_id, params, header) {
return request({
url: `/api/project/${project_id}/testDataStorageList`,
headers: header,
params: params,
method: 'GET'
})
}
export function getTestDataStorageDetail (project_id, storage_id, params, header) {
return request({
url: `/api/project/${project_id}/testDataStorageList/${storage_id}`,
headers: header,
params: params,
method: 'GET'
})
}
export function addTestDataStorage (project_id, params, header) {
return request({
url: `/api/project/${project_id}/addTestDataStorage`,
headers: header,
method: 'POST',
data: params
})
}
export function updateTestDataStorage (project_id, storage_id, params, header) {
return request({
url: `/api/project/${project_id}/testDataStorageList/${storage_id}/updateStorage`,
method: 'POST',
headers: header,
data: params
})
}
......@@ -17,3 +17,14 @@ export function getReportDetail (project_id, report_id, header) {
method: 'GET'
})
}
export function exportReportDetail (project_id, report_id, header) {
return request({
url: `/api/project/${project_id}/reportsList/${report_id}/export`,
headers: header,
responseType: 'blob',
method: 'POST',
data: null
})
}
......@@ -28,7 +28,7 @@ Vue.component("header-view", Header)
router.beforeEach(async (to, from, next) => {
if (to.matched.length === 0) { //匹配前往的路由不存在
next('/aboutAuthor')
next('/interfaceProjectList')
return
}
......@@ -37,7 +37,7 @@ router.beforeEach(async (to, from, next) => {
//TODO 判断太草率
nickName !== '' ?
to.path.trim() === '/' ?
next('/aboutAuthor') :
next('/interfaceProjectList') :
next() :
next('/login')
} else {
......
......@@ -13,6 +13,7 @@ import addCaseApi from '@/views/interfaceTestProject/api/automation/AddCaseApi'
import updateCaseApi from '@/views/interfaceTestProject/api/automation/UpdateCaseApi'
import globalHost from '@/views/interfaceTestProject/global/Globalhost'
import globalMail from '@/views/interfaceTestProject/global/GlobalMail'
import globalTestDataStorage from '@/views/interfaceTestProject/global/GlobalTestDataStorage'
import projectReport from '@/views/interfaceTestProject/ProjectReport'
Vue.use(Router)
......@@ -65,6 +66,12 @@ export default new Router({
name: '邮箱配置',
leaf: true
},
{
path: '/interfaceTestProject/:project_id/GlobalTestDataStorage',
component: globalTestDataStorage,
name: '数据仓库',
leaf: true
},
{
path: '/interfaceTestProject/:project_id/CronList',
component: cronList,
......
<template>
<div style="margin:35px">
<section style="margin:35px">
<!--工具条-->
<el-col :span="24" class="toolbar" style="padding-bottom: 0px;">
......@@ -24,23 +24,24 @@
</el-table-column>
<el-table-column sortable='custom' prop="_id" label="报告编号" min-width="17%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="testCount" label="测试接口总数" min-width="8%" show-overflow-tooltip>
<el-table-column sortable='custom' prop="testCount" label="用例总数" min-width="8%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="passCount" label="通过的接口总数" min-width="8%" show-overflow-tooltip>
<el-table-column sortable='custom' prop="passCount" label="通过数" min-width="8%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="failedCount" label="失败的接口总数" min-width="8%" show-overflow-tooltip>
<el-table-column sortable='custom' prop="failedCount" label="失败数" min-width="8%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="passRate" label="通过率" min-width="8%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="comeFrom" label="执行方式" min-width="10%" show-overflow-tooltip>
<el-table-column sortable='custom' prop="comeFrom" label="报告来源" min-width="10%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="executorNickName" label="执行人" min-width="10%" show-overflow-tooltip>
</el-table-column>
<el-table-column sortable='custom' prop="createAt" label="报告生成时间" min-width="15%" show-overflow-tooltip>
</el-table-column>
<el-table-column label="操作" min-width="15%">
<el-table-column label="操作" min-width="20%">
<template slot-scope="scope">
<el-button size="small" class="el-icon-document" type="primary" @click="showReportDetail(scope.$index, scope.row)"> 查看详情</el-button>
<el-button size="small" class="el-icon-download" :loading="exportLoading" type="primary" @click="exportReportDetail(scope.$index, scope.row)"> 导出</el-button>
<!--<el-button type="danger" size="small" @click="handleDel(scope.$index, scope.row)">删除</el-button>-->
</template>
</el-table-column>
......@@ -63,8 +64,8 @@
<!--报告详情-->
<el-dialog title="报告详情" width="97%" v-loading="detailLoading" :visible.sync="isReportDetailShow" :close-on-click-modal="false">
<div style="height:700px;overflow:auto;overflow-x:hidden;border: 1px solid #e6e6e6">
<el-table height="700" :data="testReportDetail" :row-class-name="reportsTableRow" :header-cell-style="reportHeaderColor" :row-style="reportRowStyle" v-loading="listLoading" style="width: 100%;">
<el-table-column prop="testBaseInfo.name" label="用例名称" min-width="30%" sortable show-overflow-tooltip>
<el-table height="700" :data="testReportDetail" :row-class-name="reportsTableRow" :header-cell-style="reportHeaderColor" v-loading="listLoading" style="width: 100%;">
<el-table-column prop="testBaseInfo.name" label="用例名称" min-width="25%" sortable show-overflow-tooltip>
</el-table-column>
<el-table-column prop="testBaseInfo.requestMethod" label="请求方法" min-width="15%" sortable show-overflow-tooltip>
</el-table-column>
......@@ -92,21 +93,35 @@
</el-table-column>
<el-table-column prop="testStartTime" label="测试开始时间" min-width="25%" sortable show-overflow-tooltip>
</el-table-column>
<el-table-column prop="spendingTimeInSec" label="测试耗时/s" min-width="18%" sortable show-overflow-tooltip>
<el-table-column prop="testBaseInfo.checkResponseTime" label="耗时校验/s" min-width="17%" sortable show-overflow-tooltip>
</el-table-column>
<el-table-column prop="spendingTimeInSec" label="测试耗时/s" min-width="17%" sortable show-overflow-tooltip>
</el-table-column>
</el-table>
</div>
</el-dialog>
</div>
<a
class="js-download-doc"
:href="downloadLink"
:download="downloadName"
v-show="false"
/>
</section>
</template>
<script>
import {getReportList, getReportDetail} from "../../api/testReport";
import {getReportList, getReportDetail, exportReportDetail} from "../../api/testReport";
import moment from "moment";
export default {
data () {
return {
downloadLink: '',
downloadName: '',
listLoading: false,
detailLoading: false,
exportLoading: false,
isReportDetailShow: false,
testReports: [],
testReportDetail: [],
......@@ -159,6 +174,37 @@
self.listLoading = false;
})
},
// 导出报告详情
async exportReportDetail(index, row){
let self = this;
self.exportLoading = true;
let project_id = row.projectId
let report_id = row._id
let header = {
"Content-Type": "application/json"
};
exportReportDetail(project_id, report_id, header).then((res) => {
const blob = new Blob([res])
self.downloadLink = window.URL.createObjectURL(blob)
self.downloadName = `接口测试报告_${moment().format('YYYY-MM-DD-HH-mm-ss')}.xlsx`
self.$nextTick(() => {
self.$el.querySelector('.js-download-doc').click()
window.URL.revokeObjectURL(this.downloadLink)
self.exportLoading = false;
self.$message.success({
message: '报告导出成功',
center: true,
});
})
}).catch((error) => {
console.log(error)
self.$message.error({
message: '报告导出失败,请稍后重试哦~',
center: true,
});
self.exportLoading = false;
})
},
showReportDetail(index, row){
let self = this;
self.isReportDetailShow = true;
......@@ -175,6 +221,10 @@
item["testBaseInfo"]["cookies"] = JSON.stringify(item["testBaseInfo"]["cookies"]) || '';
item["testBaseInfo"]["presendParams"] = JSON.stringify(item["testBaseInfo"]["presendParams"]) || '';
if(item["testBaseInfo"]["presendParams"].length > 5000){
item["testBaseInfo"]["presendParams"] = item["testBaseInfo"]["presendParams"].substr(0, 5000) + '......(长度已超出限制,完整请求参数请前往数据库查看)'
}
// TODO 判断可优化
item["responseData"] ?
......@@ -183,6 +233,13 @@
item["responseData"] = item["responseData"]:
item["responseData"] = '(无任何数据)'
if(item["responseData"].length > 5000){
item["responseData"] = item["responseData"].substr(0, 5000) + '......(长度已超出限制,完整响应请前往数据库查看)'
}
if(item["testConclusion"].length > 5000){
item["testConclusion"] = item["testConclusion"].substr(0, 5000) + '......(长度已超出限制,完整响应请前往数据库查看)'
}
item["responseHttpStatusCode"] ?
item["responseHttpStatusCode"].toString().trim() === '' ?
......@@ -221,6 +278,9 @@
if (item["testBaseInfo"]["checkHttpCode"] === null || item["testBaseInfo"]["checkHttpCode"] === undefined){
item["testBaseInfo"]["checkHttpCode"] = '(无任何校验)'
}
if (item["testBaseInfo"]["checkResponseTime"] === null || item["testBaseInfo"]["checkResponseTime"] === undefined){
item["testBaseInfo"]["checkResponseTime"] = '(无任何校验)'
}
});
}
self.testReportDetail = testDetails
......@@ -294,16 +354,13 @@
})
},
// 修改table tr行的背景色
reportRowStyle({ row, rowIndex }){
reportsTableRow({ row, rowIndex }){
if (row.status.toString() === 'ok')
return 'background-color: #33CC00;color: #fff;font-weight: 500;'
return 'bg1-row reportsTableRow'
else {
return 'background-color: #FF3333;color: #fff;font-weight: 500;'
return 'bg2-row reportsTableRow'
}
},
reportsTableRow({ row, rowIndex }){
return 'reportsTableRow';
},
// 修改table header的背景色
reportHeaderColor({ row, column, rowIndex, columnIndex }) {
if (rowIndex === 0) {
......@@ -358,4 +415,14 @@
.el-table .el-table__body .reportsTableRow:hover>td {
background-color: deepskyblue;
}
.el-table .bg1-row{
background-color: #33CC00;
color: #fff;
font-weight: 500;
}
.el-table .bg2-row{
background-color: #FF3333;
color: #fff;
font-weight: 500;
}
</style>
......@@ -76,6 +76,14 @@
</el-tooltip>
</el-form-item>
<el-form-item style="float: right; margin-right: 116px">
<el-select v-model="testDataId" @visible-change='checkActiveStorage' clearable placeholder="测试数据" >
<el-option
v-for="(item,index) in TestDataStorage"
:key="index+''"
:label="item.name"
:value="item._id">
</el-option>
</el-select>
<el-select v-model="url" @visible-change='checkActiveEnv' clearable placeholder="测试环境" >
<el-option
v-for="(item,index) in Host"
......@@ -219,6 +227,7 @@
<script>
import {getCaseList, updateCase, copyCase, importTestCases, exportTestCases, addCase, getLastSingleTestResult} from '../../../../api/testCase';
import {getHosts} from '../../../../api/host';
import {getTestDataStorageList} from '../../../../api/testDataStorage';
import {startInterfaceTest} from "../../../../api/common";
import {getCookie} from '@/utils/cookie';
import moment from "moment";
......@@ -282,6 +291,8 @@
totalNum: 0,
url: '',
Host: [],
testDataId: '',
TestDataStorage: [],
apiListLoading: false,
sels: [],//列表选中列
TestResult: false,
......@@ -535,7 +546,8 @@
caseIdList: [row._id],
domain: self.url,
executorNickName: unescape(getCookie('nickName').replace(/\\u/g, '%u')),
executionMode: '单个用例手动执行'
executionMode: '单个用例手动执行',
globalVarsId : self.testDataId
};
startInterfaceTest(params, header).then((res) => {
self.testLoading = false;
......@@ -793,6 +805,28 @@
self.listLoading = false;
})
},
getTestDataStorage(){
let self = this;
let header = {};
let params = {status: true, projectId: self.$route.params.project_id};
getTestDataStorageList(self.$route.params.project_id, params, header).then((res) => {
let {status, data} = res
if (status === 'ok'){
self.TestDataStorage = data.rows
}
else{
self.$message.error({
message: data,
center: true,
})
}
}).catch((error) => {
self.$message.error({
message: '暂时无法获取 TestDataStorage,请稍后刷新重试~',
center: true,
});
})
},
getHost() {
let self = this;
let header = {};
......@@ -810,7 +844,7 @@
}
}).catch((error) => {
self.$message.error({
message: '暂时无法获取HOST,请稍后刷新重试~',
message: '暂时无法获取 HOST,请稍后刷新重试~',
center: true,
});
})
......@@ -824,6 +858,15 @@
})
}
},
checkActiveStorage: function(){
let self = this;
if (self.TestDataStorage.length < 1){
self.$message.warning({
message: '未找到「启用的数据字典」哦, 请前往「数据仓库」进行设置',
center: true,
})
}
},
//显示新增界面
handleAdd: function () {
this.addFormVisible = true;
......@@ -862,6 +905,7 @@
mounted() {
this.getCaseApiList();
this.getHost();
this.getTestDataStorage();
this.warmPrompt();
},
computed: {
......
......@@ -13,6 +13,16 @@
<el-form-item style="margin-left: 5px">
<el-button type="primary" class="el-icon-caret-right" :disabled="!hasSels" @click="executeTest"> 执行测试</el-button>
</el-form-item>
<el-select v-model="testDataId" @visible-change='checkActiveStorage' clearable placeholder="测试数据" >
<el-option
v-for="(item,index) in TestDataStorage"
:key="index+''"
:label="item.name"
:value="item._id">
</el-option>
</el-select>
<el-select v-model="testUrl" @visible-change="checkActiveEnv" clearable placeholder="测试环境" style="margin-left: 5px">
<el-option v-for="(item,index) in Host" :key="index+''" :label="item.name" :value="item.host"></el-option>
</el-select>
......@@ -155,6 +165,7 @@
import {getCaseSuiteList, addCaseSuite, updateCaseSuite, copyCaseSuite} from '../../../../api/caseSuite';
import {exportTestCases} from '../../../../api/testCase';
import {getHosts} from "../../../../api/host";
import {getTestDataStorageList} from '../../../../api/testDataStorage';
import {getCrons, addCron, pauseCron, resumeCron, delCron} from "../../../../api/cron";
import {startInterfaceTest} from "../../../../api/common";
import {getCookie} from "@/utils/cookie";
......@@ -189,6 +200,7 @@
currentPage: 1,
totalNum: 0,
testUrl: '',
testDataId: '',
listLoading: false,
copyLoading: false,
exportLoading: false,
......@@ -199,6 +211,7 @@
disDel: true,
TestStatus: false,
Host: [],
TestDataStorage: [],
hasSels: false,
editFormVisible: false,//编辑界面是否显示
editLoading: false,
......@@ -257,8 +270,10 @@
"domain": self.testUrl,
"caseSuiteIdList": ids,
"executorNickName": unescape(getCookie('nickName').replace(/\\u/g, '%u')),
"executionMode" : "用例组手动执行"
"executionMode" : "用例组手动执行",
"globalVarsId" : self.testDataId
});
startInterfaceTest(params, header).then((res) => {
self.listLoading = false;
self.update = false;
......@@ -301,6 +316,28 @@
}
});
},
getTestDataStorage(){
let self = this;
let header = {};
let params = {status: true, projectId: self.$route.params.project_id};
getTestDataStorageList(self.$route.params.project_id, params, header).then((res) => {
let {status, data} = res
if (status === 'ok'){
self.TestDataStorage = data.rows
}
else{
self.$message.error({
message: data,
center: true,
})
}
}).catch((error) => {
self.$message.error({
message: '暂时无法获取 TestDataStorage,请稍后刷新重试~',
center: true,
});
})
},
getHost() {
let self = this;
let header = {};
......@@ -726,6 +763,15 @@
})
}
},
checkActiveStorage: function(){
let self = this;
if (self.TestDataStorage.length < 1){
self.$message.warning({
message: '未找到「启用的数据字典」哦, 请前往「数据仓库」进行设置',
center: true,
})
}
},
// 修改table tr行的背景色
reportRowStyle({ row, rowIndex }){
if (!(row.status === true))
......@@ -741,6 +787,7 @@
mounted() {
this.getCaseSuites();
this.getHost();
this.getTestDataStorage();
},
computed: {
......
......@@ -181,6 +181,7 @@
<el-radio-group v-model="form.check">
<el-radio-button label="noCheck"><div>不校验</div></el-radio-button>
<el-radio-button label="checkHttpStatusCode"><div>HTTP状态校验</div></el-radio-button>
<el-radio-button label="checkResponseTime"><div>接口耗时校验</div></el-radio-button>
<el-radio-button label="checkJsonRegex"><div>JSON正则校验</div></el-radio-button>
<el-radio-button label="checkNumber"><div>数值校验</div></el-radio-button>
<el-radio-button label="checkSimilarity"><div>智能相似度校验</div></el-radio-button>
......@@ -192,6 +193,14 @@
<el-option v-for="(item,index) in httpCode" :key="index+''" :label="item.label" :value="item.value"></el-option>
</el-select>
</div>
<div v-show="showResponseTimeCheck">
<el-input
v-model="form.checkResponseTime"
placeholder="接口期望耗时/s(以内)"
type="number"
style="max-width:20%">
</el-input>
</div>
<div v-show="showJsonRegexCheck">
<el-collapse-item title="JSON正则校验" name="4">
<el-table :data="form.checkRegex" highlight-current-row>
......@@ -421,6 +430,7 @@
apiResponseLoading: false,
saveCorrelation: false,
showHttpCodeCheck: false,
showResponseTimeCheck: false,
showJsonRegexCheck: false,
showNumberCheck: false,
showSimilarityCheck: false,
......@@ -446,6 +456,7 @@
check: "checkSimilarity",
RegularParam: "",
checkHttp: "",
checkResponseTime: null,
},
FormRules: {
name : [{ required: true, message: '请输入名称', trigger: 'blur' }],
......@@ -511,11 +522,17 @@
lastUpdatorNickName: unescape(getCookie('nickName').replace(/\\u/g, '%u')) || '未知用户'
};
if (self.form.checkHttp){
params["checkHttpCode"] = self.form.checkHttp
}
params["checkHttpCode"] = self.form.checkHttp
params["checkResponseTime"] = parseFloat(self.form.checkResponseTime)
if (self.form.check === 'noCheck'){
params["checkHttpCode"] = null
params["checkResponseTime"] = null
params["checkResponseData"] = null
params["checkResponseNumber"] = null
params["checkResponseSimilarity"] = null
......@@ -649,7 +666,7 @@
});
}
self.form.checkHttp = data.checkHttpCode;
self.form.checkResponseTime = data.checkResponseTime;
if (data.checkResponseData === null || data.checkResponseData === undefined){
self.form.checkRegex = [{regex: "", query: []}]
}
......@@ -711,6 +728,7 @@
//注意:当观察的数据为对象或数组时,curVal和oldVal是相等的,因为这两个形参指向的是同一个数据对象
handler(curVal,oldVal){
if (curVal.check === 'noCheck') {
this.showResponseTimeCheck = false
this.showHttpCodeCheck = false
this.showJsonRegexCheck = false
this.showNumberCheck = false
......@@ -720,21 +738,31 @@
this.showJsonRegexCheck = false
this.showNumberCheck = false
this.showSimilarityCheck = false
this.showResponseTimeCheck = false
} else if (curVal.check === 'checkJsonRegex'){
this.showHttpCodeCheck = false
this.showJsonRegexCheck = true
this.showNumberCheck = false
this.showSimilarityCheck = false
this.showResponseTimeCheck = false
} else if (curVal.check === 'checkNumber'){
this.showHttpCodeCheck = false
this.showJsonRegexCheck = false
this.showNumberCheck = true
this.showSimilarityCheck = false
this.showResponseTimeCheck = false
} else if (curVal.check === 'checkSimilarity'){
this.showHttpCodeCheck = false
this.showJsonRegexCheck = false
this.showNumberCheck = false
this.showSimilarityCheck = true
this.showResponseTimeCheck = false
} else if (curVal.check === 'checkResponseTime'){
this.showHttpCodeCheck = false
this.showJsonRegexCheck = false
this.showNumberCheck = false
this.showSimilarityCheck = false
this.showResponseTimeCheck = true
}
},
deep:true
......
......@@ -117,10 +117,10 @@
<el-dialog title="发件人配置" :visible.sync="ConfigFormVisible" :close-on-click-modal="false" style="width: 60%; left: 20%">
<el-form :inline="true" :model="ConfigForm" label-width="100px" :rules="ConfigFormRules" ref="ConfigForm">
<el-form-item label="发件人邮箱" prop="username">
<el-input v-model.trim="ConfigForm.username" auto-complete="off"></el-input>
<el-input placeholder="目前仅支持 QQ 邮箱哦~" v-model.trim="ConfigForm.username" auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱授权码" prop='password'>
<el-input type="password" v-model.trim="ConfigForm.password" auto-complete="off"></el-input>
<el-input placeholder="目前仅支持 QQ 邮箱哦~" type="password" v-model.trim="ConfigForm.password" auto-complete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button :disabled="isMailSenderChecked" type="info" @click.native="testMailSender" :loading="testMailSenderLoading">请先验证</el-button>
......