first commit

This commit is contained in:
chenyh 2021-09-24 11:54:40 +08:00
parent a744962900
commit cc1f5371b1
155 changed files with 4219 additions and 1 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
__pycache__/
.pytest_cache/
.idea/
./config/settings.py
*.pyc

26
Pipfile Normal file
View File

@ -0,0 +1,26 @@
[[source]]
url = "https://pypi.douban.com/simple"
verify_ssl = true
name = "pypi"
[packages]
selenium = "==3.141.0"
allure-python-commons = "==2.9.43"
pytest = "==6.2.4"
yagmail = "==0.14.256"
requests = "==2.26.0"
urllib3 = "==1.26.6"
openpyxl = "==3.0.7"
click = "==8.0.1"
loguru = "==0.5.3"
pytest-rerunfailures = "==10.1"
Faker = "==8.10.1"
PyMySQL = "==1.0.2"
allure-pytest = "*"
PyYAML = "==5.4.1"
pytest-assume = "*"
[dev-packages]
[requires]
python_version = "3.8"

362
Pipfile.lock generated Normal file
View File

@ -0,0 +1,362 @@
{
"_meta": {
"hash": {
"sha256": "85c5967db3107f2f6694d875cfc0e8292f0d9b880ff52376e83065bcb2f24eaf"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.douban.com/simple",
"verify_ssl": true
}
]
},
"default": {
"allure-pytest": {
"hashes": [
"sha256:9ba613858a33c6becba539966116be5720c15f8c0427122e03c41082cfaa173a",
"sha256:b2ba613fe33b53924fdef2f21ee72cf14c2cc39a0ee8dadefd5d6ed7c927f430"
],
"index": "pypi",
"version": "==2.9.43"
},
"allure-python-commons": {
"hashes": [
"sha256:07e366f099c5ae248a1e8c56713bc9c25316ce8269944b4b038c62d2aacd8dbb",
"sha256:461bdd17c6514e130cfbb01d7060c82821a5ae1d2e049daefe64c4738d865cef"
],
"index": "pypi",
"version": "==2.9.43"
},
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"cachetools": {
"hashes": [
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"certifi": {
"hashes": [
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
],
"version": "==2021.5.30"
},
"charset-normalizer": {
"hashes": [
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
],
"markers": "python_version >= '3'",
"version": "==2.0.6"
},
"click": {
"hashes": [
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
"sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"
],
"index": "pypi",
"version": "==8.0.1"
},
"cssselect": {
"hashes": [
"sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf",
"sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0"
},
"cssutils": {
"hashes": [
"sha256:0cf1f6086b020dee18048ff3999339499f725934017ef9ae2cd5bb77f9ab5f46",
"sha256:b2d3b16047caae82e5c590036935bafa1b621cf45c2f38885af4be4838f0fd00"
],
"markers": "python_version >= '3.6'",
"version": "==2.3.0"
},
"et-xmlfile": {
"hashes": [
"sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c",
"sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"
],
"markers": "python_version >= '3.6'",
"version": "==1.1.0"
},
"faker": {
"hashes": [
"sha256:9ac6b39b9618f55be6b8b45089e624564469a035cc845c69ce990332ce3663f4",
"sha256:a665e6e2e9087ec9ad4ebcd2f09acd031b44193ee93401817001b6557c6502b4"
],
"index": "pypi",
"version": "==8.10.1"
},
"idna": {
"hashes": [
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
],
"markers": "python_version >= '3'",
"version": "==3.2"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"loguru": {
"hashes": [
"sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319",
"sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"
],
"index": "pypi",
"version": "==0.5.3"
},
"lxml": {
"hashes": [
"sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d",
"sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3",
"sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2",
"sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae",
"sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f",
"sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927",
"sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3",
"sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7",
"sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59",
"sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f",
"sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade",
"sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96",
"sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468",
"sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b",
"sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4",
"sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354",
"sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83",
"sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04",
"sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16",
"sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4",
"sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791",
"sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a",
"sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51",
"sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1",
"sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a",
"sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f",
"sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee",
"sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec",
"sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969",
"sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28",
"sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a",
"sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa",
"sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106",
"sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d",
"sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d",
"sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617",
"sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4",
"sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92",
"sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0",
"sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4",
"sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24",
"sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2",
"sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e",
"sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0",
"sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654",
"sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2",
"sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23",
"sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.6.3"
},
"openpyxl": {
"hashes": [
"sha256:46af4eaf201a89b610fcca177eed957635f88770a5462fb6aae4a2a52b0ff516",
"sha256:6456a3b472e1ef0facb1129f3c6ef00713cebf62e736cd7a75bcc3247432f251"
],
"index": "pypi",
"version": "==3.0.7"
},
"packaging": {
"hashes": [
"sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
"sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
],
"markers": "python_version >= '3.6'",
"version": "==21.0"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"premailer": {
"hashes": [
"sha256:021b8196364d7df96d04f9ade51b794d0b77bcc19e998321c515633a2273be1a",
"sha256:d1875a8411f5dc92b53ef9f193db6c0f879dc378d618e0ad292723e388bfe4c2"
],
"version": "==3.10.0"
},
"py": {
"hashes": [
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0"
},
"pymysql": {
"hashes": [
"sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641",
"sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"
],
"index": "pypi",
"version": "==1.0.2"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b",
"sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"
],
"index": "pypi",
"version": "==6.2.4"
},
"pytest-assume": {
"hashes": [
"sha256:09074220213b87cf1f400609701a44b75078b4e69588c7227e061ff9b3ecf54b",
"sha256:983e90537660fd7aa18bb554693523020272e7edeee3e40435593297bcc6347e"
],
"index": "pypi",
"version": "==2.4.3"
},
"pytest-rerunfailures": {
"hashes": [
"sha256:53db94acf7499c75c5257c79d8a1dc22c3db4bc8d32ec3a713ea91eda3f98359",
"sha256:7617c06de13ee6dd2df9add7e275bfb2bcebbaaf3e450f5937cd0200df824273"
],
"index": "pypi",
"version": "==10.1"
},
"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"
},
"pyyaml": {
"hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
"sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696",
"sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393",
"sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77",
"sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922",
"sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5",
"sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8",
"sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10",
"sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc",
"sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018",
"sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e",
"sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253",
"sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347",
"sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183",
"sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541",
"sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb",
"sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185",
"sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc",
"sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db",
"sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa",
"sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46",
"sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122",
"sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b",
"sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63",
"sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df",
"sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc",
"sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247",
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
],
"index": "pypi",
"version": "==5.4.1"
},
"requests": {
"hashes": [
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"index": "pypi",
"version": "==2.26.0"
},
"selenium": {
"hashes": [
"sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
"sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
],
"index": "pypi",
"version": "==3.141.0"
},
"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"
},
"text-unidecode": {
"hashes": [
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8",
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"
],
"version": "==1.3"
},
"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"
},
"urllib3": {
"hashes": [
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
],
"index": "pypi",
"version": "==1.26.6"
},
"yagmail": {
"hashes": [
"sha256:30dc2bc9d84fe56897df34adcd63fadf1d8dc2505d54c3bad9ba101915fabb1b",
"sha256:88f20899a193025fcdc6c93d2781abbcd2f029d99e6242f67d5fbb15e1ebc366"
],
"index": "pypi",
"version": "==0.14.256"
}
},
"develop": {}
}

View File

@ -1,2 +1,97 @@
# auotest-demo
# auotest
python+request/selenium+pytest_allure集成的API & UI自动化测试框架
# 项目结构
├────.gitignore git上传时忽略的文件
├────api/ 存放项目的基础API信息
│ ├────__init__.py
│ ├────login_api.py 登录接口
│ └────project_api.py 项目接口
├────common/ 存放公共方法
│ ├────__init__.py
│ ├────baseapi.py 封装基础的request方法
│ ├────basepage.py 封装浏览器基础的操作方法用于UI自动化测试
│ ├────download_img.py 封装通过URL下载图片的方法
│ ├────handle_email.py python自带的SMTP模块发送邮件 (目前项目未使用该模块)
│ ├────handle_excel.py 使用openpyxl对excel进行读写操作
│ ├────handle_faker.py 使用faker模块造测试数据
│ ├────handle_log.py python自带的logging模块对日志进行处理目前项目未使用该模块
│ ├────handle_mysql.py 使用pymysql对mysql数据库进行操作
│ ├────handle_platform.py 跨平台的支持allure用于生成allure测试报告
│ ├────handle_time.py 处理时间的类
│ ├────handle_yagmail.py 使用yagmail对自动发送报告邮件
│ ├────handle_yaml.py 使用pyyaml对yaml文件的读写方法
│ ├────helper.py 封装一些小方法
│ └────project_tree.py 获取项目文件树的方法
├────config/ 存放环境配置信息
│ ├────__init__.py
│ ├────report_template.html 直接通过python发送邮件的内容正文模板
│ ├────report_template_jenkins.html 通过jenkins发送邮件的内容正文模板
│ ├────settings.py 项目配置文件
│ └────settings_example.py 项目配置示例
├────data/ 存放测试数据
│ ├────api_data/ 存放API测试的测试数据
│ │ └────login_data.yaml 登录用例的测试数据
│ └────ui_data/ 存放UI测试的测试数据
│ │ ├────login_data.py 登录用例的测试数据
│ │ └────new_project_data.py 新建项目的测试数据
├────page/ 封装页面元素定位和操作用于UI自动化测试
│ ├────__init__.py
│ ├────login_page.py 登录页面的元素定位和操作
│ ├────project_detail_page.py 项目详情页的元素定位和操作
│ └────users_center_page.py 个人主页的元素定位和操作
├────tools/
│ ├────__init__.py
│ └────export_issue.py
├────lib/ 存放第三方库
│ └────__init__.py
├────log/ 存放日志文件
├────test_api/ 存放api测试用例
│ ├────__init__.py
│ └────conftest.py 作用于当前包的fixture的配置文件
├────test_app/ 存放app测试用例
│ ├────__init__.py
│ └────conftest.py 作用于当前包的fixture的配置文件
├────test_ui/ 存放UI测试用例
│ ├────__init__.py
│ └────conftest.py 作用于当前包的fixture的配置文件
├────report/ 存放allure测试报告
├────image/ 存放UI测试过程中的截图
├────conftest.py 作用于整个项目的全局的fixture的配置文件
├────Pipfile 记录虚拟环境的相关信息
├────Pipfile.lock 记录了当前虚拟环境中安装的依赖的版本号以及哈希
├────pytest.ini pytest的配置文件
├────README.md
├────requirements.txt 项目依赖包使用了pipenv后该文件不需要
└────run.py 项目的运行文件
# pipenv使用
# 参考连接https://zhuanlan.zhihu.com/p/71598248
安装pipenv: pip3 install pipenv
创建虚拟环境pipenv install
激活已存在的虚拟环境如果不存在会创建一个pipenv shell
#注意:以下三个参数只能单独使用。它们还具有破坏性,会删除当前的虚拟环境,然后用适当版本的虚拟环境替代。
指定使用Python3.6的虚拟环境: pipenv --python 3.6
使用系统的Python2在创建虚拟环境: pipenv --two
使用系统的Python3在创建虚拟环境: pipenv --three
列出本地工程路径: pipenv --where
列出虚拟环境路径pipenv --venv
列出虚拟环境python的可执行路径pipenv --py
安装包pipenv isntall 包名
安装包到开发环境pipenv install 包名--dev
更新包pipenv update 包名
卸载包pipenv uninstall 包名
卸载所有包pipenv uninstall --all
查看包依赖pipenv graph
更新Pipfile.lock文件锁定当前环境的依赖版本pipenv lock
运行py文件pipenv run python "python文件.py"
删除虚拟环境pipenv --rm
退出虚拟环境exit
# 命令参考
更新依赖包pipreqs . --encoding=utf8 --force
安装依赖包pipenv install -r requirements.txt
jenkins工作目录/var/lib/jenkins/workspace

6
api/__init__.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/13 20:39
# @Author : Flora.Chen
# @File : __init__.py.py
# @Software: PyCharm
# @Desc:

43
api/login_api.py Normal file
View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/11 9:02
# @Author : Flora.Chen
# @File : login_api.py
# @Software: PyCharm
# @Desc: 登录接口
from common.baseapi import BaseApi
from loguru import logger
class TrustieLogin(BaseApi):
"""trustie登录"""
def __init__(self, host):
super().__init__()
self.host = host
def get_token_session_api(self):
"""获取cookie"""
response = self.send_request(url=self.host, method="GET", verify=False)
return response.cookies
def login_api(self, user, pwd):
"""登录"""
headers = {"Content-Type": "application/json;charset=UTF-8"}
data = {"login": user, "password": pwd,
"autologin": 1}
cookies = self.get_token_session_api()
response = self.send_request(url=self.host + "/api/accounts/login.json", method="POST", headers=headers,
json=data,
cookies=cookies,
allow_redirects=False)
try:
json_data = response.json()
if json_data["login"] == user:
logger.debug(f'登录接口请求成功user_id={json_data["user_id"]}')
return response
except Exception as e:
logger.error(f"登录接口请求错误:{e}")

32
api/project_api.py Normal file
View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/30 10:34
# @Author : Flora.Chen
# @File : project_api.py
# @Software: PyCharm
# @Desc: 项目接口
from common.baseapi import BaseApi
from api.login_api import TrustieLogin
from loguru import logger
class ProjectApi(BaseApi):
"""项目相关的api"""
def __init__(self, host):
super().__init__()
self.host = host
def delete_project(self, **kwargs):
"""删除项目"""
url = "/api/" + str(kwargs.get("owner")) + "/" + str(kwargs.get("repository_name")) + ".json"
response = self.send_request(url=self.host + url, method="DELETE", cookies=kwargs.get("cookies"))
try:
json_data = response.json()
if json_data["message"] == "success" and json_data["status"] == 0:
logger.debug('删除项目接口请求成功!')
return response
except Exception as e:
logger.error(f"删除项目接口请求错误:{e}")

6
common/__init__.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/13 20:39
# @Author : Flora.Chen
# @File : __init__.py.py
# @Software: PyCharm
# @Desc:

72
common/baseapi.py Normal file
View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:56
# @Author : Flora.Chen
# @File : baseapi.py
# @Software: PyCharm
# @Desc:
import requests
from loguru import logger
import urllib3
from urllib3.exceptions import InsecureRequestWarning
# 加这句不会报错(requests证书警告)
urllib3.disable_warnings(InsecureRequestWarning)
class BaseApi(object):
"""
BaseApi作为所有单接口的父类出现将所有接口公共的属性或者方法信息进行抽象封装
"""
session = None
def __init__(self):
self.url = None
self.method = None
self.headers = None
self.data = None
self.params = None
self.json = None
self.response = None
@classmethod
def get_session(cls):
"""
单例模式保证测试过程中使用的都是一个session对象
:return:
"""
if cls.session is None:
cls.session = requests.Session()
return cls.session
def send_api(self, **kwargs):
session = self.get_session()
return session.request(**kwargs)
def send_request(self, **kwargs):
"""
发送请求
:param kwargs: 表示接口在发起时有一些自定义的参数或者其他的数据
:return:
"""
# if处理表示的是调用方如果不传递某些参数那么就用当前对象自己的属性
if kwargs.get("method") is None:
kwargs["method"] = self.method
if kwargs.get("url") is None:
kwargs["url"] = self.url
if kwargs.get("params") is None:
kwargs["params"] = self.params
if kwargs.get("data") is None:
kwargs["data"] = self.data
if kwargs.get("json") is None:
kwargs["json"] = self.json
if kwargs.get("headers") is None:
kwargs["headers"] = self.headers
logger.debug("----------- 开始发送请求 -----------\n")
logger.debug(f"----------- 请求参数:{kwargs} -----------\n")
response = self.send_api(**kwargs)
if response.status_code // 100 == 2:
logger.debug(f"----------- 响应码200-299返回的响应数据(text){response.text} -----------\n")
else:
logger.debug(f"----------- 响应码不是200-299返回的响应数据{response.text} -----------\n")
logger.debug("----------- 结束发送请求 -----------\n")
return response

228
common/basepage.py Normal file
View File

@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:24
# @Author : Flora.Chen
# @File : basepage.py
# @Software: PyCharm
# @Desc: UI自动化测试的一些基础浏览器操作方法
import os
from datetime import datetime
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
class BasePage(object):
"""
UI自动化基础操作封装
"""
def __int__(self, host, driver):
self.host = host
self.driver = driver
def visit(self, url: str):
"""
访问页面
:param url:
:return:
"""
if not url.startswith("http"):
url = self.host + url
self.driver.get(url)
return self
def hit(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))
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))
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))
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")
# ------------------------ 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))
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: 鼠标事件:双击,悬停,拖动 ------------------------ #
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 find_elements(self, locator):
"""
查找元素们
:param locator:
:return:
"""
try:
return self.driver.find_elements(*locator)
except NoSuchElementException as e:
print("未找到元素:{}".format(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}")
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):
self.driver.execute_script(js)
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
# ------------------------ END: JS事件 ------------------------ #

