2023年3月14日初步完成了UI自动化测试框架的搭建
This commit is contained in:
parent
0d88c95962
commit
f5d9b42ad3
|
@ -0,0 +1,48 @@
|
||||||
|
*.log
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
out
|
||||||
|
gen
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
.pyre/
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
[[source]]
|
||||||
|
url = "http://173.15.15.82:8081/repository/group-pypi/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
name = "pypi"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
jsonpath = "==0.82"
|
||||||
|
openpyxl = "==3.0.9"
|
||||||
|
pytest = "==6.2.5"
|
||||||
|
pyyaml = "==6.0"
|
||||||
|
requests = "==2.26.0"
|
||||||
|
loguru = "*"
|
||||||
|
click = "*"
|
||||||
|
pytest-rerunfailures = "*"
|
||||||
|
faker = "*"
|
||||||
|
deepdiff = "*"
|
||||||
|
pymysql = "*"
|
||||||
|
yagmail = "*"
|
||||||
|
selenium = "*"
|
||||||
|
pyautogui = "*"
|
||||||
|
pywinauto = "*"
|
||||||
|
pytest-html = "==2.1.1"
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.9"
|
|
@ -0,0 +1,716 @@
|
||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "3df5e4a40a747e5304b47b134642284160a598fbdefc0f9788261b563b469aef"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.9"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "http://173.15.15.82:8081/repository/group-pypi/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"async-generator": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b",
|
||||||
|
"sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==1.10"
|
||||||
|
},
|
||||||
|
"atomicwrites": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==1.4.1"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
|
||||||
|
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==22.2.0"
|
||||||
|
},
|
||||||
|
"cachetools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:13dfddc7b8df938c21a940dfa6557ce6e94a2f1cdfa58eb90c805721d58f2c14",
|
||||||
|
"sha256:429e1a1e845c008ea6c85aa35d4b98b65d6a9763eeef3e37e92728a12d1de9d4"
|
||||||
|
],
|
||||||
|
"markers": "python_version ~= '3.7'",
|
||||||
|
"version": "==5.3.0"
|
||||||
|
},
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
|
||||||
|
"sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==2022.12.7"
|
||||||
|
},
|
||||||
|
"cffi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5",
|
||||||
|
"sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef",
|
||||||
|
"sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104",
|
||||||
|
"sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426",
|
||||||
|
"sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405",
|
||||||
|
"sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375",
|
||||||
|
"sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a",
|
||||||
|
"sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e",
|
||||||
|
"sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc",
|
||||||
|
"sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf",
|
||||||
|
"sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185",
|
||||||
|
"sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497",
|
||||||
|
"sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3",
|
||||||
|
"sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35",
|
||||||
|
"sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c",
|
||||||
|
"sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83",
|
||||||
|
"sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21",
|
||||||
|
"sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca",
|
||||||
|
"sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984",
|
||||||
|
"sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac",
|
||||||
|
"sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd",
|
||||||
|
"sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee",
|
||||||
|
"sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a",
|
||||||
|
"sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2",
|
||||||
|
"sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192",
|
||||||
|
"sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7",
|
||||||
|
"sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585",
|
||||||
|
"sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f",
|
||||||
|
"sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e",
|
||||||
|
"sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27",
|
||||||
|
"sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b",
|
||||||
|
"sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e",
|
||||||
|
"sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e",
|
||||||
|
"sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d",
|
||||||
|
"sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c",
|
||||||
|
"sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415",
|
||||||
|
"sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82",
|
||||||
|
"sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02",
|
||||||
|
"sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314",
|
||||||
|
"sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325",
|
||||||
|
"sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c",
|
||||||
|
"sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3",
|
||||||
|
"sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914",
|
||||||
|
"sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045",
|
||||||
|
"sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d",
|
||||||
|
"sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9",
|
||||||
|
"sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5",
|
||||||
|
"sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2",
|
||||||
|
"sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c",
|
||||||
|
"sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3",
|
||||||
|
"sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2",
|
||||||
|
"sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8",
|
||||||
|
"sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d",
|
||||||
|
"sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d",
|
||||||
|
"sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9",
|
||||||
|
"sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162",
|
||||||
|
"sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76",
|
||||||
|
"sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4",
|
||||||
|
"sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e",
|
||||||
|
"sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9",
|
||||||
|
"sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6",
|
||||||
|
"sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b",
|
||||||
|
"sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01",
|
||||||
|
"sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"
|
||||||
|
],
|
||||||
|
"markers": "os_name == 'nt' and implementation_name != 'pypy'",
|
||||||
|
"version": "==1.15.1"
|
||||||
|
},
|
||||||
|
"charset-normalizer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||||
|
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==2.0.12"
|
||||||
|
},
|
||||||
|
"click": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
|
||||||
|
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==8.1.3"
|
||||||
|
},
|
||||||
|
"colorama": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
|
||||||
|
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==0.4.6"
|
||||||
|
},
|
||||||
|
"comtypes": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5d7caf6d3a86d51ddfc53e4548cae2dceee1b46672f8bd59679711dd01a934f2",
|
||||||
|
"sha256:7cdbe2dc65fe105eea97be04dc50ee3178747bcb64d802d7c0832416d75a87bd"
|
||||||
|
],
|
||||||
|
"version": "==1.1.14"
|
||||||
|
},
|
||||||
|
"cssselect": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc",
|
||||||
|
"sha256:da1885f0c10b60c03ed5eccbb6b68d6eff248d91976fcde348f395d54c9fd35e"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"cssutils": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:30c72f3a5c5951a11151640600aae7b3bf10e4c0d5c87f5bc505c2cd4a26e0c2",
|
||||||
|
"sha256:f7dcd23c1cec909fdf3630de346e1413b7b2555936dec14ba2ebb9913bf0818e"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==2.6.0"
|
||||||
|
},
|
||||||
|
"deepdiff": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:a02aaa8171351eba675cff5f795ec7a90987f86ad5449553308d4e18df57dc3d",
|
||||||
|
"sha256:d83b06e043447d6770860a635abecb46e849b0494c43ced2ecafda7628c7ce72"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.2.3"
|
||||||
|
},
|
||||||
|
"et-xmlfile": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c",
|
||||||
|
"sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
|
"exceptiongroup": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e",
|
||||||
|
"sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"
|
||||||
|
],
|
||||||
|
"markers": "python_version < '3.11'",
|
||||||
|
"version": "==1.1.1"
|
||||||
|
},
|
||||||
|
"faker": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:51f37ff9df710159d6d736d0ba1c75e063430a8c806b91334d7794305b5a6114",
|
||||||
|
"sha256:5aaa16fa9cfde7d117eef70b6b293a705021e57158f3fa6b44ed1b70202d2065"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==17.6.0"
|
||||||
|
},
|
||||||
|
"h11": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
|
||||||
|
"sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==0.14.0"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
|
||||||
|
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==3.4"
|
||||||
|
},
|
||||||
|
"iniconfig": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
|
||||||
|
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==2.0.0"
|
||||||
|
},
|
||||||
|
"jsonpath": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:46d3fd2016cd5b842283d547877a02c418a0fe9aa7a6b0ae344115a2c990fef4"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.82"
|
||||||
|
},
|
||||||
|
"loguru": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c",
|
||||||
|
"sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.6.0"
|
||||||
|
},
|
||||||
|
"lxml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7",
|
||||||
|
"sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726",
|
||||||
|
"sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03",
|
||||||
|
"sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140",
|
||||||
|
"sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a",
|
||||||
|
"sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05",
|
||||||
|
"sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03",
|
||||||
|
"sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419",
|
||||||
|
"sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4",
|
||||||
|
"sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e",
|
||||||
|
"sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67",
|
||||||
|
"sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50",
|
||||||
|
"sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894",
|
||||||
|
"sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf",
|
||||||
|
"sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947",
|
||||||
|
"sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1",
|
||||||
|
"sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd",
|
||||||
|
"sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3",
|
||||||
|
"sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92",
|
||||||
|
"sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3",
|
||||||
|
"sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457",
|
||||||
|
"sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74",
|
||||||
|
"sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf",
|
||||||
|
"sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1",
|
||||||
|
"sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4",
|
||||||
|
"sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975",
|
||||||
|
"sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5",
|
||||||
|
"sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe",
|
||||||
|
"sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7",
|
||||||
|
"sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1",
|
||||||
|
"sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2",
|
||||||
|
"sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409",
|
||||||
|
"sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f",
|
||||||
|
"sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f",
|
||||||
|
"sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5",
|
||||||
|
"sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24",
|
||||||
|
"sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e",
|
||||||
|
"sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4",
|
||||||
|
"sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a",
|
||||||
|
"sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c",
|
||||||
|
"sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de",
|
||||||
|
"sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f",
|
||||||
|
"sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b",
|
||||||
|
"sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5",
|
||||||
|
"sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7",
|
||||||
|
"sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a",
|
||||||
|
"sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c",
|
||||||
|
"sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9",
|
||||||
|
"sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e",
|
||||||
|
"sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab",
|
||||||
|
"sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941",
|
||||||
|
"sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5",
|
||||||
|
"sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45",
|
||||||
|
"sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7",
|
||||||
|
"sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892",
|
||||||
|
"sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746",
|
||||||
|
"sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c",
|
||||||
|
"sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53",
|
||||||
|
"sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe",
|
||||||
|
"sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184",
|
||||||
|
"sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38",
|
||||||
|
"sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df",
|
||||||
|
"sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9",
|
||||||
|
"sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b",
|
||||||
|
"sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2",
|
||||||
|
"sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0",
|
||||||
|
"sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda",
|
||||||
|
"sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b",
|
||||||
|
"sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5",
|
||||||
|
"sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380",
|
||||||
|
"sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33",
|
||||||
|
"sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8",
|
||||||
|
"sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1",
|
||||||
|
"sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889",
|
||||||
|
"sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9",
|
||||||
|
"sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f",
|
||||||
|
"sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==4.9.2"
|
||||||
|
},
|
||||||
|
"mouseinfo": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7"
|
||||||
|
],
|
||||||
|
"version": "==0.1.3"
|
||||||
|
},
|
||||||
|
"openpyxl": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:40f568b9829bf9e446acfffce30250ac1fa39035124d55fc024025c41481c90f",
|
||||||
|
"sha256:8f3b11bd896a95468a4ab162fc4fcd260d46157155d1f8bfaabb99d88cfcf79f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.0.9"
|
||||||
|
},
|
||||||
|
"ordered-set": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562",
|
||||||
|
"sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==4.1.0"
|
||||||
|
},
|
||||||
|
"orjson": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:010e2970ec9e826c332819e0da4b14b29b19641da0f1a6af4cec91629ef9b988",
|
||||||
|
"sha256:0110970aed35dec293f30ed1e09f8604afd5d15c5ef83de7f6c427619b3ba47b",
|
||||||
|
"sha256:0295a7bfd713fa89231fd0822c995c31fc2343c59a1d13aa1b8b6651335654f5",
|
||||||
|
"sha256:06180014afcfdc167ca984b312218aa62ce20093965c437c5f9166764cb65ef7",
|
||||||
|
"sha256:109b539ce5bf60a121454d008fa67c3b67e5a3249e47d277012645922cf74bd0",
|
||||||
|
"sha256:188ed9f9a781333ad802af54c55d5a48991e292239aef41bd663b6e314377eb8",
|
||||||
|
"sha256:1a1a8f4980059f48483782c608145b0f74538c266e01c183d9bcd9f8b71dbada",
|
||||||
|
"sha256:1c19f47b35b9966a3abadf341b18ee4a860431bf2b00fd8d58906d51cf78aa70",
|
||||||
|
"sha256:1dee503c6c1a0659c5b46f5f39d9ca9d3657b11ca8bb4af8506086df416887d9",
|
||||||
|
"sha256:226bfc1da2f21ee74918cee2873ea9a0fec1a8830e533cb287d192d593e99d02",
|
||||||
|
"sha256:2e8c430d82b532c5ab95634e034bbf6ca7432ffe175a3e63eadd493e00b3a555",
|
||||||
|
"sha256:366cc75f7e09106f9dac95a675aef413367b284f25507d21e55bd7f45f445e80",
|
||||||
|
"sha256:3ffaabb380cd0ee187b4fc362516df6bf739808130b1339445c7d8878fca36e7",
|
||||||
|
"sha256:403c8c84ac8a02c40613b0493b74d5256379e65196d39399edbf2ed3169cbeb5",
|
||||||
|
"sha256:41244431ba13f2e6ef22b52c5cf0202d17954489f4a3c0505bd28d0e805c3546",
|
||||||
|
"sha256:4f733062d84389c32c0492e5a4929056fac217034a94523debe0430bcc602cda",
|
||||||
|
"sha256:51b275475d4e36118b65ad56f9764056a09d985c5d72e64579bf8816f1356a5e",
|
||||||
|
"sha256:5bb32259ea22cc9dd47a6fdc4b8f9f1e2f798fcf56c7c1122a7df0f4c5d33bf3",
|
||||||
|
"sha256:5d88837002c5a8af970745b8e0ca1b0fdb06aafbe7f1279e110d338ea19f3d23",
|
||||||
|
"sha256:63144d27735f3b60f079f247ac9a289d80dfe49a7f03880dfa0c0ba64d6491d5",
|
||||||
|
"sha256:697abde7350fb8076d44bcb6b4ab3ce415ae2b5a9bb91efc460e5ab0d96bb5d3",
|
||||||
|
"sha256:78604d3acfd7cd502f6381eea0c42281fe2b74755b334074ab3ebc0224100be1",
|
||||||
|
"sha256:7a3ab1a473894e609b6f1d763838c6689ba2b97620c256a32c4d9f10595ac179",
|
||||||
|
"sha256:7bd4fd37adb03b1f2a1012d43c9f95973a02164e131dfe3ff804d7e180af5653",
|
||||||
|
"sha256:7d6ac5f8a2a17095cd927c4d52abbb38af45918e0d3abd60fb50cfd49d71ae24",
|
||||||
|
"sha256:8460c8810652dba59c38c80d27c325b5092d189308d8d4f3e688dbd8d4f3b2dc",
|
||||||
|
"sha256:84d154d07e8b17d97e990d5d710b719a031738eb1687d8a05b9089f0564ff3e0",
|
||||||
|
"sha256:89dc786419e1ce2588345f58dd6a434e6728bce66b94989644234bcdbe39b603",
|
||||||
|
"sha256:9e432c6c9c8b97ad825276d5795286f7cc9689f377a97e3b7ecf14918413303f",
|
||||||
|
"sha256:a16273d77db746bb1789a2bbfded81148a60743fd6f9d5185e02d92e3732fa18",
|
||||||
|
"sha256:ad02e9102d4ba67db30a136e631e32aeebd1dce26c9f5942a457b02df131c5d0",
|
||||||
|
"sha256:ad4d441fbde4133af6fee37f67dbf23181b9c537ecc317346ec8c3b4c8ec7705",
|
||||||
|
"sha256:b20f29fa8371b8023f1791df035a2c3ccbd98baa429ac3114fc104768f7db6f8",
|
||||||
|
"sha256:cc4fa83831f42ce5c938f8cefc2e175fa1df6f661fdeaba3badf26d2b8cfcf73",
|
||||||
|
"sha256:cc52f58c688cb10afd810280e450f56fbcb27f52c053463e625c8335c95db0dc",
|
||||||
|
"sha256:d60304172a33705ce4bd25a6261ab84bed2dab0b3d3b79672ea16c7648af4832",
|
||||||
|
"sha256:dbcfcec2b7ac52deb7be3685b551addc28ee8fa454ef41f8b714df6ba0e32a27",
|
||||||
|
"sha256:e1a0e5504a5fc86083cc210c6946e8d61e13fe9f1d7a7bf81b42f7050a49d4fb",
|
||||||
|
"sha256:e7129a6847f0494aa1427167486ef6aea2e835ba05f6c627df522692ee228f65",
|
||||||
|
"sha256:e75c11023ac29e29fd3e75038d0e8dd93f9ea24d7b9a5e871967a8921a88df24",
|
||||||
|
"sha256:ee519964a5a0efb9633f38b1129fd242807c5c57162844efeeaab1c8de080051",
|
||||||
|
"sha256:f98c82850b7b4b7e27785ca43706fa86c893cdb88d54576bbb9b0d9c1070e421",
|
||||||
|
"sha256:feb32aaaa34cf2f891eb793ad320d4bb6731328496ae59b6c9eb1b620c42b529",
|
||||||
|
"sha256:ff60187d1b7e0bfab376b6002b08c560b7de06c87cf3a8ac639ecf58f84c5f3b"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==3.8.7"
|
||||||
|
},
|
||||||
|
"outcome": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672",
|
||||||
|
"sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"packaging": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
|
||||||
|
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==23.0"
|
||||||
|
},
|
||||||
|
"pluggy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
|
||||||
|
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.6'",
|
||||||
|
"version": "==1.0.0"
|
||||||
|
},
|
||||||
|
"premailer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a",
|
||||||
|
"sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2"
|
||||||
|
],
|
||||||
|
"version": "==3.10.0"
|
||||||
|
},
|
||||||
|
"py": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
|
||||||
|
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||||
|
"version": "==1.11.0"
|
||||||
|
},
|
||||||
|
"pyautogui": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:d31de8f712218d90be7fc98091fce1a12a3e9196e0c814eb9afd73bb2ec97035"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.9.53"
|
||||||
|
},
|
||||||
|
"pycparser": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
|
||||||
|
"sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
|
||||||
|
],
|
||||||
|
"version": "==2.21"
|
||||||
|
},
|
||||||
|
"pygetwindow": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688"
|
||||||
|
],
|
||||||
|
"version": "==0.0.9"
|
||||||
|
},
|
||||||
|
"pymsgbox": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2194227de8bff7a3d6da541848705a155dcbb2a06ee120d9f280a1d7f51263ff"
|
||||||
|
],
|
||||||
|
"version": "==1.0.9"
|
||||||
|
},
|
||||||
|
"pymysql": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
|
||||||
|
"sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.0.2"
|
||||||
|
},
|
||||||
|
"pyperclip": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"
|
||||||
|
],
|
||||||
|
"version": "==1.8.2"
|
||||||
|
},
|
||||||
|
"pyrect": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78"
|
||||||
|
],
|
||||||
|
"version": "==0.2.0"
|
||||||
|
},
|
||||||
|
"pyscreeze": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4428600ed19b30cd3f4b5d83767d198fc1dbae7439eecf9bd795445c009b67ae"
|
||||||
|
],
|
||||||
|
"version": "==0.1.28"
|
||||||
|
},
|
||||||
|
"pysocks": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299",
|
||||||
|
"sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5",
|
||||||
|
"sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
|
||||||
|
],
|
||||||
|
"version": "==1.7.1"
|
||||||
|
},
|
||||||
|
"pytest": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
|
||||||
|
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.2.5"
|
||||||
|
},
|
||||||
|
"pytest-html": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6a4ac391e105e391208e3eb9bd294a60dd336447fd8e1acddff3a6de7f4e57c5",
|
||||||
|
"sha256:9e4817e8be8ddde62e8653c8934d0f296b605da3d2277a052f762c56a8b32df2"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.1.1"
|
||||||
|
},
|
||||||
|
"pytest-metadata": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:acb739f89fabb3d798c099e9e0c035003062367a441910aaaf2281bc1972ee14",
|
||||||
|
"sha256:fcc653f65fe3035b478820b5284fbf0f52803622ee3f60a2faed7a7d3ba1f41e"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
||||||
|
"version": "==2.0.4"
|
||||||
|
},
|
||||||
|
"pytest-rerunfailures": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:55611661e873f1cafa384c82f08d07883954f4b76435f4b8a5b470c1954573de",
|
||||||
|
"sha256:d21fe2e46d9774f8ad95f1aa799544ae95cac3a223477af94aa985adfae92b7e"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==11.1.2"
|
||||||
|
},
|
||||||
|
"python-dateutil": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
||||||
|
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==2.8.2"
|
||||||
|
},
|
||||||
|
"pytweening": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8533282cf70b31de8a0499e1cf420930b0013c787118872b2ec899382792e2e6"
|
||||||
|
],
|
||||||
|
"version": "==1.0.4"
|
||||||
|
},
|
||||||
|
"pywin32": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:109f98980bfb27e78f4df8a51a8198e10b0f347257d1e265bb1a32993d0c973d",
|
||||||
|
"sha256:13362cc5aa93c2beaf489c9c9017c793722aeb56d3e5166dadd5ef82da021fe1",
|
||||||
|
"sha256:19ca459cd2e66c0e2cc9a09d589f71d827f26d47fe4a9d09175f6aa0256b51c2",
|
||||||
|
"sha256:326f42ab4cfff56e77e3e595aeaf6c216712bbdd91e464d167c6434b28d65990",
|
||||||
|
"sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116",
|
||||||
|
"sha256:48d8b1659284f3c17b68587af047d110d8c44837736b8932c034091683e05863",
|
||||||
|
"sha256:4ecd404b2c6eceaca52f8b2e3e91b2187850a1ad3f8b746d0796a98b4cea04db",
|
||||||
|
"sha256:50768c6b7c3f0b38b7fb14dd4104da93ebced5f1a50dc0e834594bff6fbe1271",
|
||||||
|
"sha256:56d7a9c6e1a6835f521788f53b5af7912090674bb84ef5611663ee1595860fc7",
|
||||||
|
"sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478",
|
||||||
|
"sha256:742eb905ce2187133a29365b428e6c3b9001d79accdc30aa8969afba1d8470f4",
|
||||||
|
"sha256:9d968c677ac4d5cbdaa62fd3014ab241718e619d8e36ef8e11fb930515a1e918",
|
||||||
|
"sha256:9dd98384da775afa009bc04863426cb30596fd78c6f8e4e2e5bbf4edf8029504",
|
||||||
|
"sha256:a55db448124d1c1484df22fa8bbcbc45c64da5e6eae74ab095b9ea62e6d00496"
|
||||||
|
],
|
||||||
|
"version": "==305"
|
||||||
|
},
|
||||||
|
"pywinauto": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:931ce622d7f402b1892ab472987a1332e4c0681bf87e106f798390d16ca95e58",
|
||||||
|
"sha256:de23f1e977cc51e7eddd95c8f365710343136433968d1e2ad377962d6bd6540a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.6.8"
|
||||||
|
},
|
||||||
|
"pyyaml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf",
|
||||||
|
"sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293",
|
||||||
|
"sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b",
|
||||||
|
"sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57",
|
||||||
|
"sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b",
|
||||||
|
"sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4",
|
||||||
|
"sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07",
|
||||||
|
"sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba",
|
||||||
|
"sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9",
|
||||||
|
"sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287",
|
||||||
|
"sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513",
|
||||||
|
"sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
|
||||||
|
"sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782",
|
||||||
|
"sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0",
|
||||||
|
"sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92",
|
||||||
|
"sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f",
|
||||||
|
"sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2",
|
||||||
|
"sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc",
|
||||||
|
"sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1",
|
||||||
|
"sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c",
|
||||||
|
"sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86",
|
||||||
|
"sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4",
|
||||||
|
"sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c",
|
||||||
|
"sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34",
|
||||||
|
"sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b",
|
||||||
|
"sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d",
|
||||||
|
"sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c",
|
||||||
|
"sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb",
|
||||||
|
"sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7",
|
||||||
|
"sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737",
|
||||||
|
"sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3",
|
||||||
|
"sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d",
|
||||||
|
"sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358",
|
||||||
|
"sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53",
|
||||||
|
"sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78",
|
||||||
|
"sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803",
|
||||||
|
"sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a",
|
||||||
|
"sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f",
|
||||||
|
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
|
||||||
|
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==6.0"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||||
|
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.26.0"
|
||||||
|
},
|
||||||
|
"selenium": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:bd04eb41395605d9b2b65fe587f3fed21431da75512985c52772529e5e210c60",
|
||||||
|
"sha256:c48372905bffcc3b24bd55ab4683a07ee5e1f30fe918c59558ea5ee44cedf6c3"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==4.8.2"
|
||||||
|
},
|
||||||
|
"six": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||||
|
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==1.16.0"
|
||||||
|
},
|
||||||
|
"sniffio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101",
|
||||||
|
"sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==1.3.0"
|
||||||
|
},
|
||||||
|
"sortedcontainers": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88",
|
||||||
|
"sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"
|
||||||
|
],
|
||||||
|
"version": "==2.4.0"
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||||
|
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
|
"version": "==0.10.2"
|
||||||
|
},
|
||||||
|
"trio": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ce68f1c5400a47b137c5a4de72c7c901bd4e7a24fbdebfe9b41de8c6c04eaacf",
|
||||||
|
"sha256:f1dd0780a89bfc880c7c7994519cb53f62aacb2c25ff487001c0052bd721cdf0"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==0.22.0"
|
||||||
|
},
|
||||||
|
"trio-websocket": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:5b558f6e83cc20a37c3b61202476c5295d1addf57bd65543364e0337e37ed2bc",
|
||||||
|
"sha256:a3d34de8fac26023eee701ed1e7bf4da9a8326b61a62934ec9e53b64970fd8fe"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.5'",
|
||||||
|
"version": "==0.9.2"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
|
||||||
|
"sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||||
|
"version": "==1.26.15"
|
||||||
|
},
|
||||||
|
"win32-setctime": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2",
|
||||||
|
"sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"
|
||||||
|
],
|
||||||
|
"markers": "sys_platform == 'win32'",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
|
"wsproto": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
|
||||||
|
"sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
|
||||||
|
],
|
||||||
|
"markers": "python_full_version >= '3.7.0'",
|
||||||
|
"version": "==1.2.0"
|
||||||
|
},
|
||||||
|
"yagmail": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:44e8d0cda4f63e22a14902cc9096d52197fd0ced023d50b0409325f401585296",
|
||||||
|
"sha256:947a0864e4a64452c8e6b58c80b5bf45389bf8842d779701febfd34fa09649c7"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.15.293"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
145
README.md
145
README.md
|
@ -1,2 +1,145 @@
|
||||||
# uiautotest_pytest
|
# python + selenium + pytest+ pytest-html集成的UI自动化测试框架
|
||||||
|
|
||||||
|
**对于框架任何问题,欢迎联系我!**
|
||||||
|
|
||||||
|
#### 一、框架架构
|
||||||
|
暂略
|
||||||
|
|
||||||
|
#### 二、项目目录结构
|
||||||
|
├────.gitignore
|
||||||
|
├────case_utils/
|
||||||
|
│ ├────__init__.py
|
||||||
|
│ ├────basepage.py
|
||||||
|
│ ├────data_handle.py
|
||||||
|
│ └────url_handle.py
|
||||||
|
├────common_utils/
|
||||||
|
│ ├────__init__.py
|
||||||
|
│ ├────loguru_handle.py
|
||||||
|
│ ├────project_tree.py
|
||||||
|
│ └────yaml_handle.py
|
||||||
|
├────config/
|
||||||
|
│ ├────__init__.py
|
||||||
|
│ ├────global_vars.py
|
||||||
|
│ ├────project_path.py
|
||||||
|
│ ├────report.css
|
||||||
|
│ └────settings.py
|
||||||
|
├────page/
|
||||||
|
│ ├────__init__.py
|
||||||
|
│ ├────login_page.py
|
||||||
|
│ └────projects_page.py
|
||||||
|
├────test_case/
|
||||||
|
│ └────test_login_demo.py
|
||||||
|
├────data/
|
||||||
|
│ └────login_demo_data.py
|
||||||
|
├────outputs/
|
||||||
|
│ ├────image/
|
||||||
|
│ ├────log/
|
||||||
|
│ │ └────runtime_2023-03-14_17-01-20_856212_error.log
|
||||||
|
│ └────report/
|
||||||
|
│ │ └────uiautotest-report.html
|
||||||
|
├────conftest.py
|
||||||
|
├────Pipfile
|
||||||
|
├────Pipfile.lock
|
||||||
|
├────pytest.ini
|
||||||
|
├────README.md
|
||||||
|
├────run.py
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 三、框架功能说明
|
||||||
|
|
||||||
|
**解决痛点:**
|
||||||
|
|
||||||
|
1. 执行环境**一键切换**,解决**多环境**相互影响问题
|
||||||
|
|
||||||
|
```
|
||||||
|
1) 通过pytest_addoption将命令行参数--env添加到pytest配置对象中
|
||||||
|
2) 通过get_config去配置文件config.settings.py中读取不同环境的配置信息,包括域名,测试账号
|
||||||
|
3) 在主运行文件run.py中通过click模块,读取输入的-env的值
|
||||||
|
4) 最后在运行时输入 python run.py -env=test可以指定运行的环境
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 采用luguru管理日志,可以输出更为优雅,简洁的日志
|
||||||
|
|
||||||
|
#### 框架使用说明
|
||||||
|
|
||||||
|
1. 拉取代码到本地
|
||||||
|
|
||||||
|
2. 使用pipenv管理安装环境。
|
||||||
|
|
||||||
|
```
|
||||||
|
python版本要求:3.9.5
|
||||||
|
安装pipenv: pip install pipenv(必须在项目根目录下)
|
||||||
|
创建虚拟环境:pipenv install (必须在项目根目录下执行)
|
||||||
|
激活已存在的虚拟环境(如果不存在会创建一个):pipenv shell (必须在项目根目录下执行)
|
||||||
|
查看项目虚拟环境路径: pipenv --venv
|
||||||
|
退出虚拟环境:exit
|
||||||
|
|
||||||
|
注意:使用pipenv install会自动安装Pipfile里面的依赖包,该依赖包仅安装在虚拟环境里,不安装在测试机。
|
||||||
|
|
||||||
|
注意检查一下pip的安装源(位置:Pipfile)
|
||||||
|
以下安装源均可:
|
||||||
|
pip默认的镜像地址是:https://pypi.org/simple
|
||||||
|
清华大学:https://pypi.tuna.tsinghua.edu.cn/simple 清华大学的pip源是官网pypi的镜像,每隔5分钟同步一次,重点推荐!!!
|
||||||
|
|
||||||
|
阿里云:http://mirrors.aliyun.com/pypi/simple/
|
||||||
|
|
||||||
|
中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
|
||||||
|
|
||||||
|
华中理工大学:http://pypi.hustunique.com/
|
||||||
|
|
||||||
|
山东理工大学:http://pypi.sdutlinux.org/
|
||||||
|
|
||||||
|
豆瓣:http://pypi.douban.com/simple/
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 更改配置文件config.settings.py,配置test和live环境及测试账号, 测试报告的定制化信息展示, 浏览器驱动类型
|
||||||
|
|
||||||
|
4. 在page目录下新建一个py文件,编写某个页面的元素定位及相关操作
|
||||||
|
|
||||||
|
5. 在data目录下新建测试用例数据文件,编写测试用例, 测试用例使用py编写,呈现形式是python列表嵌套字典
|
||||||
|
|
||||||
|
6. 在test_case下编写测试用例
|
||||||
|
|
||||||
|
7. 框架主入口为 run.py文件
|
||||||
|
```
|
||||||
|
必须在项目根目录下,输入命令运行(如果依赖包是安装在虚拟环境中,需要先启动虚拟环境)。
|
||||||
|
注意:本机环境中没有安装依赖包的情况下,不要直接在run.py中右键直接run
|
||||||
|
> python run.py (默认在test环境运行测试用例)
|
||||||
|
> python run.py -env live 在live环境运行测试用例
|
||||||
|
> python run.py -env=test 在test环境运行测试用例
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 三、框架使用过程中遇到的问题
|
||||||
|
##### 测试机安装的是python3.7,但是本框架要求3.9.5,怎么办?
|
||||||
|
方法一:建议采纳此方法
|
||||||
|
1)首先在项目根目录下打开命令窗口,移除虚拟环境:pipenv --rm
|
||||||
|
2)安装虚拟环境时忽略锁定的版本号,同时安装依赖包:pipenv install --skip-lock
|
||||||
|
如果使用上述命令报错:Warning: Python 3.9 was not found on your system... Neither 'pyenv' nor 'asdf' could be found to install Python.
|
||||||
|
请使用如下命令:pipenv install --pyhon 3.7 --skip-lock (注意:这里的版本号,如果你的是3.8,就应该如下写命令:pipenv install --python 3.8 --skip-lock)
|
||||||
|
|
||||||
|
5)激活虚拟环境:pipenv shell
|
||||||
|
|
||||||
|
6)运行框架:python run.py
|
||||||
|
|
||||||
|
|
||||||
|
方法二:
|
||||||
|
1)首先在项目根目录下打开命令窗口,移除虚拟环境:pipenv --rm
|
||||||
|
2)更改项目根目录下的Pipfile文件
|
||||||
|
```
|
||||||
|
# 如下所示,3.9更改为3.7
|
||||||
|
[requires]
|
||||||
|
python_version = "3.7"
|
||||||
|
```
|
||||||
|
3)更改项目根目录下的Pipfile.lock文件
|
||||||
|
```
|
||||||
|
# 如下所示,3.9更改为3.7
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.7"
|
||||||
|
},
|
||||||
|
```
|
||||||
|
4)安装虚拟环境,同时安装依赖包:pipenv install
|
||||||
|
|
||||||
|
5)激活虚拟环境:pipenv shell
|
||||||
|
|
||||||
|
6)运行框架:python run.py
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 14:20
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : __init__.py.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 这是文件的描述信息
|
|
@ -0,0 +1,289 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2021/8/14 12:24
|
||||||
|
# @Author : Flora.Chen
|
||||||
|
# @File : basepage.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: UI自动化测试的一些基础浏览器操作方法
|
||||||
|
|
||||||
|
import os
|
||||||
|
from selenium.common.exceptions import NoSuchElementException
|
||||||
|
from selenium.webdriver import ActionChains
|
||||||
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.support.wait import WebDriverWait
|
||||||
|
import pyautogui
|
||||||
|
from pywinauto.keyboard import send_keys
|
||||||
|
|
||||||
|
|
||||||
|
class BasePage(object):
|
||||||
|
"""
|
||||||
|
UI自动化基础操作封装
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __int__(self, driver):
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def visit(self, url: str):
|
||||||
|
"""
|
||||||
|
访问页面
|
||||||
|
:param url:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.driver.get(url)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
"""刷新网页"""
|
||||||
|
self.driver.refresh()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click(self, locator: tuple, force=False):
|
||||||
|
"""
|
||||||
|
鼠标点击,当元素不可点击的时候,使用强制点击
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:param force: 强制点击,默认false
|
||||||
|
:return: self
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
elem = self.driver.find_element(*locator)
|
||||||
|
if not force:
|
||||||
|
self.driver.execute_script("arguments[0].click()", elem)
|
||||||
|
else:
|
||||||
|
self.driver.execute_script("arguments[0].click({force: true})", elem)
|
||||||
|
except Exception as e:
|
||||||
|
print("未找到元素:{}".format(e))
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def input(self, locator: tuple, text):
|
||||||
|
"""
|
||||||
|
输入内容
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:param text: 输入的内容
|
||||||
|
:return: self
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
elem = self.driver.find_element(*locator)
|
||||||
|
elem.send_keys(text)
|
||||||
|
return self
|
||||||
|
except NoSuchElementException as e:
|
||||||
|
print("未找到元素:{}".format(e))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def wait_element_visibility(self, locator: tuple, timeout=20, poll_frequency=0.2):
|
||||||
|
"""
|
||||||
|
显性等待: 等待元素可见
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return WebDriverWait(self.driver, timeout, poll_frequency).until(
|
||||||
|
EC.visibility_of_element_located(locator)
|
||||||
|
)
|
||||||
|
|
||||||
|
def wait_element_clickable(self, locator: tuple, timeout=10, poll_frequency=0.2):
|
||||||
|
"""
|
||||||
|
显性等待: 等待元素可点击
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return WebDriverWait(self.driver, timeout, poll_frequency).until(
|
||||||
|
EC.element_to_be_clickable(locator)
|
||||||
|
)
|
||||||
|
|
||||||
|
def wait_element_presence(self, locator: tuple, timeout=10, poll_frequency=0.2):
|
||||||
|
"""
|
||||||
|
显性等待: 等待元素被加载出来
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return WebDriverWait(self.driver, timeout, poll_frequency).until(
|
||||||
|
EC.presence_of_all_elements_located(locator)
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_element_attribute(self, locator: tuple, attr_name):
|
||||||
|
"""
|
||||||
|
获取元素属性值
|
||||||
|
:param locator: 元素定位,元祖类型
|
||||||
|
:return: 元素属性值
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.driver.find_element(*locator).get_attribute(attr_name)
|
||||||
|
except NoSuchElementException as e:
|
||||||
|
print("未找到元素:{}".format(e))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_name(self, locator: tuple):
|
||||||
|
"""
|
||||||
|
获取元素的name属性值
|
||||||
|
"""
|
||||||
|
return self.get_element_attribute(locator, "name")
|
||||||
|
|
||||||
|
def get_title(self, locator: tuple):
|
||||||
|
"""
|
||||||
|
获取元素的title属性值
|
||||||
|
"""
|
||||||
|
return self.get_element_attribute(locator, "title")
|
||||||
|
|
||||||
|
def get_class(self, locator: tuple):
|
||||||
|
"""
|
||||||
|
获取元素的class属性值
|
||||||
|
"""
|
||||||
|
return self.get_element_attribute(locator, "class")
|
||||||
|
|
||||||
|
def switch_to_frame(self, reference=None, timeout=10, poll=0.2):
|
||||||
|
"""
|
||||||
|
iframe切换
|
||||||
|
:param reference: 可以是id, name,索引或者元素定位(元祖)
|
||||||
|
:param timeout:
|
||||||
|
:param poll:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not reference:
|
||||||
|
return self.driver.switch_to.default_content()
|
||||||
|
return WebDriverWait(self.driver, timeout, poll).until(
|
||||||
|
EC.frame_to_be_available_and_switch_to_it(reference)
|
||||||
|
)
|
||||||
|
|
||||||
|
def switch_new_window(self):
|
||||||
|
"""切换到新窗口"""
|
||||||
|
# 获取所有的窗口
|
||||||
|
windows = self.driver.window_handles
|
||||||
|
if len(windows) >= 2:
|
||||||
|
# 切换窗口
|
||||||
|
self.driver.switch_to.window(self.driver.window_handles[-1])
|
||||||
|
return self
|
||||||
|
|
||||||
|
def find_elements(self, locator):
|
||||||
|
"""
|
||||||
|
查找元素们
|
||||||
|
:param locator:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.driver.find_elements(*locator)
|
||||||
|
except NoSuchElementException as e:
|
||||||
|
print("未找到元素:{}".format(e))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def get_text(self, locator: tuple):
|
||||||
|
"""
|
||||||
|
获取元素的文本值
|
||||||
|
:param locator: 元素定位
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
elem = self.driver.find_element(*locator)
|
||||||
|
value = elem.text
|
||||||
|
return value
|
||||||
|
except NoSuchElementException as e:
|
||||||
|
print(f"get未找到元素{e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def screenshot(self, path, filename):
|
||||||
|
"""
|
||||||
|
截图
|
||||||
|
:param path: 文件保存的目录
|
||||||
|
:param filename: 截图文件名
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
file_path = os.path.join(path, filename)
|
||||||
|
self.driver.save_screenshot(file_path)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# ------------------------ START: JS事件 ------------------------ #
|
||||||
|
def execute_js(self, js, *args):
|
||||||
|
"""
|
||||||
|
执行javascript脚本
|
||||||
|
js: 元组形式参数
|
||||||
|
"""
|
||||||
|
self.driver.execute_script(js, *args)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def new_open_window(self, url):
|
||||||
|
"""打开一个新窗口"""
|
||||||
|
# 获取所有的窗口
|
||||||
|
start_window = self.driver.window_handls
|
||||||
|
# 打开新窗口
|
||||||
|
js = "window.open({})".format(url)
|
||||||
|
self.driver.execute_script(js)
|
||||||
|
# 等待新窗口出现,进行切换
|
||||||
|
WebDriverWait(self.driver, 5, 0.5).until(
|
||||||
|
EC.new_window_is_opened(start_window)
|
||||||
|
)
|
||||||
|
# 切换窗口
|
||||||
|
self.driver.switch_to.window(self.driver.window_handls[-1])
|
||||||
|
return self
|
||||||
|
|
||||||
|
def upload_file_pywinauto(self, file_path):
|
||||||
|
"""
|
||||||
|
使用pywinauto来上传
|
||||||
|
缺点:只能在windows上使用。
|
||||||
|
优点:可以选择多个文件,路径中有中文也可以。
|
||||||
|
安装:pip install pywinauto -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
:param file_path: 文件绝对路径,支持传数组
|
||||||
|
"""
|
||||||
|
if isinstance(file_path, list):
|
||||||
|
for path in file_path:
|
||||||
|
# 上传文件
|
||||||
|
send_keys(path)
|
||||||
|
else:
|
||||||
|
# 上传文件
|
||||||
|
send_keys(file_path)
|
||||||
|
# 点击回车
|
||||||
|
send_keys("{VK_RETURN}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def upload_file_pyautogui(self, file_path):
|
||||||
|
"""
|
||||||
|
使用pyautogui来上传
|
||||||
|
缺点:只能选择一个文件,路径中有中文会出问题。
|
||||||
|
优点:跨平台。Linux, mac,windows都可以。
|
||||||
|
安装:pip install pyautogui -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
:param file_path: 文件绝对路径,支持传数组
|
||||||
|
"""
|
||||||
|
if isinstance(file_path, list):
|
||||||
|
print("只能选择一个文件,默认选择第一个")
|
||||||
|
file_path = file_path[0]
|
||||||
|
|
||||||
|
# 上传文件
|
||||||
|
pyautogui.write(file_path)
|
||||||
|
# 点击回车
|
||||||
|
pyautogui.press("enter", 2)
|
||||||
|
return self
|
||||||
|
|
||||||
|
# ------------------------ END: JS事件 ------------------------ #
|
||||||
|
|
||||||
|
# ------------------------ START: 鼠标事件:双击,悬停,拖动 ------------------------ #
|
||||||
|
|
||||||
|
def double_click(self, locator):
|
||||||
|
"""
|
||||||
|
鼠标双击
|
||||||
|
:param locator:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
elem = self.driver.find_element(*locator)
|
||||||
|
action = ActionChains(self.driver)
|
||||||
|
action.double_click(elem).perform()
|
||||||
|
return self
|
||||||
|
except NoSuchElementException as e:
|
||||||
|
print("未找到元素:{}".format(e))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def drag_and_drop(self, start_locator, end_locator):
|
||||||
|
"""鼠标拖动"""
|
||||||
|
elem_start = self.driver.find_element(*start_locator)
|
||||||
|
elem_end = self.driver.find_element(*end_locator)
|
||||||
|
action = ActionChains(self.driver)
|
||||||
|
action.double_click((elem_start, elem_end)).perform()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def hover(self, locator):
|
||||||
|
"""鼠标悬停"""
|
||||||
|
el = self.driver.find_element(*locator)
|
||||||
|
action = ActionChains(self.driver)
|
||||||
|
action.move_to_element(el).perform()
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------ END: 鼠标事件:双击,悬停,拖动 ------------------------ #
|
|
@ -0,0 +1,57 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/9 16:41
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : data_handle.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 处理用例数据,针对用例数据进行替换
|
||||||
|
|
||||||
|
from config.global_vars import GLOBAL_VARS
|
||||||
|
from string import Template
|
||||||
|
from loguru import logger
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def case_data_replace(content):
|
||||||
|
"""
|
||||||
|
用例数据替换的方法
|
||||||
|
:param content: 原始的字符串内容
|
||||||
|
return content: 替换表达式后的字符串
|
||||||
|
"""
|
||||||
|
if content is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if len(content) != 0:
|
||||||
|
logger.info(f"开始进行字符串替换: 替换字符串为:{content}")
|
||||||
|
content = Template(str(content)).safe_substitute(GLOBAL_VARS)
|
||||||
|
logger.debug(f"使用模板函数Template替换字符串完成。 替换后的字符串如下:{content}")
|
||||||
|
for func in re.findall('\\${(.*?)}', content):
|
||||||
|
try:
|
||||||
|
content = content.replace('${%s}' % func, exec_func(func))
|
||||||
|
logger.debug(f"通过执行函数替换用例数据值 替换字符串后为:{content}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
return str_to_python(content)
|
||||||
|
|
||||||
|
|
||||||
|
def exec_func(func) -> str:
|
||||||
|
"""
|
||||||
|
:params func 字符的形式调用函数
|
||||||
|
: return 返回的转换成函数执行的结果,已字符串格式返回
|
||||||
|
"""
|
||||||
|
result = eval(func)
|
||||||
|
return str(result)
|
||||||
|
|
||||||
|
|
||||||
|
# 将"[1,2,3]" 或者"{'k':'v'}" -> [1,2,3], {'k':'v'}
|
||||||
|
def str_to_python(content) -> object:
|
||||||
|
"""
|
||||||
|
将字符串包裹的表达式摘出来
|
||||||
|
"""
|
||||||
|
if content is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return eval(content)
|
||||||
|
except:
|
||||||
|
return content
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/9 16:41
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : url_handle.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 根据配置文件中的域名,以及测试用例数据中的url处理访问url
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
def url_handle(host, url):
|
||||||
|
"""
|
||||||
|
将host以及url拼接组成full_url
|
||||||
|
"""
|
||||||
|
if url is None:
|
||||||
|
url = ""
|
||||||
|
|
||||||
|
# 如果url是以http开头的,则直接使用该url,不与host进行拼接
|
||||||
|
if url.lower().startswith("http"):
|
||||||
|
full_url = url
|
||||||
|
else:
|
||||||
|
# 如果host以/结尾 并且 url以/开头
|
||||||
|
if host.endswith("/") and url.startswith("/"):
|
||||||
|
full_url = host[0:len(host) - 1] + url
|
||||||
|
# 如果host以/结尾 并且 url不以/开头
|
||||||
|
elif host.endswith("/") and (not url.startswith("/")):
|
||||||
|
full_url = host + url
|
||||||
|
else:
|
||||||
|
# # 如果host不以/结尾 或者 url不以/开头,则将host和url拼接起来的时候增加/,组成新的url
|
||||||
|
full_url = host + "/" + url
|
||||||
|
logger.info("处理前的host:{}, 处理前的url: {}, 处理后的full_url:{}".format(host, url, full_url))
|
||||||
|
return full_url
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 14:20
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : __init__.py.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 这是文件的描述信息
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 15:02
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : loguru_handle.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 配置loguru日志值生成错误日志的方法
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
|
||||||
|
def error_only(record):
|
||||||
|
"""
|
||||||
|
error日志判断
|
||||||
|
Args:
|
||||||
|
record:
|
||||||
|
Returns: 若日志级别为ERROR,输出True
|
||||||
|
"""
|
||||||
|
return record["level"].name == "ERROR"
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2021/8/16 11:42
|
||||||
|
# @Author : Flora.Chen
|
||||||
|
# @File : project_tree.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 快速生成文件夹目录结构图
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from pathlib import WindowsPath
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
|
||||||
|
class DirectionTree:
|
||||||
|
def __init__(self,
|
||||||
|
direction_name: str = 'WorkingDirection',
|
||||||
|
direction_path: str = '.',
|
||||||
|
ignore_list: Optional[List[str]] = None):
|
||||||
|
self.owner: WindowsPath = Path(direction_path)
|
||||||
|
self.tree: str = direction_name + '/\n'
|
||||||
|
self.ignore_list = ignore_list
|
||||||
|
if ignore_list is None:
|
||||||
|
self.ignore_list = []
|
||||||
|
self.direction_ergodic(path_object=self.owner, n=0)
|
||||||
|
|
||||||
|
def tree_add(self, path_object: WindowsPath, n=0, last=False):
|
||||||
|
if n > 0:
|
||||||
|
if last:
|
||||||
|
self.tree += '│' + (' │' * (n - 1)) + ' └────' + path_object.name
|
||||||
|
else:
|
||||||
|
self.tree += '│' + (' │' * (n - 1)) + ' ├────' + path_object.name
|
||||||
|
else:
|
||||||
|
if last:
|
||||||
|
self.tree += '└' + ('──' * 2) + path_object.name
|
||||||
|
else:
|
||||||
|
self.tree += '├' + ('──' * 2) + path_object.name
|
||||||
|
if path_object.is_file():
|
||||||
|
self.tree += '\n'
|
||||||
|
return False
|
||||||
|
elif path_object.is_dir():
|
||||||
|
self.tree += '/\n'
|
||||||
|
return True
|
||||||
|
|
||||||
|
def filter_file(self, file):
|
||||||
|
for item in self.ignore_list:
|
||||||
|
if re.fullmatch(item, file.name):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def direction_ergodic(self, path_object: WindowsPath, n=0):
|
||||||
|
dir_file: list = list(path_object.iterdir())
|
||||||
|
dir_file.sort(key=lambda x: x.name.lower())
|
||||||
|
dir_file = [f for f in filter(self.filter_file, dir_file)]
|
||||||
|
for i, item in enumerate(dir_file):
|
||||||
|
if i + 1 == len(dir_file):
|
||||||
|
if self.tree_add(item, n, last=True):
|
||||||
|
self.direction_ergodic(item, n + 1)
|
||||||
|
else:
|
||||||
|
if self.tree_add(item, n, last=False):
|
||||||
|
self.direction_ergodic(item, n + 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from config.project_path import BASE_DIR
|
||||||
|
ignore_list = [
|
||||||
|
'\.git', '__pycache__', 'venv', '.+\.whl', '\.idea', '.+\.jpg', '.+\.png',
|
||||||
|
'css', 'admin', 'tool.py', 'db.sqlite3'
|
||||||
|
]
|
||||||
|
tree = DirectionTree(ignore_list=ignore_list, direction_path=BASE_DIR)
|
||||||
|
print(tree.tree)
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 15:22
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : yaml_handle.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 从日志文件中提取响应数据
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
class ReadYaml:
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
"""
|
||||||
|
初始化用例文件
|
||||||
|
:param file_path: 文件绝对路径,如:D:\test\test.xlsx
|
||||||
|
"""
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
@property
|
||||||
|
def read_yaml(self) -> object:
|
||||||
|
with open(file=self.filename, mode="r", encoding="utf-8") as fp:
|
||||||
|
return yaml.safe_load(fp.read())
|
||||||
|
|
||||||
|
def write(self, data, mode="a"):
|
||||||
|
"""
|
||||||
|
往yaml文件中写入数据,默认是追加写入
|
||||||
|
:param data: 要写入的数据
|
||||||
|
:param mode: 写入模式
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
with open(self.filename, mode=mode, encoding="utf-8") as f:
|
||||||
|
yaml.dump(data, f)
|
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/12 15:22
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : __init__.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 这是文件的描述信息
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 14:31
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : global_vars.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 全局变量
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
# 定义一个全局变量,作用于接口关联数据存储
|
||||||
|
GLOBAL_VARS = {}
|
||||||
|
|
||||||
|
|
||||||
|
class CaseFileType(Enum):
|
||||||
|
"""
|
||||||
|
用例数据可存储文件的类型枚举
|
||||||
|
"""
|
||||||
|
EXCEL = 1
|
||||||
|
YAML = 2
|
||||||
|
ALL = 0
|
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/31 17:27
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : project_path.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 项目相关路径
|
||||||
|
import os
|
||||||
|
|
||||||
|
# ------------------------------------ 项目路径 ----------------------------------------------------#
|
||||||
|
# 项目根目录
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
# 通用模块目录
|
||||||
|
COMMON_DIR = os.path.join(BASE_DIR, "common_utils")
|
||||||
|
|
||||||
|
# 配置模块目录
|
||||||
|
CONF_DIR = os.path.join(BASE_DIR, "config")
|
||||||
|
|
||||||
|
# 日志/报告保存目录
|
||||||
|
OUT_DIR = os.path.join(BASE_DIR, "outputs")
|
||||||
|
if not os.path.exists(OUT_DIR):
|
||||||
|
os.mkdir(OUT_DIR)
|
||||||
|
|
||||||
|
# 报告保存目录
|
||||||
|
REPORT_DIR = os.path.join(OUT_DIR, "report")
|
||||||
|
if not os.path.exists(REPORT_DIR):
|
||||||
|
os.mkdir(REPORT_DIR)
|
||||||
|
|
||||||
|
# 日志保存目录
|
||||||
|
LOG_DIR = os.path.join(OUT_DIR, "log")
|
||||||
|
if not os.path.exists(LOG_DIR):
|
||||||
|
os.mkdir(LOG_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
# 图片保存的目录
|
||||||
|
IMG_DIR = os.path.join(OUT_DIR, "image")
|
||||||
|
if not os.path.exists(IMG_DIR):
|
||||||
|
os.mkdir(IMG_DIR)
|
||||||
|
|
||||||
|
# 测试用例模块
|
||||||
|
TEST_CASE_DIR = os.path.join(BASE_DIR, "test_case")
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* change pytest-html test report style */
|
||||||
|
@charset "UTF-8";
|
||||||
|
body {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
color: #2084D9;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #2084D9;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #466AFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#environment td {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#results-table {
|
||||||
|
font-size: 16px;
|
||||||
|
table-layout:fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
#results-table td{
|
||||||
|
word-break:break-all;
|
||||||
|
word-wrap:break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#results-table th{
|
||||||
|
font-size: 20px;
|
||||||
|
background-color: #2084D9;
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/9 17:08
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : settings.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 项目配置文件
|
||||||
|
|
||||||
|
from config.project_path import REPORT_DIR
|
||||||
|
import platform
|
||||||
|
|
||||||
|
# 测试环境配置
|
||||||
|
test = [
|
||||||
|
{
|
||||||
|
# 示例测试环境及示例测试账号
|
||||||
|
"host": "https://testforgeplus.trustie.net/",
|
||||||
|
"login": "chytest10",
|
||||||
|
"password": "12345678",
|
||||||
|
"nickname": "chy测试10",
|
||||||
|
"user_id": "85422"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
live = [
|
||||||
|
{
|
||||||
|
"host": "https://www.gitlink.org.cn",
|
||||||
|
"login": "******",
|
||||||
|
"password": "******",
|
||||||
|
"nickname": "******",
|
||||||
|
"user_id": "******"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# 测试报告的定制化信息展示
|
||||||
|
REPORT_TITLE = "自动化测试报告"
|
||||||
|
REPORT_NAME = "uiautotest-report.html"
|
||||||
|
PROJECT_NAME = "GitLink 确实开源"
|
||||||
|
TESTER = "测试人员:陈银花"
|
||||||
|
DEPARTMENT = "所属部门: 开源协同创新中心"
|
||||||
|
|
||||||
|
|
||||||
|
# 浏览器驱动类型
|
||||||
|
class RunDriver:
|
||||||
|
"""
|
||||||
|
运行的驱动配置信息
|
||||||
|
"""
|
||||||
|
# 配置浏览器驱动类型(chrome/firefox/chrome-headless/firefox-headless)。
|
||||||
|
driver_type = "firefox-headless"
|
||||||
|
|
||||||
|
# 浏览器驱动放置的位置,根据不同平台,驱动的位置不一致
|
||||||
|
if platform.system() == "Linux":
|
||||||
|
if "chrome" in driver_type:
|
||||||
|
driver_path = "/usr/bin/chromedriver"
|
||||||
|
if "firefox" in driver_type:
|
||||||
|
driver_path = ""
|
||||||
|
else:
|
||||||
|
if "chrome" in driver_type:
|
||||||
|
driver_path = r"C:\Program Files\Python\Python39\chromedriver.exe"
|
||||||
|
if "firefox" in driver_type:
|
||||||
|
driver_path = r"C:\Program Files\Python\Python39\geckodriver.exe"
|
|
@ -0,0 +1,234 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/2/2 16:05
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : conftest.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 这是文件的描述信息
|
||||||
|
|
||||||
|
|
||||||
|
from config.global_vars import GLOBAL_VARS
|
||||||
|
from loguru import logger
|
||||||
|
import pytest
|
||||||
|
from config.settings import test, live, RunDriver, REPORT_TITLE, PROJECT_NAME, TESTER, DEPARTMENT
|
||||||
|
from py._xmlgen import html # 安装pytest-html,版本最好是2.1.1
|
||||||
|
from time import strftime
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.chrome.options import Options as CH_Options
|
||||||
|
from selenium.webdriver.firefox.options import Options as FF_Options
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
from config.project_path import IMG_DIR
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------- START: 配置运行环境 ---------------------------------------#
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
"""
|
||||||
|
pytest_addoption 可以让用户注册一个自定义的命令行参数,方便用户将数据传递给 pytest;
|
||||||
|
这个 Hook 方法一般和 内置 fixture pytestconfig 配合使用,pytest_addoption 注册命令行参数,pytestconfig 通过配置对象读取参数的值;
|
||||||
|
:param parser:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser.addoption(
|
||||||
|
# action="store" 默认,只存储参数的值,可以存储任何类型的值,此时 default 也可以是任何类型的值,而且命令行参数多次使用也只能生效一个,最后一个值覆盖之前的值;
|
||||||
|
# action="append",将参数值存储为一个列表,用append模式将可以在pytest命令行方式执行测试用例的同时多次向程序内部传递自定义参数对应的参数值
|
||||||
|
"--env", action="store",
|
||||||
|
default="test",
|
||||||
|
choices=["test", "live"], # choices 只允许输入的值的范围
|
||||||
|
type=str,
|
||||||
|
help="将命令行参数--env添加到pytest配置对象中,通过--env设置当前运行的环境host"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def get_config(request):
|
||||||
|
"""
|
||||||
|
从配置对象中读取自定义参数的值
|
||||||
|
"""
|
||||||
|
# 根据指定的环境,获取指定环境的域名以及用例数据文件类型
|
||||||
|
env = request.config.getoption("--env")
|
||||||
|
if env.lower() == "live":
|
||||||
|
config_data = live
|
||||||
|
else:
|
||||||
|
config_data = test
|
||||||
|
for item in config_data:
|
||||||
|
for k, v in item.items():
|
||||||
|
GLOBAL_VARS[k] = v
|
||||||
|
|
||||||
|
logger.info(f"当前环境变量为:{GLOBAL_VARS}")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------- END: 配置运行环境 ---------------------------------------#
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------- START: 启动浏览器驱动 ---------------------------------------#
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def init_driver():
|
||||||
|
"""
|
||||||
|
定义浏览器驱动
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
driver_type = RunDriver.driver_type
|
||||||
|
driver_path = RunDriver.driver_path
|
||||||
|
logger.debug(f"运行的浏览器:{driver_type}, 驱动执行路径:{driver_path}")
|
||||||
|
if driver_type.lower() == "chrome":
|
||||||
|
# 本地chrome浏览器
|
||||||
|
# driver = webdriver.Chrome() # 不指定浏览器驱动路径的写法
|
||||||
|
driver = webdriver.Chrome(executable_path=driver_path)
|
||||||
|
driver.maximize_window()
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
driver.maximize_window()
|
||||||
|
driver.delete_all_cookies() # 清除浏览器所有缓存
|
||||||
|
|
||||||
|
elif driver_type.lower() == "firefox":
|
||||||
|
# 本地firefox浏览器
|
||||||
|
# driver = webdriver.Firefox() # 不指定浏览器驱动路径的写法
|
||||||
|
driver = webdriver.Firefox(executable_path=driver_path)
|
||||||
|
driver.maximize_window()
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
driver.maximize_window()
|
||||||
|
driver.delete_all_cookies() # 清除浏览器所有缓存
|
||||||
|
|
||||||
|
elif driver_type.lower() == "chrome-headless":
|
||||||
|
# chrome headless模式
|
||||||
|
# 参数说明,参考地址:https://github.com/GoogleChrome/chrome-launcher/blob/master/docs/chrome-flags-for-tools.md#--enable-automation
|
||||||
|
chrome_option = CH_Options()
|
||||||
|
chrome_option.add_argument("--headless")
|
||||||
|
chrome_option.add_argument("--no-sandbox") # 注意:linux运行必须要有这个
|
||||||
|
chrome_option.add_argument("--window-size=1920x1080")
|
||||||
|
# driver = webdriver.Chrome(options=chrome_option) # 不指定浏览器驱动路径的写法
|
||||||
|
driver = webdriver.Chrome(executable_path=driver_path, options=chrome_option)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
driver.delete_all_cookies() # 清除浏览器所有缓存
|
||||||
|
|
||||||
|
elif driver_type.lower() == "firefox-headless":
|
||||||
|
# firefox headless模式
|
||||||
|
firefox_options = webdriver.FirefoxOptions()
|
||||||
|
firefox_options.add_argument("--headless")
|
||||||
|
firefox_options.add_argument("--disable-gpu")
|
||||||
|
# driver = webdriver.Firefox(options=firefox_options) # 不指定浏览器驱动路径的写法
|
||||||
|
driver = webdriver.Firefox(options=firefox_options, executable_path=driver_path)
|
||||||
|
driver.implicitly_wait(10)
|
||||||
|
driver.delete_all_cookies() # 清除浏览器所有缓存
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.error("driver驱动类型定义错误!")
|
||||||
|
raise NameError("driver驱动类型定义错误!")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"初始化dirver的异常,请检查以下几点:1. driver_path的路径是否配置正确 2. driver版本是否与浏览器兼容:{e}")
|
||||||
|
raise NameError(f"driver异常,请检查以下几点:1. driver_path的路径是否配置正确 2. driver版本是否与浏览器兼容 {e}")
|
||||||
|
|
||||||
|
GLOBAL_VARS["driver"] = driver
|
||||||
|
yield driver
|
||||||
|
# 关闭浏览器
|
||||||
|
driver.quit()
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------- END: 启动浏览器驱动 ---------------------------------------#
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------- START: 报告处理 ---------------------------------------#
|
||||||
|
def pytest_html_report_title(report):
|
||||||
|
"""
|
||||||
|
修改报告标题
|
||||||
|
"""
|
||||||
|
report.title = REPORT_TITLE
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
"""
|
||||||
|
# 在测试运行前,修改Environment部分信息,配置测试报告环境信息
|
||||||
|
"""
|
||||||
|
# 给环境表 添加项目名称及开始时间
|
||||||
|
config._metadata["项目名称"] = PROJECT_NAME
|
||||||
|
config._metadata['开始时间'] = strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
# 给环境表 移除packages 及plugins
|
||||||
|
config._metadata.pop("Packages")
|
||||||
|
config._metadata.pop("Plugins")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(tryfirst=True)
|
||||||
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
|
"""
|
||||||
|
在测试运行后,修改Environment部分信息
|
||||||
|
"""
|
||||||
|
# 给环境表 添加 项目环境
|
||||||
|
session.config._metadata['项目环境'] = GLOBAL_VARS.get("host", "")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_html_results_summary(prefix, summary, postfix):
|
||||||
|
"""
|
||||||
|
修改Summary部分的信息
|
||||||
|
"""
|
||||||
|
prefix.extend([html.p(TESTER)])
|
||||||
|
prefix.extend([html.p(DEPARTMENT)])
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_html_results_table_header(cells):
|
||||||
|
"""
|
||||||
|
修改结果表的表头
|
||||||
|
"""
|
||||||
|
# 往表格中增加一列"用例描述",并且给"用例描述"增加排序
|
||||||
|
cells.insert(2, html.th('用例描述', class_="sortable", col="name"))
|
||||||
|
# 往表格中增加一列"执行时间",并且给"执行时间"增加排序
|
||||||
|
cells.insert(3, html.th('执行时间', class_="sortable time", col="time"))
|
||||||
|
# 移除表格最后一列
|
||||||
|
cells.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_html_results_table_row(report, cells):
|
||||||
|
"""
|
||||||
|
修改结果表的表头后给对应的行增加值
|
||||||
|
"""
|
||||||
|
# 往列"用例描述"插入每行的值
|
||||||
|
cells.insert(2, html.td(report.description))
|
||||||
|
# 往列"执行时间"插入每行的值
|
||||||
|
cells.insert(3, html.td(strftime("%Y-%m-%d %H:%M:%S"), class_="col-time"))
|
||||||
|
# 移除表格最后一列
|
||||||
|
cells.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_html_results_table_html(report, data):
|
||||||
|
"""如果测试通过,则显示这条用例通过啦!"""
|
||||||
|
if report.passed:
|
||||||
|
del data[:]
|
||||||
|
data.append(html.div("这条用例通过啦!", class_="empty log"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.hookwrapper
|
||||||
|
def pytest_runtest_makereport(item, call):
|
||||||
|
"""
|
||||||
|
1. 测试方法的文档注释作为结果表的Description的值
|
||||||
|
2. 测试用例失败进行截图操作,并将截图追加到测试报告中
|
||||||
|
"""
|
||||||
|
pytest_html = item.config.pluginmanager.getplugin("html")
|
||||||
|
outcome = yield
|
||||||
|
# 获取调用结果的测试报告,返回一个report对象
|
||||||
|
# report对象的属性包括when(steup, call, teardown三个值)、nodeid(测试用例的名字)、outcome(用例的执行结果,passed,failed)
|
||||||
|
report = outcome.get_result()
|
||||||
|
report.description = ""
|
||||||
|
extra = getattr(report, "extra", [])
|
||||||
|
if report.when == "call" or report.when == "setup":
|
||||||
|
# 将获取到的用例数据的title作为结果表的Description的值
|
||||||
|
report.description = GLOBAL_VARS.get("title", "")
|
||||||
|
# 报错截图添加到报告
|
||||||
|
xfail = hasattr(report, "wasxfail")
|
||||||
|
if (report.skipped and xfail) or (report.failed and not xfail):
|
||||||
|
# 定义截图的文件名
|
||||||
|
file_name = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".png"
|
||||||
|
# 进行截图操作
|
||||||
|
GLOBAL_VARS.get("driver").save_screenshot(os.path.join(IMG_DIR, file_name))
|
||||||
|
# 如果存在截图,就将截图以html形式插入到报告中
|
||||||
|
# 由于img里面必须写相对路径,因此需要对IMG_DIR进行处理
|
||||||
|
if "\\" in IMG_DIR:
|
||||||
|
image_name = IMG_DIR.split("\\")[-1]
|
||||||
|
else:
|
||||||
|
image_name = IMG_DIR.split("/")[-1]
|
||||||
|
image_path = os.path.join(f"{os.path.join('..', image_name)}", file_name)
|
||||||
|
image_path = image_path.replace("\\", "/") # html标签中需要使用/
|
||||||
|
html = f'<div class="image"><img src="{image_path}" onclick="window.open(this.src)"/></div>'
|
||||||
|
extra.append(pytest_html.extras.html(html))
|
||||||
|
report.extra = extra
|
||||||
|
# ------------------------------------- END: 报告处理 ---------------------------------------#
|
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/3/13 16:41
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : login_demo_data.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 登录功能 测试数据
|
||||||
|
|
||||||
|
cases_data = [
|
||||||
|
{"title": "正确用户名和密码登录成功 (弹窗登录)", "user": "${login}", "password": "${password}", "run": True},
|
||||||
|
{"title": "正确用户名和错误密码登录成功 (弹窗登录)", "user": "${login}", "password": "xxxxxxxx", "run": False}
|
||||||
|
]
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,146 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2021/8/10 10:15
|
||||||
|
# @Author : Flora.Chen
|
||||||
|
# @File : login_page.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 登录页面的元素定位 和 操作
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
from case_utils.basepage import BasePage
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from case_utils.url_handle import url_handle
|
||||||
|
|
||||||
|
# 导航栏 登录按钮
|
||||||
|
login_button = (By.XPATH, "//a[text()='登录']")
|
||||||
|
|
||||||
|
# ------------------------------ 登录弹窗元素定位 ---------------------------------------#
|
||||||
|
# 弹框中的用户名输入框
|
||||||
|
username_inputbox = (By.XPATH, "//input[@name='username']")
|
||||||
|
# 弹框中的密码输入框
|
||||||
|
password_inputbox = (By.XPATH, "//input[@name='password']")
|
||||||
|
# 弹框中的登录按钮
|
||||||
|
login_button_on_pop = (By.XPATH, "//div[text()='登录']")
|
||||||
|
# 弹框的错误提示信息
|
||||||
|
error_pop = (By.XPATH, "//div[@class='ant-notification-notice-description']")
|
||||||
|
|
||||||
|
# ------------------------------ 登录页面元素定位 ---------------------------------------#
|
||||||
|
# 用户名输入框
|
||||||
|
login_username = (By.ID, "login_username")
|
||||||
|
# 密码输入框
|
||||||
|
login_password = (By.ID, "login_password")
|
||||||
|
# 登录按钮
|
||||||
|
login_button_on_page = (By.XPATH, "//span[text()='登 录']/..")
|
||||||
|
# 下次自动登录
|
||||||
|
auto_login = (By.XPATH, "//span[contains(text(), '下次自动登录')]")
|
||||||
|
# 忘记密码
|
||||||
|
forget_password_button = (By.XPATH, "//span[contains(text(), '下次自动登录')]/../")
|
||||||
|
# 去注册
|
||||||
|
go_register_button = (By.XPATH, "//a[contains(text(), '注册')]")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------ 登录 操作 ---------------------------------------#
|
||||||
|
class LoginPop(BasePage):
|
||||||
|
"""
|
||||||
|
弹窗登录 (除了首页,注册,找回密码页面,其他都是弹窗登录)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver, host):
|
||||||
|
"""
|
||||||
|
host: 环境域名
|
||||||
|
driver:浏览器驱动
|
||||||
|
"""
|
||||||
|
self.full_url = url_handle(host, "/explore")
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""访问项目首页"""
|
||||||
|
self.visit(self.full_url)
|
||||||
|
logger.info(f"访问项目首页成功:{self.full_url}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def input_login_info(self, username, password):
|
||||||
|
"""
|
||||||
|
弹窗登录操作-输入登录信息
|
||||||
|
步骤:
|
||||||
|
1. 点击导航栏的登录按钮
|
||||||
|
2. 输入用户名
|
||||||
|
3. 输入密码
|
||||||
|
"""
|
||||||
|
self.wait_element_clickable(login_button).click()
|
||||||
|
self.wait_element_visibility(username_inputbox).send_keys(username)
|
||||||
|
self.wait_element_visibility(password_inputbox).send_keys(password)
|
||||||
|
time.sleep(5)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def login(self, username, password):
|
||||||
|
"""
|
||||||
|
弹窗登录操作
|
||||||
|
步骤:
|
||||||
|
1. 点击导航栏的登录按钮
|
||||||
|
2. 输入用户名
|
||||||
|
3. 输入密码
|
||||||
|
4. 点击登录按钮
|
||||||
|
"""
|
||||||
|
self.wait_element_clickable(login_button).click()
|
||||||
|
self.wait_element_visibility(username_inputbox).send_keys(username)
|
||||||
|
self.wait_element_visibility(password_inputbox).send_keys(password)
|
||||||
|
time.sleep(5)
|
||||||
|
self.wait_element_visibility(login_button_on_pop).click()
|
||||||
|
time.sleep(5)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def error_pop(self):
|
||||||
|
"""弹框的错误提示"""
|
||||||
|
elems = self.driver.find_elements(*error_pop)
|
||||||
|
return [elem.text for elem in elems]
|
||||||
|
|
||||||
|
def login_button_class(self):
|
||||||
|
"""获取弹框中登录按钮的class属性"""
|
||||||
|
return self.get_class(login_button_on_pop)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginPage(BasePage):
|
||||||
|
"""
|
||||||
|
登录页面的一系列操作 (首页,注册,找回密码页面)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, driver, host):
|
||||||
|
self.full_url = host
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""访问首页"""
|
||||||
|
self.visit(self.full_url)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def login(self, username, password):
|
||||||
|
"""
|
||||||
|
登录操作
|
||||||
|
1. 点击登录按钮
|
||||||
|
2. 输入用户名
|
||||||
|
3. 输入密码
|
||||||
|
4. 勾选自动登录
|
||||||
|
5. 点击登录按钮
|
||||||
|
"""
|
||||||
|
# 点击登录按钮
|
||||||
|
self.click(login_button)
|
||||||
|
# 输入用户名
|
||||||
|
self.input(login_username, username)
|
||||||
|
# 输入密码
|
||||||
|
self.input(login_password, password)
|
||||||
|
# 勾选自动登录
|
||||||
|
self.click(auto_login)
|
||||||
|
# 点击登录按钮
|
||||||
|
self.click(login_button_on_page)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click_go_register(self):
|
||||||
|
"""点击 去注册"""
|
||||||
|
self.click(go_register_button)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def click_forget_password(self):
|
||||||
|
"""点击 忘记密码"""
|
||||||
|
self.click(forget_password_button)
|
||||||
|
return self
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Time : 2021/12/19 15:36
|
||||||
|
# @Author : Administrator
|
||||||
|
# @File : project_home_page.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 项目首页
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
from case_utils.basepage import BasePage
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from case_utils.url_handle import url_handle
|
||||||
|
|
||||||
|
# ------------------------------ 元素定位 ---------------------------------------#
|
||||||
|
# 右上角的用户登录后的头像
|
||||||
|
avatar = (By.XPATH, "//a[@class='ant-dropdown-trigger']")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------ 操作 ---------------------------------------#
|
||||||
|
class ProjectsPage(BasePage):
|
||||||
|
"""项目首页"""
|
||||||
|
|
||||||
|
def __init__(self, driver, host):
|
||||||
|
"""
|
||||||
|
host: 项目首页地址
|
||||||
|
driver:浏览器驱动
|
||||||
|
"""
|
||||||
|
self.full_url = url_handle(host, "/explore")
|
||||||
|
self.driver = driver
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
self.visit(self.full_url)
|
||||||
|
logger.info(f"访问项目首页::{self.full_url}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_avatar(self):
|
||||||
|
"""获取用户头像"""
|
||||||
|
return self.get_element_attribute(avatar, "href")
|
|
@ -0,0 +1,6 @@
|
||||||
|
[pytest]
|
||||||
|
# cmd params
|
||||||
|
addopts = -v --capture=sys --self-contained-html --reruns=0 --reruns-delay=5
|
||||||
|
markers =
|
||||||
|
smoke:smoke case
|
||||||
|
high:high level case
|
|
@ -0,0 +1,54 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/9 17:09
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : run.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: 框架主入口
|
||||||
|
"""
|
||||||
|
说明:
|
||||||
|
1、用例创建原则,测试文件名必须以“test”开头,测试函数必须以“test”开头。
|
||||||
|
2、运行方式:
|
||||||
|
> python run.py (默认在test环境运行测试用例)
|
||||||
|
> python run.py -env live 在live环境运行测试用例
|
||||||
|
> python run.py -env=test 在test环境运行测试用例
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
from common_utils.loguru_handle import error_only
|
||||||
|
from loguru import logger
|
||||||
|
import click
|
||||||
|
from config.project_path import LOG_DIR, REPORT_DIR, CONF_DIR
|
||||||
|
from config.settings import REPORT_NAME
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option("-env", default=None, help="输入运行环境:test 或 live")
|
||||||
|
def run(env):
|
||||||
|
# 捕获所有日志
|
||||||
|
logger.add(os.path.join(LOG_DIR, "runtime_{time}_all.log"), enqueue=True, encoding="utf-8", rotation="00:00",
|
||||||
|
format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}")
|
||||||
|
# 仅捕获错误日志
|
||||||
|
logger.add(os.path.join(LOG_DIR, "runtime_{time}_error.log"), enqueue=True, encoding="utf-8", rotation="00:00",
|
||||||
|
filter=error_only, format="{time:YYYY-MM-DD HH:mm:ss} {level} From {module}.{function} : {message}")
|
||||||
|
logger.info("""
|
||||||
|
_ _ _ _____ _
|
||||||
|
__ _ _ __ (_) / \\ _ _| |_ __|_ _|__ ___| |_
|
||||||
|
/ _` | "_ \\| | / _ \\| | | | __/ _ \\| |/ _ \\/ __| __|
|
||||||
|
| (_| | |_) | |/ ___ \\ |_| | || (_) | | __/\\__ \\ |_
|
||||||
|
\\__,_| .__/|_/_/ \\_\\__,_|\\__\\___/|_|\\___||___/\\__|
|
||||||
|
|_|
|
||||||
|
Starting ... ... ...
|
||||||
|
""")
|
||||||
|
# 执行用例
|
||||||
|
report_name = os.path.join(REPORT_DIR, REPORT_NAME)
|
||||||
|
report_css = os.path.join(CONF_DIR, "report.css")
|
||||||
|
arg_list = [f'--html={report_name}', f"--css={report_css}"]
|
||||||
|
if env == "live":
|
||||||
|
arg_list.append("--env=live")
|
||||||
|
pytest.main(args=arg_list)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
|
@ -0,0 +1,75 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# @Version: Python 3.9
|
||||||
|
# @Time : 2023/1/9 16:41
|
||||||
|
# @Author : chenyinhua
|
||||||
|
# @File : test_demo.py
|
||||||
|
# @Software: PyCharm
|
||||||
|
# @Desc: python脚本编写的测试用例文件
|
||||||
|
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from config.global_vars import GLOBAL_VARS
|
||||||
|
from loguru import logger
|
||||||
|
from page.login_page import LoginPop
|
||||||
|
from page.projects_page import ProjectsPage
|
||||||
|
from data.login_demo_data import cases_data
|
||||||
|
from case_utils.data_handle import case_data_replace
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoginDemo:
|
||||||
|
"""
|
||||||
|
登录示例
|
||||||
|
"""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("case_data", cases_data)
|
||||||
|
def test_login_pop_success(self, init_driver, case_data):
|
||||||
|
"""
|
||||||
|
名称:正确用户名和密码登录成功 (弹窗登录)
|
||||||
|
步骤:
|
||||||
|
1. 打开浏览器
|
||||||
|
2. 进入弹窗登录页面
|
||||||
|
3. 输入用户名和密码
|
||||||
|
4. 点击登录按钮
|
||||||
|
断言:出现用户昵称以及浏览器地址正确
|
||||||
|
"""
|
||||||
|
logger.info("------------------------------------------开始执行用例------------------------------------------\n")
|
||||||
|
# 首先,清除浏览器缓存
|
||||||
|
init_driver.delete_all_cookies()
|
||||||
|
|
||||||
|
# 处理用例数据
|
||||||
|
case = case_data_replace(case_data)
|
||||||
|
logger.debug(f"当前执行的用例数据:{case}, {type(case)}")
|
||||||
|
|
||||||
|
# 处理URL
|
||||||
|
host = GLOBAL_VARS.get("host", "")
|
||||||
|
|
||||||
|
# 处理用例标题
|
||||||
|
GLOBAL_VARS["title"] = case["title"]
|
||||||
|
|
||||||
|
# 如果用例数据run=false则跳过该条用例不执行
|
||||||
|
if case["run"]:
|
||||||
|
# 初始化浏览器驱动
|
||||||
|
login_page = LoginPop(driver=init_driver, host=host)
|
||||||
|
projects_page = ProjectsPage(driver=init_driver, host=host)
|
||||||
|
# 访问开源项目首页,并进行登录操作
|
||||||
|
login_page.load().login(case["user"], case["password"])
|
||||||
|
|
||||||
|
# 断言
|
||||||
|
try:
|
||||||
|
logger.info(f"断言浏览器地址是否一致----预期:{login_page.full_url} 实际:{init_driver.current_url}")
|
||||||
|
assert login_page.full_url == init_driver.current_url
|
||||||
|
# 通过定位获取登录后用户的login
|
||||||
|
actual_user_login = projects_page.get_avatar().split("/")[-1]
|
||||||
|
logger.info(f"断言用户名是否一致----预期:{case['user']} 实际:{actual_user_login}")
|
||||||
|
assert case["user"] == actual_user_login.replace("/", "")
|
||||||
|
logger.info(f"{case['title']}:测试通过!")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"断言时遇到了异常:{e}")
|
||||||
|
logger.info(f"{case['title']}:测试失败!")
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
reason = f"标记了该用例为false,不执行\n"
|
||||||
|
logger.warning(f"{reason}")
|
||||||
|
pytest.skip(reason)
|
||||||
|
|
||||||
|
logger.info("------------------------------------------用例执行结束------------------------------------------\n")
|
Loading…
Reference in New Issue