29
common/download_img.py Normal file
View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# @Time : 2021/9/10 13:44
# @Author : Flora.Chen
# @File : download_img.py
# @Software: PyCharm
# @Desc: 下载图片
import urllib.request
from loguru import logger
def download_img(url, path):
"""
通过Python自带的库urllib下载图片
需要导入import urllib.request
:param url: url地址
:param path: 图片保存的绝对路径
"""
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
if response.getcode() == 200:
logger.debug("请求状态码200 开始下载图片......")
with open(path, "wb") as fb:
fb.write(response.read())
return path
except Exception as e:
logger.debug("捕获到异常,下载图片失败")
return f"{e}"

78
common/handle_email.py Normal file
View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:21
# @Author : Flora.Chen
# @File : handle_email.py
# @Software: PyCharm
# @Desc: python自带的SMTP模块发送邮件
import smtplib
from email.mime.text import MIMEText # 用来发送邮件
from email.mime.application import MIMEApplication # 用来添加附件
from email.mime.multipart import MIMEMultipart # 用来构造多组件邮件
from email.header import Header
class HandleEmail:
"""邮件处理类"""
def __init__(self, host, port, mail_user, mail_auth_code, sender, recipients):
"""
初始化邮件服务器信息
:param host: 邮箱服务器地址
:param port: 邮箱服务器端口号
:param mail_user: 邮箱登录账号
:param mail_auth_code: 邮箱授权码/密码
:param sender: 邮件发送人
:param recipients: 邮件接收人
"""
self.sender = sender
self.recipients = recipients
# ---------- 连接smtp服务器并登录 ----------
# 连接到smtp服务器
self.smtp = smtplib.SMTP_SSL(host=host, port=port)
# 登录smtp服务邮箱账号+授权码登录)
self.smtp.login(user=mail_user, password=mail_auth_code)
def send_email(self, subject, content, file=None):
"""
发送邮件
:param subject: 邮件主题
:param content: 邮件内容
:param file: 邮件附件
:return:
"""
# ---------- 构建邮件内容 ----------
msg = MIMEMultipart()
# 邮件主题
msg['Subject'] = Header(subject, charset="utf8")
# 邮件内容
text = MIMEText(content, _subtype="html", _charset='utf8')
# 收件人:是邮件中显示的收件人,不是实际的收件人
msg['To'] = self.recipients
# 发件人:是邮件中显示的发件人,不是实际的发件人
msg['From'] = self.sender
# 将构造的文本内容,添加到多组件邮件中
msg.attach(text)
# 构造邮件附件 问题发送html样式没有了
# 读取报告中的内容,作为附件发送
if file:
with open(file, 'rb') as f:
f_msg = f.read()
att = MIMEApplication(f_msg)
att.add_header('content-disposition', 'attachment', filename=file)
msg.attach(att)
# att = MIMEText(content, _subtype="html", _charset='utf8')
# att["Content-Type"] = "appilication/octet-stream"
# att.add_header('content-disposition', 'attachment', filename=file)
# msg.attach(att)
# ---------- 发送邮件 ----------
# from_addr是真正的发件人地址 to_addrs真正的收件人地址
self.smtp.send_message(msg, from_addr=self.sender, to_addrs=self.recipients)
# 关闭服务
self.smtp.quit()

91
common/handle_excel.py Normal file
View File

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:22
# @Author : Flora.Chen
# @File : handle_excel.py
# @Software: PyCharm
# @Desc: 使用openpyxl对excel进行读写操作
import os
import openpyxl
from loguru import logger
class HandleExcel:
"""
从excel读取写入数据的类
"""
def __init__(self, file):
"""
初始化用例文件
:param file: 文件绝对路径D:\test\test.xlsx
"""
self.file = file
def create_excel(self, excel_name):
"""
创建excel文件
"""
# 创建文件对象
wb = openpyxl.Workbook()
path = os.path.join(self.file, excel_name)
logger.debug(f"self.file{self.file}")
logger.debug(f"excel_name{excel_name}")
logger.debug(f"创建的excel路径{path}")
wb.save(path)
self.file = path
def read(self, sheet_name):
"""
读取excel数据并返回
:param sheet_name: 表单名称
:return: 存在传入的表单, 返回表单数据不存在则返回空
"""
# 创建一个工作簿工作对象(excel文件已存在的情况)
workbook = openpyxl.open(self.file)
# 跟上面那句一个意思 workbook = openpyxl.load_workbook(self.file)
# 获取excel当中所有的sheet返回的是一个列表
sheets = workbook.sheetnames
# 获取sheet对象
if sheet_name in sheets:
sheet = workbook[sheet_name]
all_values = list(sheet.values)
header = all_values[0]
data = []
for i in all_values[1:]:
data.append(dict(zip(header, i)))
# 关闭excel
workbook.close()
return data
else:
# 关闭excel
workbook.close()
logger.error(f"表单: 【{sheet_name}】文件不存在")
def write(self, row, column, data, sheet_name=None):
"""
往excel写入数据
:param sheet_name: 表单名称
:param row: 要写入的行
:param column: 要写入的列
:param data: 要写入的数据
:return: None
"""
workbook = openpyxl.open(self.file)
# 获取excel当中所有的sheet返回的是一个列表
sheets = workbook.sheetnames
if sheet_name in sheets:
sheet = workbook[sheet_name]
logger.error(f"往表单【{sheet_name}】中写入数据")
else:
# 如果表单为空,就默认使用第一个表单
sheet = workbook.active
logger.error(f"表单【{sheet_name}】不存在,默认往第一个表单中写入数据")
sheet.cell(row=row, column=column, value=data)
# 更上面写法效果一样 sheet.cell(row=row, column=column).value = data
# 保存并关闭文件
workbook.save(self.file)
workbook.close()

25
common/handle_faker.py Normal file
View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/23 14:32
# @Author : Flora.Chen
# @File : handle_faker.py
# @Software: PyCharm
# @Desc: 使用faker模块造测试数据
from faker import Faker
# 生成中文的随机数据
z_fk = Faker(locale="zh_CN")
# 生成英文的随机数据
e_fk = Faker()
# 随机名字
z_fk_name = z_fk.name()
e_fk_name = e_fk.name()
# ----------------------------- 文本相关 ----------------------------------#
# 单个文本
z_fk_text = z_fk.text(max_nb_chars=200, ext_word_list=None)
e_fk_text = e_fk.text(max_nb_chars=200, ext_word_list=None)
# 多个句子
z_fk_sentences = z_fk.sentences(nb=3, ext_word_list=None)
e_fk_sentences = e_fk.sentences(nb=3, ext_word_list=None)

63
common/handle_log.py Normal file
View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:08
# @Author : Flora.Chen
# @File : handle_log.py
# @Software: PyCharm
# @Desc: python内置的logging模块处理日志
import logging
def get_logger(name,
level,
fmt,
handler_level,
file_level,
file=None):
"""
日志收集器
:param name: 日志收集器名称
:param level: 日志收集等级
:param fmt: 日志输出格式
:param handler_level: 控制台输出日志最低等级
:param file_level: 文件输出日志最低等级
:param file: 日志文件路径
:return: logger 日志收集器
"""
# 创建日志收集器
logger = logging.getLogger(name)
# 设置日志收集器收集等级
logger.setLevel(level)
# ----------- 控制台输出日志 --------------------#
# 创建日志输出渠道-控制台
handler = logging.StreamHandler()
# 设置日志收集等级-控制台
handler.setLevel(handler_level)
# 创建一个日志输出格式对象
fmt = logging.Formatter(fmt)
# 设置日志输出格式 - 控制台
handler.setFormatter(fmt)
# 添加日志处理器-控制台
logger.addHandler(handler)
# ----------- 日志保存在日志文件 --------------------#
if file:
# 创建日志输出渠道-文件
file_handler = logging.FileHandler(file, encoding="utf-8")
# 设置日志收集等级-文件
file_handler.setLevel(file_level)
# 设置日志输出格式 - 文件
file_handler.setFormatter(fmt)
# 添加日志处理器-文件
logger.addHandler(file_handler)
return logger

119
common/handle_mysql.py Normal file
View File

@ -0,0 +1,119 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:15
# @Author : Flora.Chen
# @File : handle_mysql.py
# @Software: PyCharm
# @Desc: 使用pymysql模块连接mysql数据库的公共方法
import pymysql
from typing import Union
import json
from datetime import datetime
class DBHandler:
"""
初始化数据库连接并指定查询的结果集以字典形式返回
"""
def __init__(self,
host=None,
port=None,
user=None,
password=None,
database=None,
charset="utf8",
cursorclass=pymysql.cursors.DictCursor # 加上这个返回的就是字典
):
"""
初始化方法中 连接到数据库
"""
# 建立连接
self.conn = pymysql.connect(host=host,
port=port,
user=user,
password=password,
database=database,
charset=charset,
cursorclass=cursorclass
)
# 创建一个游标对象
self.cursor = self.conn.cursor()
def query_all(self, sql):
"""
查询所有符合sql条件的数据
:param sql: 执行的sql
:return: 查询结果
"""
self.conn.commit()
self.cursor.execute(sql)
data = self.cursor.fetchall()
self.cursor.close()
return self.verify(data)
def query_one(self, sql):
"""
查询符合sql条件的数据的第一条数据
:param sql: 执行的sql
:return: 返回查询结果的第一条数据
"""
self.conn.commit()
self.cursor.execute(sql)
data = self.cursor.fetchone()
self.cursor.close()
return self.verify(data)
def insert(self, sql):
"""
插入数据
:param sql: 执行的sql
"""
self.cursor.execute(sql)
# 提交 只要数据库更新就要commit
self.conn.commit()
self.cursor.close()
def update(self, sql):
"""
更新数据
:param sql: 执行的sql
"""
self.cursor.execute(sql)
# 提交 只要数据库更新就要commit
self.conn.commit()
self.cursor.close()
def query(self, sql, one=True):
"""
根据传值决定查询一条数据还是所有
:param sql: 查询的SQL语句
:param one: 默认True. True查一条数据否则查所有
:return:
"""
if one:
return self.query_one(sql)
else:
return self.query_all(sql)
def close(self):
"""
断开游标关闭数据库
:return:
"""
self.conn.close()
def verify(self, result: dict) -> Union[dict, None]:
"""验证结果能否被json.dumps序列化"""
# 尝试变成字符串解决datetime 无法被json 序列化问题
try:
json.dumps(result)
except TypeError: # TypeError: Object of type datetime is not JSON serializable
for k, v in result.items():
if isinstance(v, datetime):
result[k] = str(v)
return result

21
common/handle_platform.py Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/19 9:13
# @Author : Flora.Chen
# @File : handle_platform.py
# @Software: PyCharm
# @Desc: 跨平台的支持allure用于生成allure测试报告
import platform
class HandlePlatform:
"""跨平台的支持allure, webdriver"""
@property
def allure(self):
if platform.system() == "Windows":
cmd = "allure.bat"
else:
cmd = "allure"
return cmd

90
common/handle_time.py Normal file
View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:13
# @Author : Flora.Chen
# @File : handle_time.py
# @Software: PyCharm
# @Desc: 时间计算的方法
from datetime import datetime, timedelta
now = datetime.now().strftime("%Y-%m-%d %H_%M_%S")
class AddTime:
"""
计算时间加减的类
"""
def __init__(self, start_time=now, time_unit="days", interval=1):
if isinstance(start_time, str):
start_time = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
self.start_time = start_time
self.time_unit = time_unit
self.interval = interval
def add_time(self):
"""
在指定时间基础上加减指定时间
:return: 计算结果
"""
if self.time_unit == "seconds":
return self.add_seconds()
elif self.time_unit == "hours":
return self.add_hours()
elif self.time_unit == "weeks":
return self.add_weeks()
elif self.time_unit == "minutes":
return self.add_minutes()
else:
return self.add_day()
def add_minutes(self):
"""
在当前时间基础上加减指定分钟
:return: 计算结果
"""
return (self.start_time + timedelta(minutes=self.interval)).strftime("%Y-%m-%d %H:%M:%S")
def add_hours(self):
"""
在当前时间基础上加减指定小时
:return: 计算结果
"""
return (self.start_time + timedelta(hours=self.interval)).strftime("%Y-%m-%d %H:%M:%S")
def add_day(self):
"""
在当前时间基础上加减指定天
:return: 计算结果
"""
return (self.start_time + timedelta(days=self.interval)).strftime("%Y-%m-%d %H:%M:%S")
def add_seconds(self):
"""
在当前时间基础上加减指定秒
:return: 计算结果
"""
return (self.start_time + timedelta(seconds=self.interval)).strftime("%Y-%m-%d %H:%M:%S")
def add_weeks(self):
"""
在当前时间基础上加减指定周
:return: 计算结果
"""
return (self.start_time + timedelta(weeks=self.interval)).strftime("%Y-%m-%d %H:%M:%S")
def mistiming(start_time, end_time):
"""计算两个时间差"""
stime = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
etime = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
total_seconds = (etime - stime).total_seconds()
if total_seconds < 0:
total_seconds = (stime - etime).total_seconds()
day = total_seconds // (60 * 60 * 24)
rest_seconds = total_seconds % (60 * 60 * 24)
hours = rest_seconds // (60 * 60)
rest_seconds = rest_seconds % (60 * 60)
mins = rest_seconds // 60
seconds = rest_seconds % 60
return f"{int(day)}{int(hours)}小时 {int(mins)}分钟 {int(seconds)}"

48
common/handle_yagmail.py Normal file
View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:21
# @Author : Flora.Chen
# @File : handle_yagmail.py
# @Software: PyCharm
# @Desc: 通过第三方模块yagmail发送邮件
import yagmail
from loguru import logger
import os
class EmailServe:
@staticmethod
def send_email(setting: dict):
"""
入参一个字典
:param user: 发件人邮箱
:param password: 邮箱授权码
:param host: 发件人使用的邮箱服务 例如smtp.163.com
:param contents: 内容
:param addressees: 收件人列表
:param title: 邮件标题
:param enclosures: 附件列表
:return:
"""
logger.debug("------------ 连接邮件服务器 ---------------")
yag = yagmail.SMTP(
setting['user'],
setting['password'],
setting['host'])
logger.debug("------------ 开始发送邮件 ---------------")
if os.path.exists(setting['enclosures']):
yag.send(
to=setting['addressees'],
subject=setting['title'],
contents=setting['contents'],
attachments=setting['enclosures'])
else:
yag.send(
to=setting['addressees'],
subject=setting['title'],
contents=setting['contents'])
logger.debug("------------ 邮件发送完毕,关闭邮件服务器连接 ---------------")
yag.close()

43
common/handle_yaml.py Normal file
View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:10
# @Author : Flora.Chen
# @File : handle_yaml.py
# @Software: PyCharm
# @Desc: yaml文件的读写方法
import yaml
import os
class HandleYaml:
"""
读取yaml数据以及往yaml文件中写入数据
"""
def __init__(self, file):
"""
初始化yaml文件如果不存在则创建
:param file: 文件绝对路径
"""
if not os.path.exists(file):
with open(file, mode="a", encoding="utf-8"):
print("{}文件创建成功~".format(file))
self.file = file
def read(self):
"""
读取yaml文件数据
:return: 读取到的数据
"""
with open(file=self.file, mode="r", encoding="UTF-8") as f:
return yaml.load(f, Loader=yaml.SafeLoader)
def write(self, data, mode="a"):
"""
往yaml文件中写入数据默认是追加写入
:param data: 要写入的数据
:param mode: 写入模式
:return:
"""
with open(self.file, mode=mode, encoding="utf-8") as f:
yaml.dump(data, f)

122
common/helper.py Normal file
View File

@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:17
# @Author : Flora.Chen
# @File : helper.py
# @Software: PyCharm
# @Desc: 封装一些小方法
import os
from faker import Faker
import urllib.request
from loguru import logger
import zipfile
import os
def get_newest_file(path):
"""获取目录中创建时间最新的文件名"""
files = os.listdir(path)
a = 0
newest_file = ""
for file in files:
file_path = os.path.join(path, file)
timestamp = os.path.getctime(file_path)
if timestamp > a:
a = timestamp
newest_file = file_path
return newest_file
def generate_characters(n):
"""随机生成指定长度的字符"""
fk = Faker("zh_CN")
res = fk.name()
while True:
if len(res) < n:
res = res + fk.sentence()
else:
break
return res[:n]
def create_dir(path):
"""创建目录"""
if not os.path.exists(path):
os.mkdir(path)
logger.debug(f"成功创建目录: {path}")
logger.debug(f"目录地址已存在: {path}")
def download_img(url, path):
"""
通过Python自带的库urllib下载图片
需要导入import urllib.request
:param url: url地址
:param path: 图片保存的绝对路径
"""
try:
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
if response.getcode() == 200:
logger.debug("请求状态码200 开始下载图片......")
with open(path, "wb") as fb:
fb.write(response.read())
return path
except Exception as e:
logger.debug("捕获到异常,下载图片失败")
return f"{e}"
def zip_file(file_path: str, out_path: str):
"""
压缩指定文件夹
:param file_path: 目标文件夹路径
:param out_path: 压缩文件保存路径+xxxx.zip
:return:
"""
# 如果传入的路径是一个目录才进行压缩操作
if os.path.isdir(file_path):
logger.debug("目标路径是一个目录,开始进行压缩......")
# 写入
zip = zipfile.ZipFile(out_path, "w", zipfile.ZIP_DEFLATED)
for path, dirnames, filenames in os.walk(file_path):
# 去掉目标跟路径,只对目标文件夹下边的文件及文件夹进行压缩
fpath = path.replace(file_path, '')
for filename in filenames:
zip.write(
os.path.join(
path, filename), os.path.join(
fpath, filename))
zip.close()
logger.debug("压缩完成!")
else:
logger.error("目标路径不是一个目录,请检查!")
def unzip_file():
pass
def delete_dir_file(file_path):
"""
删除指定目录下的所有文件
:param file_path: 目标文件夹路径 (存在多级路径的暂不支持)
"""
paths = os.listdir(file_path)
if paths:
logger.debug("目标目录存在文件或目录,进行删除操作")
for item in paths:
path = os.path.join(file_path, item)
# 如果目标路径是一个文件使用os.remove删除
if os.path.isfile(path):
os.remove(path)
# 如果目标路径是一个目录使用os.rmdir删除
if os.path.isdir(path):
os.rmdir(path)
else:
logger.debug("目标目录不存在文件或目录,不需要删除")
if __name__ == '__main__':
delete_dir_file(r"/home/flora/chy/auotest/report")

62
common/project_tree.py Normal file
View File

@ -0,0 +1,62 @@
# -*- 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)

6
config/__init__.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/13 20:41
# @Author : Flora.Chen
# @File : __init__.py.py
# @Software: PyCharm
# @Desc:

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<h1 class="title" style="text-align: center;color: #bd2c00">API & UI 自动化测试报告</h1>
<h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;各位同事,大家好!</h3>
<h4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本轮自动化测试已完成!可以通过下载附件查看测试报告的详细信息!</h4>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;查看测试报告的方法:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下载附件est_report.zip(自动化测试报告)<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本地解压test_report.zip(自动化测试报告)<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;请使用已安装Live Server 插件的VsCode打开解压目录下的index.html查看报告<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style type="text/css">
.logo {
float: left;
min-width: 40px;
height: 40px;
}
.title{
text-align: center;
color: rgb(235, 30, 15);
}
.desc{
text-align: left;
}
</style>
</head>
<body>
<div >
<a href="https://*******/" class="fl mr50" style="min-width: 45px;">
<img alt="Trustie" class="logo" src="https://*******/images/avatars/LaboratorySetting/1nav?t=1610102853">
</a>
</div>
<div>
<h1 class="title">API & UI 自动化测试报告</h1>
</div>
<div class="desc">
<p><span style="color: red; ">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Jenkins自动发送的测试报告邮件无需回复</span></p>
<h4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;各位同事,大家好,以下为${PROJECT_NAME}自动化测试构建信息</h4><br/>
<h4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在线测试报告直达链接:<a href="${PROJECT_URL}/${BUILD_NUMBER}/allure">${PROJECT_URL}/${BUILD_NUMBER}/allure</a></h4>
</div>
<br/>
<div>
<table width="900" cellpadding="8px" cellspacing="8px" class="table">
<tbody>
<tr>
<td><br/>
<b><font color="#0B610B">项目描述:${JOB_DESCRIPTION}<br></font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>项目名称 ${PROJECT_NAME}</td>
</tr>
<tr>
<td>构建编号 第${BUILD_NUMBER}次构建</td>
</tr>
<tr>
<td>触发原因: ${CAUSE}</td>
</tr>
<tr>
<td>构建状态: ${BUILD_STATUS}</td>
</tr>
<tr>
<td>构建日志: <a href="${PROJECT_URL}${BUILD_NUMBER}/console">${PROJECT_URL}${BUILD_NUMBER}/console</a></td>
</tr>
<tr>
<td>构建Url <a href="${BUILD_URL}">${BUILD_URL}</a></td>
</tr>
<tr>
<td>工作目录 <a href="${PROJECT_URL}ws">${PROJECT_URL}ws</a></td>
</tr>
<tr>
<td>项目Url <a href="${PROJECT_URL}">${PROJECT_URL}</a></td>
</tr>
<tr>
<td>allure在线测试报告<a href="${PROJECT_URL}/${BUILD_NUMBER}/allure">${PROJECT_URL}/${BUILD_NUMBER}/allure</a></td>
</tr>
</tbody>
</table>
</div>
</body>

131
config/settings.py Normal file
View File

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 22:50
# @Author : Flora.Chen
# @File : settings.py
# @Software: PyCharm
# @Desc: 配置文件
import enum
import os
import platform
# 项目根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 通用模块目录
COMMON_DIR = os.path.join(BASE_DIR, "common")
# 第三方库目录
LIB_DIR = os.path.join(BASE_DIR, "lib")
# 配置模块目录
CONF_DIR = os.path.join(BASE_DIR, "config")
# 数据模块目录
DATA_DIR = os.path.join(BASE_DIR, "data")
# API数据模块目录
API_DATA = os.path.join(DATA_DIR, "api_data")
# UI数据模块目录
UI_DATA = os.path.join(DATA_DIR, "ui_data")
# 日志保存目录
LOG_DIR = os.path.join(BASE_DIR, "log")
if not os.path.exists(LOG_DIR):
os.mkdir(LOG_DIR)
# 图片保存目录
IMG_DIR = os.path.join(BASE_DIR, "image")
if not os.path.exists(IMG_DIR):
os.mkdir(IMG_DIR)
# 报告保存目录
REPORT_DIR = os.path.join(BASE_DIR, "report")
if not os.path.exists(REPORT_DIR):
os.mkdir(REPORT_DIR)
# API测试用例模块
API_CASE_DIR = os.path.join(BASE_DIR, "test_api")
# API测试用例模块
UI_CASE_DIR = os.path.join(BASE_DIR, "test_ui")
class EnvConfig(enum.Enum):
"""环境配置信息"""
env_mapping = {
"test": {
"forge": "https://test*****.trustie.net"
},
"live": {
"forge": "https://*****.trustie.net"
},
}
# 不同环境对应的用户名密码
users_mapping = {
"test": {"user": "*****.", "pwd": "*****.", "nickname": "*****."},
"live": {"user": "*****.", "pwd": "*****.@", "nickname": "*****."}
}
# 数据库配置
db_mapping = {
}
class RunConfig:
"""
运行测试配置
"""
# 配置浏览器驱动类型(chrome/firefox/chrome-headless/firefox-headless)。
driver_type = "chrome-headless"
# driver_type = "chrome"
# 失败重跑次数
rerun = "0"
# 当达到最大失败数,停止执行
max_fail = "10"
# 浏览器驱动(不需要修改)
driver = None
# 浏览器驱动放置的位置
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"D:/Program Files/python39/chromedriver.exe"
if "firefox" in driver_type:
driver_path = ""
# 基准的请求头信息
headers = {
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
# 发送邮件的相关配置信息
email = {
"user": "*****.@*****..com.cn", # 发件人邮箱
"password": "*****", # 发件人邮箱授权码
"host": "smtp.qiye.aliyun.com",
"contents": ['<h1 class="title" style="text-align: center;color: #bd2c00">API & UI 自动化测试报告</h1>',
'<h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;各位同事,大家好!</h3>',
'<h4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本轮自动化测试已完成!可以通过下载附件查看测试报告的详细信息!</h4>',
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;查看测试报告的方法:<br/>"
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下载附件est_report.zip(自动化测试报告)<br/>"
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本地解压test_report.zip(自动化测试报告)<br/>",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;请使用已安装Live Server 插件的VsCode打开解压目录下的index.html查看报告<br/>",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"]
,
"addressees": ["*****.@*****..com.cn"], # 收件人邮箱
"title": "自动化测试报告",
"enclosures": os.path.join(REPORT_DIR, "autotest_report.zip") # 附件
}

131
config/settings_example.py Normal file
View File

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 22:50
# @Author : Flora.Chen
# @File : settings_example.py
# @Software: PyCharm
# @Desc: 配置文件
import enum
import os
import platform
# 项目根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 通用模块目录
COMMON_DIR = os.path.join(BASE_DIR, "common")
# 第三方库目录
LIB_DIR = os.path.join(BASE_DIR, "lib")
# 配置模块目录
CONF_DIR = os.path.join(BASE_DIR, "config")
# 数据模块目录
DATA_DIR = os.path.join(BASE_DIR, "data")
# API数据模块目录
API_DATA = os.path.join(DATA_DIR, "api_data")
# UI数据模块目录
UI_DATA = os.path.join(DATA_DIR, "ui_data")
# 日志保存目录
LOG_DIR = os.path.join(BASE_DIR, "log")
if not os.path.exists(LOG_DIR):
os.mkdir(LOG_DIR)
# 图片保存目录
IMG_DIR = os.path.join(BASE_DIR, "image")
if not os.path.exists(IMG_DIR):
os.mkdir(IMG_DIR)
# 报告保存目录
REPORT_DIR = os.path.join(BASE_DIR, "report")
if not os.path.exists(REPORT_DIR):
os.mkdir(REPORT_DIR)
# API测试用例模块
API_CASE_DIR = os.path.join(BASE_DIR, "test_api")
# API测试用例模块
UI_CASE_DIR = os.path.join(BASE_DIR, "test_ui")
class EnvConfig(enum.Enum):
"""环境配置信息"""
env_mapping = {
"test": {
"forge": "https://test*****.trustie.net"
},
"live": {
"forge": "https://*****.trustie.net"
},
}
# 不同环境对应的用户名密码
users_mapping = {
"test": {"user": "*****.", "pwd": "*****.", "nickname": "*****."},
"live": {"user": "*****.", "pwd": "*****.@", "nickname": "*****."}
}
# 数据库配置
db_mapping = {
}
class RunConfig:
"""
运行测试配置
"""
# 配置浏览器驱动类型(chrome/firefox/chrome-headless/firefox-headless)。
driver_type = "chrome-headless"
# driver_type = "chrome"
# 失败重跑次数
rerun = "0"
# 当达到最大失败数,停止执行
max_fail = "10"
# 浏览器驱动(不需要修改)
driver = None
# 浏览器驱动放置的位置
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"D:/Program Files/python39/chromedriver.exe"
if "firefox" in driver_type:
driver_path = ""
# 基准的请求头信息
headers = {
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"
}
# 发送邮件的相关配置信息
email = {
"user": "*****.@*****..com.cn", # 发件人邮箱
"password": "*****", # 发件人邮箱授权码
"host": "smtp.qiye.aliyun.com",
"contents": ['<h1 class="title" style="text-align: center;color: #bd2c00">API & UI 自动化测试报告</h1>',
'<h3>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;各位同事,大家好!</h3>',
'<h4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本轮自动化测试已完成!可以通过下载附件查看测试报告的详细信息!</h4>',
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;查看测试报告的方法:<br/>"
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下载附件est_report.zip(自动化测试报告)<br/>"
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本地解压test_report.zip(自动化测试报告)<br/>",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;请使用已安装Live Server 插件的VsCode打开解压目录下的index.html查看报告<br/>",
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"]
,
"addressees": ["*****.@*****..com.cn"], # 收件人邮箱
"title": "自动化测试报告",
"enclosures": os.path.join(REPORT_DIR, "autotest_report.zip") # 附件
}

59
conftest.py Normal file
View File

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:33
# @Author : Flora.Chen
# @File : draft_bk.py.py
# @Software: PyCharm
# @Desc:
import pytest
from loguru import logger
from config.settings import EnvConfig
from api.login_api import TrustieLogin
from api.project_api import ProjectApi
# ------------------------------------- START: 配置运行环境 ---------------------------------------#
def pytest_addoption(parser):
"""
pytest_addoption 可以让用户注册一个自定义的命令行参数方便用户将数据传递给 pytest
这个 Hook 方法一般和 内置 fixture pytestconfig 配合使用pytest_addoption 注册命令行参数pytestconfig 通过配置对象读取参数的值
:param parser:
:return:
"""
parser.addoption(
# action="store" 默认,只存储参数的值,可以存储任何类型的值,此时 default 也可以是任何类型的值,而且命令行参数多次使用也只能生效一个,最后一个值覆盖之前的值;
"--env", action="store",
default="test",
choices=["test", "live"], # choices 只允许输入的值的范围
help="通过自定义命令行参数-env设置当前运行的环境"
)
# 从配置对象中读取自定义参数的值
@pytest.fixture(scope="session", autouse=True)
def env(request):
env = request.config.getoption("--env")
env_mapping = EnvConfig.env_mapping.value
env = env_mapping.get(f"{env}")
forge_env = env.get("forge")
logger.debug(f"当前的执行环境:{forge_env}")
return {"forge_env": forge_env}
@pytest.fixture(scope="session")
def get_user(request):
env = request.config.getoption("--env")
users_mapping = EnvConfig.users_mapping.value
users = users_mapping.get(f"{env}")
return users
# ------------------------------------- END: 配置运行环境 ---------------------------------------#
@pytest.fixture(scope="session")
def get_cookies(env, get_user):
login = TrustieLogin(host=env)
return login.login_api(user=get_user.get("user"), pwd=get_user.get("pwd")).cookies

View File

@ -0,0 +1,12 @@
# 登录的用例数据
case:
-
id: 1
title: 用户名密码正确,登录成功
interface: login
url: /api/accounts/login.json
method: POST
header: { }
json: {"login": "*****","password": "*****","autologin": 1}
expected: auotest
extractor: $..user_id

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 14:22
# @Author : Flora.Chen
# @File : login_data.py
# @Software: PyCharm
# @Desc:
# 登录成功
success = [
{"title": "正确的用户名和密码登录成功", "user": "{}", "pwd": "{}", "expected": "{}"}
]
# 登录失败-错误提示是输入框下红色提示
failed_error_message = [
{"title": "用户名为空,登录失败,有错误提示:用户名不能为空", "user": "", "pwd": "{}", "expected": ["用户名不能为空"]}
]
# 登录失败,错误提示是弹窗
failed_error_pop = [
{"title": "用户名错误,登录失败,提示错误的账号或密码:", "user": "18774970061111", "pwd": "", "expected": ["错误的账号或密码"]},
{"title": "用户名正确密码第一次错误提示你已经输错密码1次还剩余4次机会", "user": "{}", "pwd": "1111111111",
"expected": ["你已经输错密码1次还剩余4次机会"]},
{"title": "用户名正确密码第二次错误提示你已经输错密码2次还剩余3次机会", "user": "{}", "pwd": "11111222222",
"expected": ["你已经输错密码2次还剩余3次机会"]},
{"title": "用户名正确密码第三次错误提示你已经输错密码3次还剩余2次机会", "user": "{}", "pwd": "1111333333",
"expected": ["你已经输错密码3次还剩余2次机会"]},
# {"title": "用户名正确密码第四次错误提示你已经输错密码4次还剩余1次机会", "user": "{}", "pwd": "11115555555",
# "expected": ["你已经输错密码4次还剩余1次机会"]},
# {"title": "用户名正确,密码第五次错误,提示:登录密码出错已达上限,账号已被锁定, 请60分钟后重新登录或找回密码", "user": "{}", "pwd": "11115555555",
# "expected": ["登录密码出错已达上限,账号已被锁定, 请60分钟后重新登录或找回密码"]}
]
# 登录失败,密码为空不能点击登录
failed_password_empty = [
{"title": "用户名正确, 密码为空,登录按钮无法点击", "user": "{}", "pwd": "", "expected": "bluebutton"}
]

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/23 14:25
# @Author : Flora.Chen
# @File : new_project_data.py
# @Software: PyCharm
# @Desc:
from common.handle_faker import *
import random
new_project = [
{
"title": "新建公有项目",
"owner": "",
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": e_fk.name().replace(" ", ""),
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"license": random.choice(["0BSD", "AAL", "AFL-1.1", "389-exception"]),
"private": False,
"expected": ""
},
{
"title": "新建私有项目",
"owner": "",
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": e_fk.name().replace(" ", ""),
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"license": random.choice(["0BSD", "AAL", "AFL-1.1", "389-exception"]),
"private": True,
"expected": ""
}
]
export_project = [
{
"title": "导入项目(公有)-原项目是公有项目",
"owner": "",
"mirror_url": "https://gitee.com/paddlepaddle/Parakeet.git",
"mirror_private": False,
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": "Parakeet",
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"private": False,
"expected": "",
"mirror_type": False
},
{
"title": "导入项目(私有)-原项目是公有项目",
"owner": "",
"mirror_url": "https://gitee.com/chinasoft4_ohos/NewQuickAction.git",
"mirror_private": False,
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": "NewQuickAction",
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"private": True,
"expected": "",
"mirror_type": True
},
{
"title": "导入项目(私有)-原项目是私有项目",
"owner": "",
"mirror_url": "https://gitee.com/florachy/notification-system.git",
"mirror_user": "*****",
"mirror_pwd": "*****",
"mirror_private": True,
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": "notification-system",
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"private": True,
"expected": "",
"mirror_type": True
},
{
"title": "导入项目(公有)-原项目是私有项目",
"owner": "",
"mirror_url": "https://gitee.com/florachy/finger-change.git",
"mirror_user": "*****",
"mirror_pwd": "*****",
"mirror_private": True,
"name": "Auto Test " + e_fk.name(),
"desc": z_fk_text,
"repository_name": "finger-change",
"project_category": random.choice(["机器学习", "大数据", "深度学习", "人工智能", "量子计算", "智慧医疗", "自动驾驶", "其他"]),
"project_language": random.choice(["C", "Ruby", "C#", "HTML", "C++"]),
"ignore": random.choice(["Ada", "Actionscript", "Ansible", "Android", "Agda"]),
"private": False,
"expected": "",
"mirror_type": False
}
]

6
lib/__init__.py Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# @Time : 2021/8/14 12:37
# @Author : Flora.Chen
# @File : __init__.py.py
# @Software: PyCharm
# @Desc:

185
lib/allure-2.14.0/bin/allure Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## allure start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/.." >/dev/null
export APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="allure"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and ALLURE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/lib/allure-commandline-2.14.0.jar:$APP_HOME/lib/jcommander-1.81.jar:$APP_HOME/lib/allure-generator-2.14.0.jar:$APP_HOME/lib/jackson-dataformat-xml-2.12.3.jar:$APP_HOME/lib/jackson-module-jaxb-annotations-2.12.3.jar:$APP_HOME/lib/jackson-annotations-2.12.3.jar:$APP_HOME/lib/jackson-core-2.12.3.jar:$APP_HOME/lib/jackson-dataformat-yaml-2.12.3.jar:$APP_HOME/lib/allure-plugin-api-2.14.0.jar:$APP_HOME/lib/allure-model-2.13.0.jar:$APP_HOME/lib/jackson-databind-2.12.3.jar:$APP_HOME/lib/commons-io-2.8.0.jar:$APP_HOME/lib/jetty-server-9.4.20.v20190813.jar:$APP_HOME/lib/slf4j-log4j12-1.7.28.jar:$APP_HOME/lib/snakeyaml-1.27.jar:$APP_HOME/lib/javax.servlet-api-3.1.0.jar:$APP_HOME/lib/jetty-http-9.4.20.v20190813.jar:$APP_HOME/lib/jetty-io-9.4.20.v20190813.jar:$APP_HOME/lib/allure1-model-1.0.jar:$APP_HOME/lib/slf4j-api-1.7.28.jar:$APP_HOME/lib/log4j-1.2.17.jar:$APP_HOME/lib/jaxb-api-2.3.1.jar:$APP_HOME/lib/httpclient-4.5.13.jar:$APP_HOME/lib/tika-core-1.26.jar:$APP_HOME/lib/freemarker-2.3.31.jar:$APP_HOME/lib/jetty-util-9.4.20.v20190813.jar:$APP_HOME/lib/opencsv-4.6.jar:$APP_HOME/lib/flexmark-0.62.2.jar:$APP_HOME/lib/woodstox-core-6.2.4.jar:$APP_HOME/lib/stax2-api-4.2.1.jar:$APP_HOME/lib/javax.activation-api-1.2.0.jar:$APP_HOME/lib/properties-2.0.RC5.jar:$APP_HOME/lib/jaxb-utils-1.0.jar:$APP_HOME/lib/httpcore-4.4.13.jar:$APP_HOME/lib/commons-beanutils-1.9.3.jar:$APP_HOME/lib/commons-logging-1.2.jar:$APP_HOME/lib/commons-codec-1.11.jar:$APP_HOME/lib/commons-text-1.3.jar:$APP_HOME/lib/commons-lang3-3.12.0.jar:$APP_HOME/lib/commons-collections4-4.2.jar:$APP_HOME/lib/flexmark-util-format-0.62.2.jar:$APP_HOME/lib/flexmark-util-ast-0.62.2.jar:$APP_HOME/lib/flexmark-util-builder-0.62.2.jar:$APP_HOME/lib/flexmark-util-dependency-0.62.2.jar:$APP_HOME/lib/flexmark-util-html-0.62.2.jar:$APP_HOME/lib/flexmark-util-sequence-0.62.2.jar:$APP_HOME/lib/flexmark-util-collection-0.62.2.jar:$APP_HOME/lib/flexmark-util-data-0.62.2.jar:$APP_HOME/lib/flexmark-util-misc-0.62.2.jar:$APP_HOME/lib/flexmark-util-visitor-0.62.2.jar:$APP_HOME/lib/jakarta.xml.bind-api-2.3.2.jar:$APP_HOME/lib/jakarta.activation-api-1.2.1.jar:$APP_HOME/lib/commons-collections-3.2.2.jar:$APP_HOME/lib/annotations-15.0.jar:$APP_HOME/lib/config
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $ALLURE_OPTS -classpath "\"$CLASSPATH\"" io.qameta.allure.CommandLine "$APP_ARGS"
exec "$JAVACMD" "$@"

View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem allure startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%..
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and ALLURE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\lib\allure-commandline-2.14.0.jar;%APP_HOME%\lib\jcommander-1.81.jar;%APP_HOME%\lib\allure-generator-2.14.0.jar;%APP_HOME%\lib\jackson-dataformat-xml-2.12.3.jar;%APP_HOME%\lib\jackson-module-jaxb-annotations-2.12.3.jar;%APP_HOME%\lib\jackson-annotations-2.12.3.jar;%APP_HOME%\lib\jackson-core-2.12.3.jar;%APP_HOME%\lib\jackson-dataformat-yaml-2.12.3.jar;%APP_HOME%\lib\allure-plugin-api-2.14.0.jar;%APP_HOME%\lib\allure-model-2.13.0.jar;%APP_HOME%\lib\jackson-databind-2.12.3.jar;%APP_HOME%\lib\commons-io-2.8.0.jar;%APP_HOME%\lib\jetty-server-9.4.20.v20190813.jar;%APP_HOME%\lib\slf4j-log4j12-1.7.28.jar;%APP_HOME%\lib\snakeyaml-1.27.jar;%APP_HOME%\lib\javax.servlet-api-3.1.0.jar;%APP_HOME%\lib\jetty-http-9.4.20.v20190813.jar;%APP_HOME%\lib\jetty-io-9.4.20.v20190813.jar;%APP_HOME%\lib\allure1-model-1.0.jar;%APP_HOME%\lib\slf4j-api-1.7.28.jar;%APP_HOME%\lib\log4j-1.2.17.jar;%APP_HOME%\lib\jaxb-api-2.3.1.jar;%APP_HOME%\lib\httpclient-4.5.13.jar;%APP_HOME%\lib\tika-core-1.26.jar;%APP_HOME%\lib\freemarker-2.3.31.jar;%APP_HOME%\lib\jetty-util-9.4.20.v20190813.jar;%APP_HOME%\lib\opencsv-4.6.jar;%APP_HOME%\lib\flexmark-0.62.2.jar;%APP_HOME%\lib\woodstox-core-6.2.4.jar;%APP_HOME%\lib\stax2-api-4.2.1.jar;%APP_HOME%\lib\javax.activation-api-1.2.0.jar;%APP_HOME%\lib\properties-2.0.RC5.jar;%APP_HOME%\lib\jaxb-utils-1.0.jar;%APP_HOME%\lib\httpcore-4.4.13.jar;%APP_HOME%\lib\commons-beanutils-1.9.3.jar;%APP_HOME%\lib\commons-logging-1.2.jar;%APP_HOME%\lib\commons-codec-1.11.jar;%APP_HOME%\lib\commons-text-1.3.jar;%APP_HOME%\lib\commons-lang3-3.12.0.jar;%APP_HOME%\lib\commons-collections4-4.2.jar;%APP_HOME%\lib\flexmark-util-format-0.62.2.jar;%APP_HOME%\lib\flexmark-util-ast-0.62.2.jar;%APP_HOME%\lib\flexmark-util-builder-0.62.2.jar;%APP_HOME%\lib\flexmark-util-dependency-0.62.2.jar;%APP_HOME%\lib\flexmark-util-html-0.62.2.jar;%APP_HOME%\lib\flexmark-util-sequence-0.62.2.jar;%APP_HOME%\lib\flexmark-util-collection-0.62.2.jar;%APP_HOME%\lib\flexmark-util-data-0.62.2.jar;%APP_HOME%\lib\flexmark-util-misc-0.62.2.jar;%APP_HOME%\lib\flexmark-util-visitor-0.62.2.jar;%APP_HOME%\lib\jakarta.xml.bind-api-2.3.2.jar;%APP_HOME%\lib\jakarta.activation-api-1.2.1.jar;%APP_HOME%\lib\commons-collections-3.2.2.jar;%APP_HOME%\lib\annotations-15.0.jar;%APP_HOME%\lib\config
@rem Execute allure
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %ALLURE_OPTS% -classpath "%CLASSPATH%" io.qameta.allure.CommandLine %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable ALLURE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%ALLURE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@ -0,0 +1,3 @@
plugins:
- junit-plugin
- packages-plugin

View File

@ -0,0 +1,10 @@
plugins:
- junit-xml-plugin
- xunit-xml-plugin
- trx-plugin
- behaviors-plugin
- packages-plugin
- screen-diff-plugin
- xctest-plugin
- jira-plugin
- xray-plugin

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.LoggerLog
org.eclipse.jetty.LEVEL=WARN

View File

@ -0,0 +1,8 @@
log4j.rootLogger=INFO, stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%m%n
log4j.logger.org.mortbay.log = INFO

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
The directory with Allure plugins. To add the plugin simply unpack it to this folder.

View File

@ -0,0 +1,7 @@
id: behaviors
name: Behaviors aggregator
description: The aggregator adds behaviors tab to the report
extensions:
- io.qameta.allure.behaviors.BehaviorsPlugin
jsFiles:
- index.js

View File

@ -0,0 +1,178 @@
'use strict';
allure.api.addTranslation('en', {
tab: {
behaviors: {
name: 'Behaviors'
}
},
widget: {
behaviors: {
name: 'Features by stories',
showAll: 'show all'
}
}
});
allure.api.addTranslation('ru', {
tab: {
behaviors: {
name: 'Функциональность'
}
},
widget: {
behaviors: {
name: 'Функциональность',
showAll: 'показать все'
}
}
});
allure.api.addTranslation('zh', {
tab: {
behaviors: {
name: '功能'
}
},
widget: {
behaviors: {
name: '特性场景',
showAll: '显示所有'
}
}
});
allure.api.addTranslation('de', {
tab: {
behaviors: {
name: 'Verhalten'
}
},
widget: {
behaviors: {
name: 'Features nach Stories',
showAll: 'Zeige alle'
}
}
});
allure.api.addTranslation('nl', {
tab: {
behaviors: {
name: 'Functionaliteit'
}
},
widget: {
behaviors: {
name: 'Features en storys',
showAll: 'Toon alle'
}
}
});
allure.api.addTranslation('he', {
tab: {
behaviors: {
name: 'התנהגויות'
}
},
widget: {
behaviors: {
name: 'תכונות לפי סיפורי משתמש',
showAll: 'הצג הכול'
}
}
});
allure.api.addTranslation('br', {
tab: {
behaviors: {
name: 'Comportamentos'
}
},
widget: {
behaviors: {
name: 'Funcionalidades por história',
showAll: 'Mostrar tudo'
}
}
});
allure.api.addTranslation('ja', {
tab: {
behaviors: {
name: '振る舞い'
}
},
widget: {
behaviors: {
name: 'ストーリー別の機能',
showAll: '全て表示'
}
}
});
allure.api.addTranslation('es', {
tab: {
behaviors: {
name: 'Funcionalidades'
}
},
widget: {
behaviors: {
name: 'Funcionalidades por Historias de Usuario',
showAll: 'mostrar todo'
}
}
});
allure.api.addTranslation('kr', {
tab: {
behaviors: {
name: '동작'
}
},
widget: {
behaviors: {
name: '스토리별 기능',
showAll: '전체 보기'
}
}
});
allure.api.addTranslation('fr', {
tab: {
behaviors: {
name: 'Comportements'
}
},
widget: {
behaviors: {
name: 'Thèmes par histoires',
showAll: 'Montrer tout'
}
}
});
allure.api.addTab('behaviors', {
title: 'tab.behaviors.name', icon: 'fa fa-list',
route: 'behaviors(/)(:testGroup)(/)(:testResult)(/)(:testResultTab)(/)',
onEnter: (function (testGroup, testResult, testResultTab) {
return new allure.components.TreeLayout({
testGroup: testGroup,
testResult: testResult,
testResultTab: testResultTab,
tabName: 'tab.behaviors.name',
baseUrl: 'behaviors',
url: 'data/behaviors.json',
csvUrl: 'data/behaviors.csv'
});
})
});
allure.api.addWidget('widgets', 'behaviors', allure.components.WidgetStatusView.extend({
rowTag: 'a',
title: 'widget.behaviors.name',
baseUrl: 'behaviors',
showLinks: true
}));

View File

@ -0,0 +1,5 @@
id: custom-logo
name: Custom logo aggregator
description: The aggregator replaces default Allure logo with a custom one
cssFiles:
- styles.css

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 128 128" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><rect fill="#F4F5F5" height="1520" opacity="0" width="727.938" x="-59.984" y="-351"/></g><g id="Layer_2"><g><circle cx="64" cy="64" fill="#6E9583" r="64"/><g><defs><circle cx="64" cy="64" id="SVGID_3_" r="64"/></defs><clipPath id="SVGID_2_"><use overflow="visible" xlink:href="#SVGID_3_"/></clipPath><polygon clip-path="url(#SVGID_2_)" fill="#648778" points="93.572,29.677 128,64 128,128 54.36,128 33.341,106.906 "/></g><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" fill="#F1F1F1"/><g><defs><path d="M84.044,20H36.018C33.579,20,32,22.11,32,24.549v78.903c0,2.439,1.579,4.549,4.018,4.549h55.989 c2.439,0,4.018-2.11,4.018-4.549V32.143L84.044,20z" id="SVGID_5_"/></defs><clipPath id="SVGID_4_"><use overflow="visible" xlink:href="#SVGID_5_"/></clipPath><g clip-path="url(#SVGID_4_)"><polygon fill="#DDE1F1" points="50.948,67.621 65.539,82.042 42.971,83.087 49.777,90 42.971,91.087 49.277,97.555 42.971,99.087 53.027,109.305 97.684,109.305 97.684,75.707 97.075,54.055 81.059,37.758 70.97,44.918 62.684,35.107 "/></g></g><path d="M88.186,32.138l7.839,0.005L84.044,20v7.96C84.044,30.398,85.769,32.138,88.186,32.138z" fill="#C2DFC9"/><path d="M84,83.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,83.5,84,83.5z" fill="#495260"/><path d="M84,91.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,91.5,84,91.5z" fill="#495260"/><path d="M84,99.5H44c-0.828,0-1.5-0.672-1.5-1.5s0.672-1.5,1.5-1.5h40c0.828,0,1.5,0.672,1.5,1.5 S84.828,99.5,84,99.5z" fill="#495260"/><g><path d="M69.568,31.844l-1.319,11.303c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 c-2.379-6.723-7.827-11.477-14.212-13.254C70.21,30.872,69.636,31.26,69.568,31.844z" fill="#0E9CD9"/><path d="M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469 c-0.527-0.251-1.155,0.023-1.329,0.58c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191 c4.655-1.537,8.399-4.531,10.911-8.3c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896 C70.751,58.296,68.929,59.49,66.68,59.901z" fill="#E95037"/><path d="M62.239,43.074c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071 c-2.218,0.035-4.469,0.421-6.676,1.202c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" fill="#69B32D"/><g><defs><path d="M69.695,30.76l-1.446,12.387c2.314,0.88,4.242,2.728,5.132,5.245c0.573,1.619,0.631,3.292,0.274,4.851 l10.257,4.895c0.527,0.252,1.155-0.023,1.329-0.581c1.308-4.188,1.323-8.819-0.253-13.273 C82.476,37.185,76.541,32.281,69.695,30.76z M66.68,59.901c-3.653,0.668-7.398-1.12-9.176-4.38 c-1.094-2.006-1.312-4.174-0.858-6.157L46.39,44.469c-0.527-0.251-1.155,0.023-1.329,0.58 c-1.286,4.118-1.322,8.663,0.175,13.049c3.701,10.842,15.624,16.783,26.503,13.191c4.655-1.537,8.399-4.531,10.911-8.3 c0.324-0.486,0.141-1.147-0.385-1.398l-10.257-4.896C70.751,58.296,68.929,59.49,66.68,59.901z M62.239,43.074 c0.734-0.26,1.479-0.405,2.22-0.464l1.316-11.275c0.067-0.576-0.389-1.08-0.968-1.071c-2.218,0.035-4.469,0.421-6.676,1.202 c-4.455,1.576-8.045,4.5-10.479,8.151c-0.324,0.486-0.142,1.147,0.385,1.399l10.257,4.895 C59.282,44.654,60.62,43.647,62.239,43.074z" id="SVGID_7_"/></defs><clipPath id="SVGID_6_"><use overflow="visible" xlink:href="#SVGID_7_"/></clipPath><circle clip-path="url(#SVGID_6_)" cx="65.151" cy="51.304" fill="#FFFFFF" opacity="0.4" r="12.507"/></g></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More